Please indicate the source of reprint, thank youAdvanced Android Socket usage (custom Protocol and Protocol Buffer usage)

The premise

I have written two articles about sockets before, but only briefly introduced the simple use of sockets FOR Tcp and Udp. If you have not seen the friends can go to see Android Socket programming (TCP) primary and Android Socket programming (UDP) primary. I believe that many friends in the company using socket development will customize the protocol to pass information. On the one hand, it is to safely exclude dirty data, and on the other hand, it is to process the data it needs more efficiently. Today we will introduce the socket custom Protocol and use the Protocol Buffer to parse data.

First of all,

Now that we’re talking about Protocol buffers, what is a Protocol Buffer? Why use the Protocol Buffer?

  • 1. What is a Protocol Buffer

A data storage format for structured data (similar to XML or Json). It serializes structured data to implement data storage/RPC data exchange. Why does the Protocol Buffer perform so well?

  • 2. Why use Protocol Buffer

    Before we answer this question, let’s first present a system scenario that is often encountered in real development. For example, our client programs are developed in Java and may run on different platforms, such as Linux, Windows or Android, while our server programs are usually based on Linux platforms and developed using C++ or Python. There are several ways to design message formats for data communication between the two programs, such as: 1, direct transmission of C/C++/Python language in a byte aligned structure data, as long as the structure is declared as a fixed-length format, then this way for C/C++/Python program is very convenient, just need to receive data according to the structure type forced conversion. It’s actually not too much trouble for a variable length structure. When sending data, you only need to define a structure variable and set the values of each member variable, and then send the binary data to the remote end as char*. On the contrary, this method will be very tedious for Java developers. First, they need to store the received data in ByteBuffer, then read each field one by one according to the agreed byte order, and assign the read value to the domain variable in another value object, so as to facilitate the writing of other code logic in the program. For this type of program, the benchmark of joint debugging is that the client and server must finish the writing of the message builder before it can be expanded, and this design will directly lead to the slow progress of Java program development. Even in the Debug phase, you will often encounter small errors in the concatenation of various fields in Java programs. 2. SOAP (WebService) is used as the format carrier of message packets. The packets generated by this method are text-based and contain a large amount of XML description information, which greatly increases the burden of network IO. Due to the complexity of XML parsing, the performance of packet parsing is greatly reduced. In short, the overall performance of the system will be significantly reduced by using this design method. The Protocol Buffer can solve the problems caused by the above two methods. Besides, the Protocol Buffer has another important advantage, that is, it can ensure the compatibility between the old and new versions of the same message. For details about how to use the Protocol Buffer, please go to the Protocol Buffer technical description (language specification)

The second

With that said, let’s take a look at our main topic today – custom socket protocol first look at a heartbeat return graph

  • 1. Protobuf

  • Suppose the client requests the packet body data protocol as follows

request.proto

syntax = "proto3"; Message Request {int32 uid = 0; string api_token = 1; }Copy the code

Send format:

{packet header}{command}{packet body}{packet header} -> Packet body into protubuf length {command} -> Command word parameters of the corresponding function {packet} -> Corresponding Protubuf data

  • Suppose the server returns the packet body data protocol

response.proto

syntax = "proto3"; Message Response {int32 login = 1; }Copy the code

The format returned by the server:

