Socket network programming learning notes (10) simple chat room case

Posted by VMinder on Mon, 28 Feb 2022 02:58:59 +0100

1. Data transmission design of chat room

  • Necessary conditions: client and server
  • Necessary constraints: data transfer protocol
  • Principle: the server listens to the message source, the client links to the server and sends a message to the server

1.1 data interaction between client and server

The client sends a message to the server, and the server replies to the message, that is, the loopback message.

1.2 data transmission protocol

As shown in the figure above, when data is transmitted, a newline character needs to be added at the end, that is, the original 5-byte data has a length of 6 bytes in actual transmission.

1.3 server and multi client model

When there are multiple clients, the client will send messages to the server; After receiving a message from the Android server, the client will send it to the current client (the client) and the client (the client) after receiving the message. After these currently connected clients receive this message, they realize the process of sending PC messages to mobile phones.

2. How does a client send a message to another client

  • Is every client a server and a client? A: No
  • How do more than 2 devices interact with data? A: agree on a basic data format. Here, carriage return line feed is used as the truncation of information
  • Client server forward to client, as shown below:
    • User1 sends a message to the server, which forwards the message to other clients (such as User2), so as to realize the function of chat room

3. Implementation of message receiving in chat room

3.1 structure

The code is divided into four module s: lib client, sample client, sample Foo and sample server.

(1)lib-clink

This module provides tool classes for checking and stream processing.

(2) Sample client, the client code, depends on lib client and sample foo module s

(3) Sample foo, basic common class code

 

(4) sample server, the server code, needs to rely on lib clink and sample foo module s

(5) The tool classes and basic data classes of Lib clink and sample foo refer to the code logic of TCP point-to-point transmission

3.2 sample-client

The code of the first version is basically the same as that of TCP point-to-point transmission. The chat room is mainly forwarded on the TCPServer side, so the Client does not need code reconstruction.

3.3 sample-server

The first version of the code is basically the same as that of TCP point-to-point transmission. In order to realize the reception of chat room messages, it needs to be reconstructed. It mainly reconstructs tcpserver java ,ClientHandler.java class.

(1)ClientHandler.java - message forwarding

The original message is just printed to the console after it is received

// Print to screen
System.out.println(str);

To realize the chat room function, you need to notify the received messages. This can be implemented through the CloseNotify() interface. Here, the interface is modified and a forwarding interface method is added to notify the message back.

    /**
     * Message callback
     */
    public interface ClientHandlerCallback {
        // Self restlessness notice
        void onSelfClosed(ClientHandler handler);
        // Notify when a message is received
        void onNewMessageArrived(ClientHandler handler,String msg);
    }

Notify the message while printing the message to the screen:

       // Print to screen
       System.out.println(str);
       clientHandlerCallback.onNewMessageArrived(ClientHandler.this,str);

Call the onNewMessageArrived() method to forward. The main purpose here is to pass back the currently received message, and also to pass itself back.

(2) Construction of self description information

New clientInfo class variable:

    private final String clientInfo;

Self description initialization:

    public ClientHandler(Socket socket, ClientHandlerCallback clientHandlerCallback) throws IOException {
        this.socket = socket;
        this.readHandler = new ClientReadHandler(socket.getInputStream());
        this.writeHandler = new ClientWriteHandler(socket.getOutputStream());
        this.clientHandlerCallback = clientHandlerCallback;
        // Add self description information
        this.clientInfo = "A[" + socket.getInetAddress().getHostAddress() + "] P[" + socket.getPort() + "]";
        System.out.println("New client connection:" + clientInfo);
    }
    public String getClientInfo() {
        return clientInfo;
    }

(3) Reconstruct tcpserver java
Refactoring clienthandler The two callback methods of clienthandlercallback are mentioned here as tcpserver Java class.

Let tcpserver Implement clienthandler. Java Clienthandlercallback interface. And implement two methods:

    @Override
    public synchronized void onSelfClosed(ClientHandler handler) {
    }

    @Override
    public void onNewMessageArrived(ClientHandler handler, String msg) {
    }

