Java uses Socket to read data correctly

  • 2021-12-04 18:36:23
  • OfStack

Directory preface Socket using process Socket data reading and writing read () reading blocking problem readreadLine () reading blocking problem summary

Preface

Usually daily development is the most used Http communication, interface debugging is relatively simple, but also a relatively strong framework support (OkHttp).

The places where individuals usually use socket communication are Android communication with peripherals, Android communication with ssl service communication, which are all based on TCP/IP communication, and the server-side and device-side protocols cannot be modified, so they can only communicate according to relevant message formats.

However, there are many communication problems using socket, and there are two difficulties:

1. The socket communication layer should be written by itself and the IO stream is incorrectly used, and the data cannot be read or blocked or the data is incomplete

2. Request and response message formats are changeable (json, xml, others), and it is troublesome to analyze. If the first two formats are simple and have corresponding framework processing, other formats 1 generally need to be handled manually.

This summary is based on the first question, which is ultimately caused by using read () or readLine ()

Socket usage flow

1. Create socket

2. Connect socket

3. Get the input and output stream

Byte stream:


   InputStream  mInputStream = mSocket.getInputStream();
   OutputStream  mOutputStream = mSocket.getOutputStream();

Character stream:


  BufferedReader mBufferedReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream(), "UTF-8"));
  PrintWriter mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream(), "UTF-8")), true);

As for the actual use of byte stream or character stream, it depends on the actual situation. If the return is a string and the read-write is related to the message terminator (/r or/n or/r/n), read using a character stream, otherwise a byte stream.

4. Read and write data

5. Turn off socket

If it is an Socket short connection, the above five steps must be taken once;

If it is Socket long connection, just pay attention to the fourth point, and the above problems will be encountered if the fourth point is used carelessly.

In actual development, long connections are mostly used, with one connection and multiple data sending and receiving.

Special attention: Using a long connection can't close the input and output stream immediately after reading the data, but must close it when it is not used at last

Socket data reading and writing

When socket is blocked, a read timeout must be set to prevent socket reading data from hanging for a long time during debugging.


mSocket.setSoTimeout(10* 1000);  // Set the timeout for clients to read server data 

Read blocking problem using read ()

Daily writing 1:


 mOutputStream.write(bytes);
 mOutputStream.flush();
byte[] buffer = new byte[1024];
int n = 0;
ByteArrayOutputStream output = new ByteArrayOutputStream();
while (-1 != (n = mInputStream .read(buffer))) {
    output.write(buffer, 0, n);
}
// Processing data 
  output.close();
byte[] result = output.toByteArray();

There seems to be nothing wrong with the above, but sometimes mInputStream. read (buffer) will block, resulting in no execution in the while loop body

Daily Writing 2:


mOutputStream.write(bytes);
mOutputStream.flush();
int  available = mInputStream.available();
byte[] buffer = new byte[available];
in.read(buffer);

Although the above is not blocked, but not 1 will be able to read the data, available may be 0, because it is network communication, not 1 will return immediately after sending the data.

Or modify mInputStream. available () to read:


 int available = 0;
while (available == 0) {
    available = mInputStream.available() ; 
}

Although the data can be read above, the data is not complete.

Furthermore, the available method returns an estimated available length of the current flow, not the total length of the current traffic flow, but an estimated value; The read method reads the data from the stream into buffer, but the read length is 1 to buffer. length, and returns-1 if the stream ends or an exception is encountered.

Final writing (recursive reading):


 /**
     *  Recursive read stream 
     *
     * @param output
     * @param inStream
     * @return
     * @throws Exception
     */
    public void readStreamWithRecursion(ByteArrayOutputStream output, InputStream inStream) throws Exception {
        long start = System.currentTimeMillis();
        while (inStream.available() == 0) {
            if ((System.currentTimeMillis() - start) > 20* 1000) {// Timeout 
                throw new SocketTimeoutException(" Timeout read ");
            }
        }
        byte[] buffer = new byte[2048];
        int read = inStream.read(buffer);
        output.write(buffer, 0, read);
        SystemClock.sleep(100);// Need to delay below, otherwise there is still a probability of missing reading 
        int a = inStream.available();// Re-judgment 1 Whether there are available bytes or verify the integrity of the message according to the actual situation 
        if (a > 0) {
            LogUtils.w("======== There are leftovers: " + a + " Bytes of data not read ");
            readStreamWithRecursion(output, inStream);
        }
    }
    /**
     *  Read byte 
     *
     * @param inStream
     * @return
     * @throws Exception
     */
    private byte[] readStream(InputStream inStream) throws Exception {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        readStreamWithRecursion(output, inStream);
        output.close();
        int size = output.size();
        LogUtils.i(" Total number of bytes read this time: " + size);
        return output.toByteArray();
    }