{packet header}{command}{status code}{packet body}{packet header} -> Length of packet body converted to Protubuf {Command} -> Command word parameters {status code} -> Status code of the corresponding state {packet} -> Corresponding Protubuf data

  • 2. Client socket writing method

  • Analysis: If a socket doesn’t break when the phone screen goes out or something, where do we write the socket and how do we keep the socket connected? It is most appropriate for Android to put it in service, and to ensure the connection status. Then, send a heartbeat packet to ensure the connection status. In that case, let’s write services and sockets.

  • 3. What kind of service do you like

     public class SocketService extends Service {
    
      Thread mSocketThread;
      Socket mSocket;
      InetSocketAddress mSocketAddress;
      //心跳线程
       Thread mHeartThread;
      //接收线程
      Thread mReceiveThread;
       //登录线程
      Thread mLoginThread;
       boolean isHeart = false;
       boolean isReceive = false;
    
    SocketBinder mBinder = new SocketBinder(this);
    
    public SocketService() {
    
    }
    
    @Override
    public void onCreate() {
       super.onCreate();
       createConnection();
       receiveMsg();
       isHeart = true;
       isReceive = true;
    }
    
    
    @Override
    public IBinder onBind(Intent intent) {
     return mBinder;
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
     startGps();
     sendHeart();
     if (!TextUtils.isEmpty(intent.getStringExtra(AppConfig.SERVICE_TAG))) {
         String TAG = intent.getStringExtra(AppConfig.SERVICE_TAG);
         switch (TAG) {
             case AppConfig.STOP_SERVICE_VALUE: {//停止服务
                 ClientSocket.getsInstance().shutDownConnection(mSocket);
                 stopSelf();
                 mSocket = null;
                 mHeartThread = null;
                 mReceiveThread = null;
                 mLoginThread = null;
                 mSocketThread = null;
                 isHeart = false;
                 isReceive = false;
                 break;
             }
            
             default:
                 break;
         }
     }
     return super.onStartCommand(intent, flags, startId);
    
     }
    
    
    /**
     * 发送心跳包
     */
    private void sendHeart() {
     mHeartThread = new Thread(new Runnable() {
         @Override
         public void run() {
             while (isHeart) {
                 ClientSocket.getsInstance().sendHeart(mSocket, SocketStatus.HEART_CODE);
                 try {
                     Thread.sleep(1000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         }
     });
     mHeartThread.start();
     }
    
     /**
      * 登录
      */
     private void login(final double mLatitude, final double mLongitude) {
       mLoginThread = new Thread(new Runnable() {
         @Override
         public void run() {
             if (PreferencesUtils.getInt(SocketService.this, Constants.USER_ID) != 0 &&
                     !TextUtils.isEmpty(PreferencesUtils.getString(SocketService.this,
                             Constants.USER_TOKEN))) {
                 Request.Request requestLogin =
                         Request.Request.newBuilder()
                                 .setUid(PreferencesUtils.getInt(SocketService.this,
                                         Constants.USER_ID))
                                 .setApiToken(PreferencesUtils.getString(SocketService.this,
                                         Constants.USER_TOKEN).trim())
                                 .build();
                
                 ClientSocket.getsInstance().sendLogin(mSocket, requestLogin, SocketStatus.LOGIN_CODE);
             
             }
         }
       });
       mLoginThread.start();
    
     }
    
    /**
     * 创建连接
     *
     * @return
     */
     public void createConnection() {
     mSocketThread = new Thread(new Runnable() {
         @Override
         public void run() {
             try {
                 mSocket = new Socket();
                 mSocketAddress = new InetSocketAddress(AppConfig.TCP_IP, AppConfig.TCP_PORT);
                 mSocket.connect(mSocketAddress, 20 * 1000);
                 // 设置 socket 读取数据流的超时时间
                 mSocket.setSoTimeout(20 * 1000);
                 // 发送数据包,默认为 false,即客户端发送数据采用 Nagle 算法;
                 // 但是对于实时交互性高的程序,建议其改为 true,即关闭 Nagle
                 // 算法,客户端每发送一次数据,无论数据包大小都会将这些数据发送出去
                 mSocket.setTcpNoDelay(true);
                 // 设置客户端 socket 关闭时,close() 方法起作用时延迟 30 秒关闭,如果 30 秒内尽量将未发送的数据包发送出去
                 // socket.setSoLinger(true, 30);
                 // 设置输出流的发送缓冲区大小,默认是4KB,即4096字节
                 mSocket.setSendBufferSize(10 * 1024);
                 // 设置输入流的接收缓冲区大小,默认是4KB,即4096字节
                 mSocket.setReceiveBufferSize(10 * 1024);
                 // 作用:每隔一段时间检查服务器是否处于活动状态,如果服务器端长时间没响应,自动关闭客户端socket
                 // 防止服务器端无效时,客户端长时间处于连接状态
                 mSocket.setKeepAlive(true);
             } catch (UnknownHostException e) {
                 Logger.e(e.getMessage() + "========+UnknownHostException");
                 e.printStackTrace();
             } catch (IOException e) {
                 createConnection();
                 Logger.e(e.getMessage() + "========IOException");
                 e.printStackTrace();
             } catch (NetworkOnMainThreadException e) {
                 Logger.e(e.getMessage() + "========NetworkOnMainThreadException");
                 e.printStackTrace();
             }
         }
     });
     mSocketThread.start();
    }
    
    
    /**
     * 接收
     */
    private void receiveMsg() {
     mReceiveThread = new Thread(new Runnable() {
         @Override
         public void run() {
             while (isReceive) {
                 try {
                     if (mSocket != null && mSocket.isConnected()) {
                         DataInputStream dis = ClientSocket.getsInstance().getMessageStream(mSocket);
                         ByteArrayOutputStream bos = new ByteArrayOutputStream();
                         if (dis != null) {
                             int length = 0;
                             int head = 0;
                             int buffer_size = 4;
                             byte[] headBuffer = new byte[4];
                             byte[] cmdBuffer = new byte[4];
                             byte[] stateBuffer = new byte[4];
                             length = dis.read(headBuffer, 0, buffer_size);
                             if (length == 4) {
                                 bos.write(headBuffer, 0, length);
                                 System.arraycopy(bos.toByteArray(), 0, headBuffer, 0, buffer_size);
                                 head = ByteUtil.bytesToInt(headBuffer, 0);
                                 length = dis.read(cmdBuffer, 0, buffer_size);
                                 bos.write(cmdBuffer, 0, length);
                                 System.arraycopy(bos.toByteArray(), 4, cmdBuffer, 0, buffer_size);
                                 int cmd = ByteUtil.hexStringToAlgorism(ByteUtil.str2HexStr(ByteUtil.byte2hex(cmdBuffer)));
                                 int heartNumber = ByteUtil.hexStringToAlgorism(ByteUtil.str2HexStr(SocketStatus.HEART));
                                 String discover = Integer.toHexString(0x0101);
                                 int discoverNumber = ByteUtil.hexStringToAlgorism(ByteUtil.str2HexStr(SocketStatus.DISCOVER));
                                 int giftNumber = ByteUtil.hexStringToAlgorism(ByteUtil.str2HexStr(SocketStatus.GIFT));
                                 if (cmd == heartNumber) {
                                     length = dis.read(stateBuffer, 0, buffer_size);
                                     bos.write(stateBuffer, 0, length);
                                     System.arraycopy(bos.toByteArray(), 8, stateBuffer, 0, buffer_size);
                                     switch (ByteUtil.bytesToInt(stateBuffer, 0)) {
                                         case SocketStatus.LOGIN_SUCCESS: {//登录成功
                                             Logger.e("登录成功");
                                             mLoginValue = "1";
                                             EventUtils.sendEvent(new Event<>(Constants.MSG_LOGIN_SUCCESS));
                                             break;
                                         }
                                       
                                         case SocketStatus.HEART_SUCCESS: {//心跳返回
                                             if (ByteUtil.bytesToInt(stateBuffer, 0) == 200
                                                     && Integer.toHexString(ByteUtil.bytesToInt(cmdBuffer, 0))
                                                     .equals(discover)) {
                                                 byte[] buffer = new byte[head];
                                                 length = dis.read(buffer, 0, head);
                                                 bos.write(buffer, 0, length);
                                                 Response.Response response = Response.
                                                         Response.parseFrom(buffer);
                                                 Logger.e(responseExplore.getNickname() + responseExplore.getAvatar());
                                                 //发送到activity中对数据进行处理
                                                 EventUtils.sendEvent(new Event<>(Constants.MSG_START_DISCOVER_RESULT,
                                                         responseExplore));
                                                 Logger.e(responseExplore + "=======response");
                                             } else {
                                                 Logger.e("心跳返回");
                                             }
                                             break;
                                         }
                                        
                                         default:
                                             break;
                                     }
                                 }
                             } else {
                                    //出错重连
                                 ClientSocket.getsInstance().shutDownConnection(mSocket);
                                 createConnection();
                             }
    
                         } else {
                             createConnection();
                         }
                     }
                 } catch (IOException ex) {
                     ex.printStackTrace();
                 }
                 try {
                     Thread.sleep(50);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         }
     });
     mReceiveThread.start();
    }
    
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        ClientSocket.getsInstance().shutDownConnection(mSocket);
        stopSelf();
        mHeartThread = null;
        mReceiveThread = null;
        mLoginThread = null;
        mSocketThread = null;
        mStopDiscoverThread = null;
        isHeart = false;
        isReceive = false;
     
    }
    
    
    
    
    /**
     * Binder
     */
    public class SocketBinder extends Binder {
    
     private SocketService mService;
     public OnServiceCallBack mCallBack;
    
     public SocketBinder(SocketService mService) {
         this.mService = mService;
     }
    
     /**
      * 发送方法
      *
      * @param object
      */
     public void sendMethod(Object object) {
         mService.sendMsg(object);
         mCallBack.onService(object);
     }
    
     /**
      * 设置回调
      *
      * @param callBack
      */
     public void setOnServiceCallBack(OnServiceCallBack callBack) {
         this.mCallBack = callBack;
       }
      }
    
    }
    Copy the code
  • The service creates the socket first, then connects to the socket, and then re-connects to the socket if the socket fails, such as a network exception. Then, open a receiving thread to continue receiving, each receiving is a 4 byte int value to determine whether it can proceed to the next step, if so, continue down. Read 4 bytes of __ packet header __ then 4 bytes of __ command __ read 4 bytes of __ status code __ finally read 4 bytes of __ packet body __, which contains the data we need to return. Also, at the beginning, a receiving thread was opened to receive data every 50 milliseconds, so that we could read not only the heartbeat packets but also the data we needed. Finally, all threads are stopped at the end of the server life cycle.

  • 4. Classes that send data

    public class ClientSocket { private DataOutputStream out = null; private DataInputStream getMessageStream; private static ClientSocket sInstance; Private ClientSocket() {} /** * @return */ public static ClientSocket getsInstance() {if (sInstance == null) { synchronized (ClientSocket.class) { if (sInstance == null) { sInstance = new ClientSocket(); } } } return sInstance; Public void sendLogin(Socket Socket, request.requestLogin RequestLogin, int code) { byte[] data = requestLogin.toByteArray(); byte[] head = ByteUtil.intToBytes(data.length); byte[] cmd = ByteUtil.intToBytes(code); byte[] bytes = addBytes(head, cmd, data); if (socket ! = null) { if (socket.isConnected()) { try { OutputStream os = socket.getOutputStream(); os.write(bytes); os.flush(); } catch (IOException e) { e.printStackTrace(); }}}} /** * Heartbeat ** @param code keyword (command) * @return */ public Boolean sendHeart(Socket Socket, int code) { boolean isSuccess; byte[] head = ByteUtil.intToBytes(0); byte[] cmd = ByteUtil.intToBytes(code); byte[] bytes = addBytes(head, cmd); if (socket.isConnected()) { try { out = new DataOutputStream(socket.getOutputStream()); out.write(bytes); out.flush(); isSuccess = true; } catch (IOException e) { e.printStackTrace(); isSuccess = false; } } else { isSuccess = false; } return isSuccess; Public void shutDownConnection(Socket Socket) {try {if (out! = null) { out.close(); } if (getMessageStream ! = null) { getMessageStream.close(); } if (socket ! = null) { socket.close(); } } catch (IOException e) { e.printStackTrace(); @param socket @return */ public DataInputStream getMessageStream(socket socket) {if (socket == null) { return null; } if (socket.isClosed()) { return null; } if (! socket.isConnected()) { return null; } try { getMessageStream = new DataInputStream(new BufferedInputStream( socket.getInputStream())); } catch (IOException e) { e.printStackTrace(); if (getMessageStream ! = null) { try { getMessageStream.close(); } catch (IOException e1) { e1.printStackTrace(); } } } return getMessageStream; }}Copy the code
  • Analysis: in this case, singleton mode is used to ensure the uniqueness of data and not repeated creation. It can be seen that the login sends the packet header, command and data length, while the heartbeat only sends the packet header and command, because the packet length is empty, so it does not need to send. Finally, the binary data is converted to 4 bytes for sending. In this way, proto Buffer has the advantage of being easily parsed by both clients and servers.

  • Binary conversion utility class

     public class ByteUtil {
    
     /**
      * 将2个byte数组进行拼接
      */
     public static byte[] addBytes(byte[] data1, byte[] data2) {
      byte[] data3 = new byte[data1.length + data2.length];
      System.arraycopy(data1, 0, data3, 0, data1.length);
      System.arraycopy(data2, 0, data3, data1.length, data2.length);
      return data3;
    }
    
     /**
      * 将3个byte数组进行拼接
      */
    public static byte[] addBytes(byte[] data1, byte[] data2, byte[] data3) {
      byte[] data4 = new byte[data1.length + data2.length + data3.length];
      System.arraycopy(data1, 0, data4, 0, data1.length);
      System.arraycopy(data2, 0, data4, data1.length, data2.length);
      System.arraycopy(data3, 0, data4, data1.length + data2.length, data3.length);
      return data4;
     }
    
     /**
      * int转byte{}
      */
     public static byte[] intToBytes(int value, ByteOrder mode) {
      byte[] src = new byte[4];
      if (mode == ByteOrder.LITTLE_ENDIAN) {
          src[3] = (byte) ((value >> 24) & 0xFF);
          src[2] = (byte) ((value >> 16) & 0xFF);
          src[1] = (byte) ((value >> 8) & 0xFF);
          src[0] = (byte) (value & 0xFF);
      } else {
          src[0] = (byte) ((value >> 24) & 0xFF);
          src[1] = (byte) ((value >> 16) & 0xFF);
          src[2] = (byte) ((value >> 8) & 0xFF);
          src[3] = (byte) (value & 0xFF);
      }
      return src;
     }
    
    
     /**
      * 16进制表示的字符串转换为字节数组
      *
      * @param s 16进制表示的字符串
      * @return byte[] 字节数组
      */
     public static byte[] hexStringToByteArray(String s) {
      int len = s.length();
      byte[] b = new byte[len / 2];
      for (int i = 0; i < len; i += 2) {
          // 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个字节
          b[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character
                  .digit(s.charAt(i + 1), 16));
      }
      return b;
     }
    
    
     /**
      * byte数组中取int数值,本方法适用于(低位在前,高位在后)的顺序,和和intToBytes()配套使用
      *
      * @param src    byte数组
      * @param offset 从数组的第offset位开始
      * @return int数值
      */
     public static int bytesToInt(byte[] src, int offset) {
      int value;
      value = (int) ((src[offset] & 0xFF)
              | ((src[offset + 1] & 0xFF) << 8)
              | ((src[offset + 2] & 0xFF) << 16)
              | ((src[offset + 3] & 0xFF) << 24));
      return value;
      }
    
     /**
      * byte数组中取int数值,本方法适用于(低位在后,高位在前)的顺序。和intToBytes2()配套使用
      */
    public static int bytesToInt2(byte[] src, int offset) {
      int value;
      value = (int) (((src[offset] & 0xFF) << 24)
              | ((src[offset + 1] & 0xFF) << 16)
              | ((src[offset + 2] & 0xFF) << 8)
              | (src[offset + 3] & 0xFF));
      return value;
     }
    
     /**
      * 将int数值转换为占四个字节的byte数组,本方法适用于(低位在前,高位在后)的顺序。 和 
       bytesToInt()配套使用
      *
      * @param value 要转换的int值
      * @return byte数组
      */
     public static byte[] intToBytes(int value) {
      byte[] src = new byte[4];
      src[3] = (byte) ((value >> 24) & 0xFF);
      src[2] = (byte) ((value >> 16) & 0xFF);
      src[1] = (byte) ((value >> 8) & 0xFF);
      src[0] = (byte) (value & 0xFF);
      return src;
     }
    
     /**
      * 将int数值转换为占四个字节的byte数组,本方法适用于(高位在前,低位在后)的顺序。  和 
       bytesToInt2()配套使用
      */
     public static byte[] intToBytes2(int value) {
      byte[] src = new byte[4];
      src[0] = (byte) ((value >> 24) & 0xFF);
      src[1] = (byte) ((value >> 16) & 0xFF);
      src[2] = (byte) ((value >> 8) & 0xFF);
      src[3] = (byte) (value & 0xFF);
      return src;
     }
    
     /**
      * 将字节转换为二进制字符串
      *
      * @param bytes 字节数组
      * @return 二进制字符串
      */
     public static String byteToBit(byte... bytes) {
      StringBuffer sb = new StringBuffer();
      int z, len;
      String str;
      for (int w = 0; w < bytes.length; w++) {
          z = bytes[w];
          z |= 256;
          str = Integer.toBinaryString(z);
          len = str.length();
          sb.append(str.substring(len - 8, len));
        }
        return sb.toString();
       }
    
        /**
         * 字节数组转为普通字符串(ASCII对应的字符)
         *
         * @param bytearray byte[]
         * @return String
         */
        public static String byte2String(byte[] bytearray) {
      String result = "";
      char temp;
    
      int length = bytearray.length;
      for (int i = 0; i < length; i++) {
          temp = (char) bytearray[i];
          result += temp;
      }
      return result;
     }
    
     /**
      * 二进制字符串转十进制
      *
      * @param binary 二进制字符串
      * @return 十进制数值
      */
    public static int binaryToAlgorism(String binary) {
      int max = binary.length();
      int result = 0;
      for (int i = max; i > 0; i--) {
          char c = binary.charAt(i - 1);
          int algorism = c - '0';
          result += Math.pow(2, max - i) * algorism;
      }
      return result;
     }
    
     /**
      * 字节数组转换为十六进制字符串
      *
      * @param b byte[] 需要转换的字节数组
      * @return String 十六进制字符串
      */
     public static String byte2hex(byte b[]) {
      if (b == null) {
          throw new IllegalArgumentException(
                  "Argument b ( byte array ) is null! ");
      }
      String hs = "";
      String stmp = "";
      for (int n = 0; n < b.length; n++) {
          stmp = Integer.toHexString(b[n] & 0xff);
          if (stmp.length() == 1) {
              hs = hs + "0" + stmp;
          } else {
              hs = hs + stmp;
          }
      }
      return hs.toUpperCase();
    }
    
     /**
      * 十六进制字符串转换十进制
      *
      * @param hex 十六进制字符串
      * @return 十进制数值
      */
    public static int hexStringToAlgorism(String hex) {
      hex = hex.toUpperCase();
      int max = hex.length();
      int result = 0;
      for (int i = max; i > 0; i--) {
          char c = hex.charAt(i - 1);
          int algorism = 0;
          if (c >= '0' && c <= '9') {
              algorism = c - '0';
          } else {
              algorism = c - 55;
          }
          result += Math.pow(16, max - i) * algorism;
      }
      return result;
     }
    
    /**
     * 字符串转换成十六进制字符串
     *
     * @param str 待转换的ASCII字符串
     * @return String 每个Byte之间空格分隔,如: [61 6C 6B]
     */
     public static String str2HexStr(String str) {
    
      char[] chars = "0123456789ABCDEF".toCharArray();
      StringBuilder sb = new StringBuilder("");
      byte[] bs = str.getBytes();
      int bit;
    
      for (int i = 0; i < bs.length; i++) {
          bit = (bs[i] & 0x0f0) >> 4;
          sb.append(chars[bit]);
          bit = bs[i] & 0x0f;
          sb.append(chars[bit]);
          sb.append(' ');
      }
      return sb.toString().trim();
     }
    
     /**
      * 16进制转换成字符串
      *
      * @param hexStr
      * @return
      */
     public static String hexStr2Str(String hexStr) {
      String str = "0123456789ABCDEF";
      char[] hexs = hexStr.toCharArray();
      byte[] bytes = new byte[hexStr.length() / 2];
      int n;
    
      for (int i = 0; i < bytes.length; i++) {
          n = str.indexOf(hexs[2 * i]) * 16;
          n += str.indexOf(hexs[2 * i + 1]);
          bytes[i] = (byte) (n & 0xff);
      }
      return new String(bytes);
     }
    
    /**
     * 重写了Inpustream 中的skip(long n) 方法,
     * 将数据流中起始的n 个字节跳过
     */
    public static long skipBytesFromStream(InputStream inputStream, long n) {
      long remaining = n;
      // SKIP_BUFFER_SIZE is used to determine the size of skipBuffer
      int SKIP_BUFFER_SIZE = 2048;
      // skipBuffer is initialized in skip(long), if needed.
      byte[] skipBuffer = null;
      int nr = 0;
      if (skipBuffer == null) {
          skipBuffer = new byte[SKIP_BUFFER_SIZE];
      }
      byte[] localSkipBuffer = skipBuffer;
      if (n <= 0) {
          return 0;
      }
      while (remaining > 0) {
          try {
              nr = inputStream.read(localSkipBuffer, 0,
                      (int) Math.min(SKIP_BUFFER_SIZE, remaining));
          } catch (IOException e) {
              e.printStackTrace();
          }
          if (nr < 0) {
              break;
          }
          remaining -= nr;
      }
      return n - remaining;
    }
    }
    Copy the code

The last

For the flexible use of socket and Proto Buffer, we should first thank our colleagues in the company. Without their ideas, it would be difficult to flexibly use socket and Proto Buffer. Secondly, I would like to thank the former company leaders, as well as all my good friends who gave me valuable advice. I also want to thank myself for being able to calm down, persevere and overcome the combination of Proto buffer and socket writing.

Thank you

Protocol Buffer (Language specification)

Why does the Protocol Buffer serialize so well?