Written before:
Last weekend I took some time to record the initial design and server-side detailed design of a simple Socket chat program I wrote. I finally waited for the soft exam certificate I took before graduation on Tuesday, and then I spent the day of overtime. Today happened to be Friday, and I planned to record the detailed design of the client and the Common module, because I will be busy with other things starting this weekend.
design:
Client design is mainly divided into two parts, namely socket communication module design and UI-related design.
Client socket communication design:
The design here is actually similar to the design of the server. The difference is that the server receives heartbeat packets, while the client sends heartbeat packets. Since the client only communicates with one server (the communication between clients is also distributed by the server), only a thread pool of size 2 is used to handle these two things (newFixedThreadPool(2)). The corresponding processing classes are ReceiveListener and KeepAliveDog. When the ReceiveListener is initialized, a Callback is sent as a callback to the client receives the server message. The default implementation of Callback is DefaultCallback. DefaultCallback is distributed to different handlers through HF according to different events. ClientHolder stores the current client information. The design is as follows:
The specific implementation of Socket communication module:
[Client.java]
Client is the entrance to the client to connect to the server. To create a client, you need to specify a Callback as the callback when the client receives the server message. Then the client's start() method starts the listening to the server (ReceiveListener). When the ReceiveListener receives the data sent by the server, the callback (Callback) method is called to process it. At the same time, the client also needs to send a heartbeat packet to notify the server that it is still connected to the server. The heartbeat packet is kept by the client. Alive() is started and implemented by KeepAliveDog; these two steps are executed by a thread pool of newFixedThreadPool(2) with a fixed size of 2. It may be more reasonable to use a newFixedThreadPool(1) and newScheduledThreadPool(1) here to handle it, because the heartbeat package is sent regularly, and this is how the server implements it (this subsequent adjustment). The specific code of the Client is as follows (the other two methods are exposed here to obtain the socket and the user to which the current socket belongs):
/** * Client* @author yaolin * */public class Client { private final Socket socket; private String from; private final ExecutorService pool; private final Callback callback; public Client(Callback callback) throws IOException { this.socket = new Socket(ConstantValue.SERVER_IP, ConstantValue.SERVER_PORT); this.pool = Executors.newFixedThreadPool(2); this.callback = callback; } public void start() { pool.execute(new ReceiveListener(socket, callback)); } public void keepAlive(String from) { this.from = from; pool.execute(new KeepAliveDog(socket, from)); } public Socket getSocket() { return socket; } public String getFrom() { return from; }}[KeepAliveDog.java]
After the client establishes a connection with the server (this program refers to the successful login, because the client's socket will be managed by the server's SocketHolder after the login is successful), it is necessary to send a heartbeat packet to the server every time to tell the server that it is still in contact with the server, otherwise the server will discard the socket without interaction after a period of time (see the server's blog for details). The code of KeepAliveDog is implemented as follows (it may be adjusted to newScheduledThreadPool(1) later, so the code here will also be adjusted):
/** * KeepAliveDog : tell Server this client is running; * * @author yaolin */public class KeepAliveDog implements Runnable { private final Socket socket; private final String from; public KeepAliveDog(Socket socket, String from) { this.socket = socket; this.from = from; } @Override public void run() { while (socket != null && !socket.isClosed()) { try { PrintWriter out = new PrintWriter(socket.getOutputStream()); AliveMessage message = new AliveMessage(); message.setFrom(from); out.println(JSON.toJSON(message)); out.flush(); Thread.sleep(ConstantValue.KEEP_ALIVE_PERIOD * 1000); } catch (Exception e) { LoggerUtil.error("Client send message failed !" + e.getMessage(), e); } } }}[ReceiveListener.java]
The client's start() method starts the listening to the server and is implemented by the ReceiveListener. After receiving the message from the server, the ReceiveListener will call back the Callback's doWork() method to let the callback handle the specific business logic. Therefore, the ReceiveListener is only responsible for listening to the messages on the server, and the specific processing is handled by Callback. It should be mentioned here that when the message type is a file type, it will sleep to configure the execution interval, so that the doWork in Callback can read the file flow to the server instead of directly entering the next loop. The design here is similar to the server. The specific implementation code of ReceiveListener is as follows:
public class ReceiveListener implements Runnable { private final Socket socket; private final Callback callback; public ReceiveListener(Socket socket, Callback callback) { this.socket = socket; this.callback = callback; } @Override public void run() { if (socket != null) { while (!socket.isClosed()) { try { InputStream is = socket.getInputStream(); String line = null; StringBuffer sb = null; if (is.available() > 0) { BufferedReader bufr = new BufferedReader(new InputStreamReader(is)); sb = new StringBuffer(); while (is.available() > 0 && (line = bufr.readLine()) != null) { sb.append(line); } LoggerUtil.trach("RECEIVE [" + sb.toString() + "] AT " + new Date()); callback.doWork(socket, sb.toString()); BaseMessage message = JSON.parseObject(sb.toString(), BaseMessage.class); if (message.getType() == MessageType.FILE) { // PAUSE TO RECEIVE FILE LoggerUtil.trach("CLIENT:PAUSE TO RECEIVE FILE"); Thread.sleep(ConstantValue.MESSAGE_PERIOD); } } else { Thread.sleep(ConstantValue.MESSAGE_PERIOD); } } catch (Exception e) { LoggerUtil.error("Client send message failed !" + e.getMessage(), e); } } } } } } }[Callback.java, DefaultCallback.java]
From the above, we can see that the client's processing of messages is a Callback callback, and its Callback is just an interface. All Callback implementations implement the interface to process messages according to their needs. Here, the default implementation of Callback is DefaultCallback. DefaultCallback only processes three types of messages, namely chat messages, file messages, and return messages. For chat messages, DefaultCallback will obtain the corresponding interface through the Router route in the UI (see the UI design below for details), and then display the message in the corresponding chat box; for file messages, DefaultCallback will write the file to the path specified in the configuration (the file is received without the user's permission here. This design is not very friendly, so for now); for return messages, DefaultCallback will be called to different handlers according to the KEY in the return message. The specific code is as follows:
public interface Callback { public void doWork(Socket server, Object data); } public class DefaultCallback implements Callback { @Override public void doWork(Socket server, Object data) { if (data != null) { BaseMessage message = JSON.parseObject(data.toString(), BaseMessage.class); switch (message.getType()) { case MessageType.CHAT: handleChatMessage(data); break; case MessageType.FILE: handleFileMessage(server, data); break; case MessageType.RETURN: handleReturnMessage(data); break; } } } private void handleChatMessage(Object data) { ChatMessage m = JSON.parseObject(data.toString(), ChatMessage.class); String tabKey = m.getFrom();// FROM JComponent comp = Router.getView(ChatRoomView.class).getComponent(ChatRoomView.CHATTABBED); if (comp instanceof JTabbedPane) { JTabbedPane tab = (JTabbedPane) comp; int index = tab.indexOfTab(tabKey); if (index == -1) { tab.addTab(tabKey, ResultHolder.get(tabKey).getScrollPane()); } JTextArea textArea = ResultHolder.get(tabKey).getTextArea(); textArea.setText(new StringBuffer() .append(textArea.getText()).append(System.lineSeparator()).append(System.lineSeparator()) .append(" [").append(m.getOwner()).append("] : ").append(System.lineSeparator()) .append(m.getContent()) .toString()); // SCROLL TO BOTTOM textArea.setCaretPosition(textArea.getText().length()); } } private void handleFileMessage(Socket server, Object data) { FileMessage message = JSON.parseObject(data.toString(), FileMessage.class); if (message.getSize() > 0) { OutputStream os = null; try { if (server != null) { InputStream is = server.getInputStream(); File dir = new File(ConstantValue.CLIENT_RECEIVE_DIR); if (!dir.exists()) { dir.mkdirs(); } os = new FileOutputStream( new File(PathUtil.combination(ConstantValue.CLIENT_RECEIVE_DIR, new Date().getTime() + message.getName()))); int total = 0; while (!server.isClosed()) { if (is.available() > 0) { byte[] buff = new byte[ConstantValue.BUFF_SIZE]; int len = -1; while (is.available() > 0 && (len = is.read(buff)) != -1) { os.write(buff, 0, len); total += len; LoggerUtil.debug("RECEIVE BUFF [" + len + "]"); } os.flush(); if (total >= message.getSize()) { LoggerUtil.info("RECEIVE BUFF [OK]"); break; } } } } } } catch (Exception e) { LoggerUtil.error("Receive file failed ! " + e.getMessage(), e); } finally { if (os != null) { try { os.close(); } catch (Exception ignore) { } os = null; } } } } private void handleReturnMessage(Object data) { ReturnMessage m = JSON.parseObject(data.toString(), ReturnMessage.class); if (StringUtil.isNotEmpty(m.getKey())) { switch (m.getKey()) { case Key.NOTIFY: // Notify client to update usr list HF.getHandler(Key.NOTIFY).handle(data); break; case Key.LOGIN: HF.getHandler(Key.LOGIN).handle(data); break; case Key.REGISTER: HF.getHandler(Key.REGISTER).handle(data); break; case Key.LISTUSER: HF.getHandler(Key.LISTUSER).handle(data); break; case Key.TIP: HF.getHandler(Key.TIP).handle(data); break; } } }}[Handler.java, HF.java, ListUserHdl.java...]
The Handler component is responsible for processing messages of the server's return message type. DefaultCallback distributes messages to different handlers according to different KEYs. This is also a simple factory component. It is similar to the data received by the server. The complete class diagram is as follows:
The code for this section is given below. In order to reduce the space, all the code implemented by Handler is collected.
public interface Handler { public Object handle(Object obj); } public class HF { public static Handler getHandler(String key) { switch (key) { case Key.NOTIFY: return new NotifyHdl(); case Key.LOGIN: return new LoginHdl(); case Key.REGISTER: return new RegisterHdl(); case Key.LISTUSER: return new ListUserHdl(); case Key.TIP: return new TipHdl(); } return null; }} public class ListUserHdl implements Handler { @Override public Object handle(Object obj) { if (obj != null) { try { ReturnMessage rm = JSON.parseObject(obj.toString(), ReturnMessage.class); if (rm.isSuccess() && rm.getContent() != null) { ClientListUserDTO dto = JSON.parseObject(rm.getContent().toString(), ClientListUserDTO.class); JComponent comp = Router.getView(ChatRoomView.class).getComponent(ChatRoomView.LISTUSRLIST); if (comp instanceof JList) { @SuppressWarnings("unchecked") // JList<String> listUsrList = (JList<String>) comp; List<String> listUser = new LinkedList<String>(); listUser.addAll(dto.getListUser()); Collections.sort(listUser); listUser.add(0, ConstantValue.TO_ALL); listUsrList.setListData(listUser.toArray(new String[]{})); } } } catch (Exception e) { LoggerUtil.error("Handle listUsr failed! " + e.getMessage(), e); } } return null; }} public class LoginHdl implements Handler { @Override public Object handle(Object obj) { if (obj != null) { try { ReturnMessage rm = JSON.parseObject(obj.toString(),ReturnMessage.class); if (rm.isSuccess()) { Router.getView(RegisterAndLoginView.class).trash(); Router.getView(ChatRoomView.class).create().display(); ClientHolder.getClient().keepAlive(rm.getTo()); // KEEP... } else { Container container = Router.getView(RegisterAndLoginView.class).container(); if (container != null) { // show error JOptionPane.showMessageDialog(container, rm.getMessage()); } } } catch (Exception e) { LoggerUtil.error("Handle login failed! " + e.getMessage(), e); } } return null; }} public class NotifyHdl implements Handler { @Override public Object handle(Object obj) { if (obj != null) { try { ReturnMessage rm = JSON.parseObject(obj.toString(), ReturnMessage.class); if (rm.isSuccess() && rm.getContent() != null) { ClientNotifyDTO dto = JSON.parseObject(rm.getContent().toString(), ClientNotifyDTO.class); JComponent comp = Router.getView(ChatRoomView.class).getComponent(ChatRoomView.LISTUSRLIST); if (comp instanceof JList) { @SuppressWarnings("unchecked") // JList<String> listUsrList = (JList<String>) comp; List<String> listUser = modelToList(listUsrList.getModel()); if (dto.isFlag()) { if (!listUser.contains(dto.getUsername())) { listUser.add(dto.getUsername()); listUser.remove(ConstantValue.TO_ALL); Collections.sort(listUser); listUser.add(0, ConstantValue.TO_ALL); } } else { listUser.remove(dto.getUsername()); } listUsrList.setListData(listUser.toArray(new String[]{})); } } } catch (Exception e) { LoggerUtil.error("Handle nofity failed!" + e.getMessage(), e); } } return null; } private List<String> modelToList(ListModel<String> listModel) { List<String> list = new LinkedList<String>(); if (listModel != null) { for (int i = 0; i < listModel.getSize(); i++) { list.add(listModel.getElementAt(i)); } } return list; }} public class RegisterHdl implements Handler { @Override public Object handle(Object obj) { if (obj != null) { try { ReturnMessage rm = JSON.parseObject(obj.toString(),ReturnMessage.class); Container container = Router.getView(RegisterAndLoginView.class).container(); if (container != null) { if (rm.isSuccess()) { JOptionPane.showMessageDialog(container, rm.getContent()); } else { JOptionPane.showMessageDialog(container, rm.getMessage()); } } } catch (Exception e) { LoggerUtil.error("Handle register failed! " + e.getMessage(), e); } } return null; }} public class TipHdl implements Handler { @Override public Object handle(Object obj) { if (obj != null) { try { ReturnMessage m = JSON.parseObject(obj.toString(), ReturnMessage.class); if (m.isSuccess() && m.getContent() != null) { String tabKey = m.getFrom(); String tip = m.getContent().toString(); JComponent comp = Router.getView(ChatRoomView.class).getComponent(ChatRoomView.CHATTABBED); if (comp instanceof JTabbedPane) { JTabbedPane tab = (JTabbedPane) comp; int index = tab.indexOfTab(tabKey); if (index == -1) { tab.addTab(tabKey, ResultHolder.get(tabKey).getScrollPane()); } JTextArea textArea = ResultHolder.get(tabKey).getTextArea(); textArea.setText(new StringBuffer() .append(textArea.getText()).append(System.lineSeparator()).append(System.lineSeparator()) .append(" [").append(m.getOwner()).append("] : ").append(System.lineSeparator()) .append(tip) .toString()); // SCROLL TO BOTTOM textArea.setCaretPosition(textArea.getText().length()); } } } catch (Exception e) { LoggerUtil.error("Handle tip failed! " + e.getMessage(), e); } } return null; }} There is another class for the Socket communication module, that is, ClientHolder, which is used to store the current Client, which is similar to the SocketHolder on the server.
/** * @author yaolin */public class ClientHolder { public static Client client; public static Client getClient() { return client; } public static void setClient(Client client) { ClientHolder.client = client; }}Specific implementation of UI module:
The above records the design of the socket communication module. Next, I record the design module of the UI. I don’t plan to write the UI by myself. After all, the writing is too ugly, so I may ask classmates or friends to help me knock it later. So I hand over the UI event processing to Action to handle it, and simply separate the UI design and event response. All UIs inherit JFrame and implement the View interface. The above Handler implementation class is obtained through Router (it will be returned directly if it exists, and it will be created and stored if it does not exist). The View provides the UI creation(), obtain container(), obtain the components in the UI getComponent(), display display(), and recycle trash(); ResultWrapper and ResultHolder are just for creating and storing chat tabs. The design is as follows:
[Router.java, View.java]
All UIs inherit JFrame and implement the View interface. The Handler implementation class obtains the specified UI through Router (it returns directly if it exists, and it creates and stores if it does not exist). The View provides the UI creation(), obtains container(), and obtains the components in the UI getComponent(), displays display(), and recycles trash(). The specific implementation is as follows:
/** * View route* @author yaolin */public class Router { private static Map<String, View> listRoute = new HashMap<String,View>(); public static View getView(Class<?> clazz) { View v = listRoute.get(clazz.getName()); if (v == null) { try { v = (View) Class.forName(clazz.getName()).newInstance(); listRoute.put(clazz.getName(), v); } catch (Exception e) { LoggerUtil.error("Create view failed! " + e.getMessage(), e); } } return v; }} /** * Canonical interfaces for all interfaces* @author yaolin * */public interface View { /** * */ public View create(); /** * */ public Container container(); /** * @param key */ public JComponent getComponent(String key); /** * */ public void display(); /** * */ public void trash(); }[RegisterAndLoginView.java, ChatRoomView.java]
Since I don't want to write the UI by myself, I just simply wrote two UI interfaces here, namely the registration and login interface and the chat interface. Here are two ugly interfaces:
Register login interface
Chat interface
The following are the specific codes for these two interfaces:
/** * Register and login* @author yaolin */public class RegisterAndLoginView extends JFrame implements View { private static final long serialVersionUID = 6322088074312546736L; private final RegisterAndLoginAction action = new RegisterAndLoginAction(); private static boolean CREATE = false; @Override public View create() { if (! CREATE) { init(); CREATE = true; } return this; } public Container container() { create(); return getContentPane(); } @Override public JComponent getComponent(String key) { return null; } @Override public void display() { setVisible(true); } @Override public void trash() { dispose(); } private void init() { // Attribute setSize(500, 300); setResizable(false); setLocationRelativeTo(null); // Container JPanel panel = new JPanel(); panel.setLayout(null); // Component // username JLabel lbUsername = new JLabel(I18N.TEXT_USERNAME); lbUsername.setBounds(100, 80, 200, 30); final JTextField tfUsername = new JTextField(); tfUsername.setBounds(150, 80, 230, 30); panel.add(lbUsername); panel.add(tfUsername); // password JLabel lbPassword = new JLabel(I18N.TEXT_PASSWORD); lbPassword.setBounds(100, 120, 200, 30); final JPasswordField pfPassword = new JPasswordField(); pfPassword.setBounds(150, 120, 230, 30); panel.add(lbPassword); panel.add(pfPassword); // btnRegister JButton btnRegister = new JButton(I18N.BTN_REGISTER); btnRegister.setBounds(100, 175, 80, 30); // btnLogin final JButton btnLogin = new JButton(I18N.BTN_LOGIN); btnLogin.setBounds(200, 175, 80, 30); // btnCancel JButton btnExit = new JButton(I18N.BTN_EXIT); btnExit.setBounds(300, 175, 80, 30); panel.add(btnRegister); panel.add(btnLogin); panel.add(btnExit); // Event pfPassword.addKeyListener(new KeyAdapter() { public void keyPressed(final KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) btnLogin.doClick(); } });// end of addKeyListener btnRegister.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { if (StringUtil.isEmpty(tfUsername.getText()) || StringUtil.isEmpty(new String(pfPassword.getPassword()))) { JOptionPane.showMessageDialog(getContentPane(), I18N.INFO_REGISTER_EMPTY_DATA); return ; } action.handleRegister(tfUsername.getText(), new String(pfPassword.getPassword())); } });// end of addActionListener btnLogin.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { if (StringUtil.isEmpty(tfUsername.getText()) || StringUtil.isEmpty(new String(pfPassword.getPassword()))) { JOptionPane.showMessageDialog(getContentPane(), I18N.INFO_LOGIN_EMPTY_DATA); return ; } action.handleLogin(tfUsername.getText(), new String(pfPassword.getPassword())); } });// end of addActionListener btnExit.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { System.exit(0); } });// end of addActionListener getContentPane().add(panel); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); }} /** * Client Chat Window* * @author yaolin */public class ChatRoomView extends JFrame implements View { private static final long serialVersionUID = -4515831172899054818L; public static final String LISTUSRLIST = "LISTUSRLIST"; public static final String CHATTABBED = "CHATTABBED"; private static boolean CREATE = false; private ChatRoomAction action = new ChatRoomAction(); private JList<String> listUsrList = null; private JTabbedPane chatTabbed = null; @Override public View create() { if (!CREATE) { init(); CREATE = true; } return this; } public Container container() { create(); return getContentPane(); } @Override public JComponent getComponent(String key) { create(); switch (key) { case LISTUSRLIST: return listUsrList; case CHATTABBED: return chatTabbed; } return null; } @Override public void display() { setVisible(true); } @Override public void trash() { dispose(); } public void init() { setTitle(I18N.TEXT_APP_NAME); setSize(800, 600); setResizable(false); setLocationRelativeTo(null); setLayout(new BorderLayout()); add(createChatPanel(), BorderLayout.CENTER); add(createUsrListView(), BorderLayout.EAST); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } private JComponent createChatPanel() { // FILE SELECTOR final JFileChooser fileChooser = new JFileChooser(); JPanel panel = new JPanel(new BorderLayout()); // CENTER chatTabbed = new JTabbedPane(); chatTabbed.addTab(ConstantValue.TO_ALL, ResultHolder.get(ConstantValue.TO_ALL).getScrollPane()); panel.add(chatTabbed, BorderLayout.CENTER); // SOUTH JPanel south = new JPanel(new BorderLayout()); // SOUTH - FILE JPanel middle = new JPanel(new BorderLayout()); middle.add(new JLabel(), BorderLayout.CENTER); // JUST FOR PADDING JButton btnUpload = new JButton(I18N.BTN_SEND_FILE); middle.add(btnUpload, BorderLayout.EAST); south.add(middle, BorderLayout.NORTH); // SOUTH - TEXTAREA final JTextArea taSend = new JTextArea(); taSend.setCaretColor(Color.BLUE); taSend.setMargin(new Insets(10, 10, 10, 10)); taSend.setRows(10); south.add(taSend, BorderLayout.CENTER); // SOUTH - BTN JPanel bottom = new JPanel(new BorderLayout()); bottom.add(new JLabel(), BorderLayout.CENTER); // JUST FOR PADDING JButton btnSend = new JButton(I18N.BTN_SEND); bottom.add(btnSend, BorderLayout.EAST); south.add(bottom, BorderLayout.SOUTH); btnUpload.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { if (! ConstantValue.TO_ALL.equals(chatTabbed.getTitleAt(chatTabbed.getSelectedIndex()))) { int returnVal = fileChooser.showOpenDialog(ChatRoomView.this); if (returnVal == JFileChooser.APPROVE_OPTION) { File file = fileChooser.getSelectedFile(); action.upload(chatTabbed.getTitleAt(chatTabbed.getSelectedIndex()), file); } } else { JOptionPane.showMessageDialog(getContentPane(), I18N.INFO_FILE_TO_ALL_ERROR); } } } }); btnSend.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { if (StringUtil.isNotEmpty(taSend.getText())) { action.send(chatTabbed.getTitleAt(chatTabbed.getSelectedIndex()), taSend.getText()); taSend.setText(null); } } }); panel.add(south, BorderLayout.SOUTH); return panel; } private JComponent createUsrListView() { listUsrList = new JList<String>(); listUsrList.setBorder(new LineBorder(Color.BLUE)); listUsrList.setListData(new String[] { ConstantValue.TO_ALL }); listUsrList.setFixedCellWidth(200); listUsrList.setFixedCellHeight(30); listUsrList.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { // chat to if (chatTabbed.indexOfTab(listUsrList.getSelectedValue()) == -1 && listUsrList.getSelectedValue() != null && !listUsrList.getSelectedValue().equals(ClientHolder.getClient().getFrom())) { chatTabbed.addTab(listUsrList.getSelectedValue(), ResultHolder.get(listUsrList.getSelectedValue()).getScrollPane()); chatTabbed.setSelectedIndex(chatTabbed.indexOfTab(listUsrList.getSelectedValue())); } } }); return listUsrList; }}[RegisterAndLoginAction.java, ChatRoomAction.java]
Here, the UI event processing is handled by Action, which simply separates the UI design and event response. The events of the RegisterAndLoginView are handled by RegisterAndLoginAction, and the events of the ChatRoomView are handled by ChatRoomAction. The specific implementation is as follows:
public class RegisterAndLoginAction { public void handleRegister(String username, String password) { if (StringUtil.isEmpty(username) || StringUtil.isEmpty(password)) { return; } RegisterMessage message = new RegisterMessage() .setUsername(username) .setPassword(password); message.setFrom(username); SendHelper.send(ClientHolder.getClient().getSocket(), message); } public void handleLogin(String username, String password) { if (StringUtil.isEmpty(username) || StringUtil.isEmpty(password)) { return; } LoginMessage message = new LoginMessage() .setUsername(username) .setPassword(password); message.setFrom(username); SendHelper.send(ClientHolder.getClient().getSocket(), message); }} There are two other classes for UI design, namely ResultHolder and ResultWrapper. ResultWrapper and ResultHolder are just for creating and storing chat tabs. The specific implementation is as follows:
public class ResultWrapper { private JScrollPane scrollPane; private JTextArea textArea; public ResultWrapper(JScrollPane scrollPane, JTextArea textArea) { this.scrollPane = scrollPane; this.textArea = textArea; } public JScrollPane getScrollPane() { return scrollPane; } public void setScrollPane(JScrollPane scrollPane) { this.scrollPane = scrollPane; } public JTextArea getTextArea() { return textArea; } public void setTextArea(JTextArea textArea) { this.textArea = textArea; }} public class ResultHolder { private static Map<String, ResultWrapper> listResultWrapper = new HashMap<String,ResultWrapper>(); public static void put(String key, ResultWrapper wrapper) { listResultWrapper.put(key, wrapper); } public static ResultWrapper get(String key) { ResultWrapper wrapper = listResultWrapper.get(key); if (wrapper == null) { wrapper = create(); put(key, wrapper); } return wrapper; } private static ResultWrapper create() { JTextArea resultTextArea = new JTextArea(); resultTextArea.setEditable(false); resultTextArea.setBorder(new LineBorder(Color.BLUE)); JScrollPane scrollPane = new JScrollPane(resultTextArea); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); ResultWrapper wrapper = new ResultWrapper(scrollPane, resultTextArea); return wrapper; }} The last one is given, the entry to which the client runs:
/** * * @author yaolin * */public class NiloayChat { public static void main(String[] args) { View v = Router.getView(RegisterAndLoginView.class).create(); try { v.display(); Client client = new Client(new DefaultCallback()); client.start(); ClientHolder.setClient(client); } catch (IOException e) { JOptionPane.showMessageDialog(v.container(), e.getMessage()); } }} demo download address: demo
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.