And migrate the remove operation of the client build overflow thread to the implementation of the {onSelfClosed() method:

    @Override
    public synchronized void onSelfClosed(ClientHandler handler) {
        clientHandlerList.remove(handler);
    }

The original ClientHandler asynchronous thread processing logic is as follows:

        // Client build asynchronous thread
        ClientHandler clientHandler = new ClientHandler(client,
                    handler -> clientHandlerList.remove(handler));

After refactoring, it is as follows:

    // Client build asynchronous thread
    ClientHandler clientHandler = new ClientHandler(client,TCPServer.this);

Message forwarding:

    /**
     * Forward messages to other clients
     * @param handler
     * @param msg
     */
    @Override
    public void onNewMessageArrived(ClientHandler handler, String msg) {
        // Print to screen
        System.out.println("Received-" + handler.getClientInfo() + ":" + msg);
        // forward
        forwardingThreadPoolExecutor.execute(()->{
             for (ClientHandler clientHandler : clientHandlerList){
                 if(clientHandler.equals(handler)){
                      // Skip yourself
                      continue;
                 }
                 // Post messages to other clients
                 clientHandler.send(msg);
            }
        });
    }

(4) Solve the security problem of multithreading operation based on synchronized:

Because there are operations such as deleting, adding and traversing the clientHandlerList collection, which involves operations on all clients, in a multithreaded environment, the default List is not thread safe, so there is a multithreaded security problem.

    public void stop() {
        if (mListener != null) {
            mListener.exit();
        }
        synchronized (TCPServer.this){
            for (ClientHandler clientHandler : clientHandlerList) {
                clientHandler.exit();
            }
            clientHandlerList.clear();
        }

        // Stop thread pool
        forwardingThreadPoolExecutor.shutdownNow();
    }

    public synchronized void broadcast(String str) {
        for (ClientHandler clientHandler : clientHandlerList) {
            clientHandler.send(str);
        }
    }

    /**
     * Delete current message
     * @param handler
     */
    @Override
    public synchronized void onSelfClosed(ClientHandler handler) {
        clientHandlerList.remove(handler);
    }

    /**
     * Forward messages to other clients
     * @param handler
     * @param msg
     */
    @Override
    public void onNewMessageArrived(ClientHandler handler, String msg) {
        // Print to screen
        System.out.println("Received-" + handler.getClientInfo() + ":" + msg);
        // forward
        
    }

Class lock is added here to ensure thread safety of deletion operation.

The thread safety problem of adding operation is solved as follows:

          try {
              // Client build asynchronous thread
              ClientHandler clientHandler = new ClientHandler(client,TCPServer.this);
              // Read data and print
              clientHandler.readToPrint();
              // Add synchronization
              synchronized (TCPServer.this) {
                  clientHandlerList.add(clientHandler);
              }
          } catch (IOException e) {
              e.printStackTrace();
              System.out.println("Client connection exception:" + e.getMessage());
          }

(5) Asynchronous forwarding

        // forward
        clientHandlerCallback.onNewMessageArrived(ClientHandler.this,str);

In clienthandler In Java, the thread where the above code is located is the main thread, and there will always be messages coming in, so synchronous processing cannot be carried out. That will lead to the blocking of the current thread, so that the messages coming in later cannot be processed in time.

So when onNewMessageArrived () throws out the message, tcpserver The implementation of Java should be returned to other clients in the way of asynchronous forwarding. Create a new singleton thread pool for forwarding:

Add forwarding thread pool:

    // Forwarding thread pool
    private final ExecutorService forwardingThreadPoolExecutor;

    public TCPServer(int port) {
        this.port = port;
        this.forwardingThreadPoolExecutor = Executors.newSingleThreadExecutor();
    }

Forward delivery message to other clients:

    /**
     * Forward messages to other clients
     * @param handler
     * @param msg
     */
    @Override
    public void onNewMessageArrived(ClientHandler handler, String msg) {
        // Print to screen
        System.out.println("Received-" + handler.getClientInfo() + ":" + msg);
        // forward
        forwardingThreadPoolExecutor.execute(()->{
            synchronized (TCPServer.this){
                for (ClientHandler clientHandler : clientHandlerList){
                    if(clientHandler.equals(handler)){
                        // Skip yourself
                        continue;
                    }
                    // Post messages to other clients
                    clientHandler.send(msg);
                }
            }
        });
    }

To prevent repeated sending after the client goes offline:

ClientHandler.java - ClientWriteHandler
       /**
         * Send to client
         * @param str
         */
        void send(String str) {
            // If the transmission has been completed, return
            if(done){
                return;
            }
            executorService.execute(new WriteRunnable(str));
        }

4. Chat room Server/Client startup and test

Start one Server and three clients:

As shown in the figure above, after the Server is started, each Client will add the Client information to the list of the Server, and the Server will forward or send messages to each terminal each time.

As shown in the figure above, it is a simulated chat between Server and three clients. At this time, in the Server, when user1 sends a message, the Server reads the message locally and forwards it to the other two non current client objects. If it is replied by the Server, the current client is empty and a message will be sent to all clients.

To sum up, according to the previous ideas and logic, a simple chat room message forwarding function is realized.

Topics: network socket server