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.