After the above method is read once, the waiting time is fixed, and there will be data if there is no data. If there is no data, the response time will be too long, which will affect the user experience. We can optimize one more time:


 /**
     *  Recursive read stream 
     *
     * @param output
     * @param inStream
     * @return
     * @throws Exception
     */
    public void readStreamWithRecursion(ByteArrayOutputStream output, InputStream inStream) throws Exception {
        long start = System.currentTimeMillis();
        int time =500;// Milliseconds, look at the actual situation 
        while (inStream.available() == 0) {
            if ((System.currentTimeMillis() - start) >time) {// Timeout 
                throw new SocketTimeoutException(" Timeout read ");
            }
        }
        byte[] buffer = new byte[2048];
        int read = inStream.read(buffer);
        output.write(buffer, 0, read);
       int wait = readWait();
        long startWait = System.currentTimeMillis();
        boolean checkExist = false;
        while (System.currentTimeMillis() - startWait <= wait) {
            int a = inStream.available();
            if (a > 0) {
                checkExist = true;
                //            LogUtils.w("======== There are leftovers: " + a + " Bytes of data not read ");
                break;
            }
        }
        if (checkExist) {
            if (!checkMessage(buffer, read)) {
                readStreamWithRecursion(output, inStream, timeout);
            }
        }        
    }
    
 /**
     *  Read wait time in milliseconds 
     */
    protected int readWait() {
        return 100;
    }
    
    /**
     *  Read byte 
     *
     * @param inStream
     * @return
     * @throws Exception
     */
    private byte[] readStream(InputStream inStream) throws Exception {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        readStreamWithRecursion(output, inStream);
        output.close();
        int size = output.size();
        LogUtils.i(" Total number of bytes read this time: " + size);
        return output.toByteArray();
    }

The above latency rate is greatly reduced, and this method is being used to read at present, and there is no incomplete data reading and blocking phenomenon. However, in this case, we should also pay attention to the problem of message terminator and when to finish reading.

Using readreadLine () to read blocking problems

Daily writing:


 mPrintWriter.print(sendData+ "\r\n");   
 mPrintWriter.flush();
 String msg = mBufferedReader.readLine();
 // Processing data 

Careful you find, when sending data add terminator, if do not add terminator, cause readLine () block, read no data, finally throw SocketTimeoutException exception

Pay special attention to:

Message Terminator: Added according to the requirements of the actual server, if necessary, ask the back-end developer or see if there is any description in the interface document

Otherwise, a lot of precious time will be wasted in interface debugging, which will affect the later function development.

Considerations for using readLine ():

1. Note that the read data is/r or/n or/r/n

This sentence means that after the server finishes writing the data, it will print the message terminator/r or/n or/r/n;

Similarly, when the client writes data, it should also print the message terminator, so that the server can read the data.

2. When there is no data, it will block, and null will be returned only when the data flow is abnormal or disconnected 3. Avoid using readLine () when using data streams such as socket, so as to avoid 1 straight blocking in order to wait for 1 line feed/carriage return character

The above long connection is to send data once and read data once, which ensures the integrity of the current communication and needs synchronous processing when necessary.

There are also long connections, where the client opens a thread loop and blocks waiting for the server to send data, such as message push. Usually using long connections is to use different commands to send data and receive data to complete different tasks.

Summarize

In actual development, long connection is more complex, but also consider heartbeat, packet loss, disconnection and reconnection and other issues. When using long connections, pay special attention to the problem of message terminators. Terminators are only used to tell the client or server that the data has been sent and the client or server can read the data, otherwise the client or server will block the read () or readLine () method.


Related articles: