This is the 16th day of my participation in the August More Text Challenge. For details, see:August is more challenging

Understanding Socket Communication (3)

The first two articles are theoretical knowledge, which is the real stuff, this article will explain and prove the theoretical knowledge in code, including the complete self-encapsulated long-link communication. Since I work on Android, code analysis tends to be more client-side.

A,

Simply said

Scenario: the upper machine (client) wants to send a message to the lower machine (server)

So let’s stretch out some requirements

  1. The message content may be a string.
  2. The message content can be large.
  3. The content of the message is very important. Make sure you receive it.
  4. Let each other know when they stop talking

2. Code design

2.1 Starting the Socket TCP Server

The lower machine corresponds to the server. Before sending messages, the server should be run first, so that the upper machine (client) can connect.

Example Start the Socket TCP service

            ServerSocket serverSocket = new ServerSocket(Config.PORT);
            serverSocket.setReuseAddress(true);
            Socket socket = serverSocket.accept();
            socket.setSendBufferSize(3000 * 1024);
Copy the code

In fact only new ServerSocket(port) can already start the service, see how it is implemented internally.

public ServerSocket(int port) throws IOException { this(port, 50, null); } public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException { setImpl(); if (port < 0 || port > 0xFFFF) throw new IllegalArgumentException( "Port value out of range: " + port); if (backlog < 1) backlog = 50; try { bind(new InetSocketAddress(bindAddr, port), backlog); } catch(SecurityException e) { close(); throw e; } catch(IOException e) { close(); throw e; }}Copy the code

The focus is on the Bind method.

The setReuseAddress() method is to reuse the port address to avoid the problem that the port is occupied. The setSendBufferSize() method sets the size of the buffer. It does not mean that the size of the buffer can be set to send such large data. The MTU of data transmission is first limited to the minimum bottleneck, such as the buffer size of the client, the size of the bandwidth, etc.

2.1 TCP Server design

Since there may be more than one client, you need a container to hold that many clients.

We need to put a lot of New SocketServerClients in the container.

What is SocketServerClient? It is a class that can manage client objects. It can listen to messages from the client, send messages to the client, and listen to whether the client is online.

  • SocketServerClientSince this is a blocking method, we need to open a thread and wait for the message to arrive.
New thread while (true) {try {InputStream InputStream = socket.getinputStream (); out = socket.getOutputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String str = reader.readLine(); // String for received messages} catch (IOException e) {e.printStackTrace(); }}Copy the code
  • SocketServerClientMethod to send a message we just need in the output streamOutputStreamJust put in the data
String data = msg + "\n";
byte[] bytes = data.getBytes();
out.write(bytes);
out.flush();
Copy the code
  • Manage the Socket client
List serverClients = new ArrayList<>();
serverClients.add(client);
Copy the code

2.2 Client-connection

There are two ways for a client to connect to a server

  • Methods a
Socket socket = new Socket(ip, port);
Copy the code
  • Way 2
 Socket socket = new Socket();
 socket.connect(new InetSocketAddress(ip, port), 10 * 1000);
Copy the code

The second method is recommended here, because it is convenient to set the connection timeout. Some people say that the first method can also set setSoTimeout(), but I tested it, it is not the connection timeout.

Complete code:

/** * Try to establish a TCP connection ** @param IP * @param port */ private Boolean startTcpConnection(final String IP, final int port) { try { if (mSocket == null) { mSocket = new Socket(); mSocket.connect(new InetSocketAddress(ip, port), 10 * 1000); mSocket.setKeepAlive(true); mSocket.setTcpNoDelay(true); mSocket.setReuseAddress(true); mSocket.setReceiveBufferSize(3000 * 1024); } is = mSocket.getInputStream(); br = new BufferedReader(new InputStreamReader(is)); OutputStream os = mSocket.getOutputStream(); pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)), true); Logwrapper. d(TAG, "TCP created successfully..." ); return true; } catch (Exception e) { e.printStackTrace(); LogWrapper.e(TAG, "startTcpConnection error:" + e.toString()); } return false; }Copy the code

2.2 Client-Listening messages

Messages received by the create receiving thread must be accompanied by a newline, otherwise readLine cannot read them. The readLine method has been tested and there is no sticky packet problem. The key point is whether the long data server sends with a lock. Although TCP is ordered, it does not guarantee that the server will put the long data into the buffer in the correct order.

private void startReceiveTcpThread() { ThreadPool.getInstance().execute(new Runnable() { @Override public void run() { String line = ""; try { while ((line = br.readLine()) ! = null) { handleReceiveTcpMessage(line); } if (line == null) {logwrapper. e(TAG, "line == null "), start to disconnect..." ); disConnect(); }} catch (IOException e) {e.printStackTrace(); Logwrapper. e(TAG, "Received message exception, start disconnect... : "+ e. oString ()); disConnect(); }}}); }Copy the code

2.3 Client-Send messages

You can just drop the data in the PrintWriter

public void sendTcpMessage(final String msg, final SendCallback sendCallback) { ThreadPool.getInstance().execute(new Runnable() { @Override public void run() { try {  pw.println(msg); if (sendCallback ! = null) sendCallback.success(); } catch (Exception e) { if (sendCallback ! = null) sendCallback.failed(); }}}); }Copy the code

2.4 Client-Heartbeat Mechanism

If the network is forcibly disconnected, both sides cannot receive messages immediately. This is where the heartbeat is needed to keep both sides online. The principle of the heartbeat mechanism is, for example, one heartbeat every 10 seconds. If the recipient receives no response after sending the heartbeat for three times, the recipient is deemed to be offline. In this case, you should run the disconnect method to disconnect the TCP connection and update the status in time.

The flowchart is as follows:

Graph of TD [to] -- > B (sending heartbeat) B > C {heart failure is greater than three times} -- > C is | | D [off] -- > C whether | | E/reset the heartbeat

The key code of heartbeat mechanism is as follows:

Private class HeartRunnable implements Runnable {@override public void run() {LogWrapper. D (TAG, "trunnable implements Runnable "); mHeartCurIndex++; If (mHeartCurIndex > HEART_SEND_COUNT) {// Failed three times. Logwrapper. d(TAG, "Heartbeat mechanism - three failures - automatic disconnection "); if (mTcpSocket ! = null) {logwrapper. e(TAG, "Heartbeat mechanism - three failures - automatic disconnection "); mTcpSocket.disConnect(); } return; } String heartJson = "{ping}"; If (mTcpSocket! = null) mTcpSocket.sendTcpMessage(heartJson, null); if (mHeartRunnable ! = null) mHeartHandler.postDelayed(mHeartRunnable, HEART_SEND_INTERVAL); }}Copy the code

2.5 Client – Disconnection mechanism

In the normal use process, it is inevitable that the network is not good, this time, it is necessary to conduct disconnection and reconnection mechanism to make both sides online faster.

Graph of TD [to] -- > B (cut off) B - > C {break line reconnection number > 20} -- > C is | | D [20 seconds A reconnection] C - > whether | | E [5 seconds A reconnection]

Key code of disconnection reconnection mechanism

Public void errorReConnect() {// resetReconnectRunnable(); // If (! NetWorkUtils.isNetworkAvailable(AppCache.getContext())) { mReconnectCount = 0; return; } mReconnectCount++; long reconnectTime = minInterval; if (mReconnectCount > 20) { reconnectTime = maxInterval; } if (mReconnectRunnable == null) { mReconnectRunnable = new ReconnectRunnable(); } logWrapper. d(TAG, "reconnectTime" + reconnectTime); mReConnectHandler.postDelayed(mReconnectRunnable, reconnectTime); }Copy the code