Implementing a long connection between Android and the server based on a custom protocol based on Java Socket (Part 2)

Implementing a long connection between Android and the server based on a custom protocol based on Java Socket (Part 2)

Before reading this article, you need to have a basic understanding of sockets and custom protocols. You can first check the previous article "Custom Protocol Based on Java Socket to Implement Long Connection between Android and Server (I)" to learn the relevant basic knowledge points.

[[178018]]

1. Protocol Definition

In the previous article, we had a brief understanding of socket programming and custom protocols. This article will go deeper on this basis to realize the long connection between Android and the server. The protocol is defined as follows:

  • Data Protocol (Data)
    • Length (length, 32 bits)
    • Version number (version, 8 bits, the first 3 bits are reserved, the last 5 bits are used to indicate the actual version number)
    • Data type (type, 8 bits, 0 indicates data)
    • Service type (pattion, 8 bits, 0 means push, others are undetermined)
    • Data format (dtype, 8bit, 0 means json, others are undetermined)
    • Message id (msgId, 32 bits)
    • Text data (data)
  • Data ack protocol (DataAck)
    • Length (length, 32 bits)
    • Version number (version, 8 bits, the first 3 bits are reserved, the last 5 bits are used to indicate the actual version number)
    • Data type (type, 8 bits, 1 indicates data ack)
    • ack message id (ackMsgId, 32 bits)
    • Reserved information (unused)
  • Heartbeat protocol (ping)
    • Length (length, 32 bits)
    • Version number (version, 8 bits, the first 3 bits are reserved, the last 5 bits are used to indicate the actual version number)
    • Data type (type, 8 bits, 2 for heartbeat)
    • Heartbeat ID (pingId, 32 bits, odd numbers are reported by the client, i.e. 1, 3, 5..., and even numbers are sent by the server, i.e. 0, 2, 4...)
    • Reserved information (unused)
  • Heartbeat ack protocol (pingAck)
    • Length (length, 32 bits)
    • Version number (version, 8 bits, the first 3 bits are reserved, the last 5 bits are used to indicate the actual version number)
    • Data type (type, 8 bits, 3 means heartbeat ack)
    • ack heartbeat id (pingId, 32 bits, the client reports an odd number, i.e. 1, 3, 5..., and the server sends an even number, i.e. 0, 2, 4...)
    • Reserved information (unused)

2. Protocol Implementation

From the above protocol definitions, we can see that the four protocols have three common elements: length, version number, and data type. Then we can first abstract a basic protocol as follows:

1. Basic Protocol

  1. import android.util.Log;
  2.  
  3. import com.shandiangou.sdgprotocol.lib.Config;
  4. import com.shandiangou.sdgprotocol.lib.ProtocolException;
  5. import com.shandiangou.sdgprotocol.lib.SocketUtil;
  6.  
  7. import java.io.ByteArrayOutputStream;
  8.  
  9. /**
  10. * Created by meishan on 16/12/1.
  11. * <p>
  12. * Protocol type: 0 for data, 1 for dataAck, 2 for ping, 3 for pingAck
  13. */
  14. public abstract class BasicProtocol {
  15.  
  16. // The length is in bytes.
  17. public   static final int LENGTH_LEN = 4; //Record the length of the entire data length value
  18. protected static final int VER_LEN = 1; //Version length of the protocol (the first 3 bits are reserved, and the last 5 bits are the version number)
  19. protected static final int TYPE_LEN = 1; //Data type length of the protocol
  20.  
  21. private int reserved = 0; //reserved information
  22. private int version = Config.VERSION; //version number
  23.  
  24. /**
  25. * Get the length of the entire data
  26. * Unit: byte
  27. *
  28. * @return  
  29. */
  30. protected int getLength() {
  31. return LENGTH_LEN + VER_LEN + TYPE_LEN;
  32. }
  33.  
  34. public   int getReserved() {
  35. return reserved;
  36. }
  37.  
  38. public void setReserved( int reserved) {
  39. this.reserved = reserved;
  40. }
  41.  
  42. public   int getVersion() {
  43. return version;
  44. }
  45.  
  46. public void setVersion( int version) {
  47. this.version = version;
  48. }
  49.  
  50. /**
  51. * Get the protocol type, implemented by the subclass
  52. *
  53. * @return  
  54. */
  55. public abstract int getProtocolType();
  56.      
  57. /**
  58. * Calculate the byte[] value of the complete version number from the reserved value and the version number
  59. *
  60. * @return  
  61. */
  62. private int getVer(byte r, byte v, int vLen) {
  63. int num = 0;
  64. int rLen = 8 - vLen;
  65. for ( int i = 0; i < rLen; i++) {
  66. num += (((r >> (rLen - 1 - i)) & 0x1) << (7 - i));
  67. }
  68. return num + v;
  69. }
  70.  
  71. /**
  72. * Splice the sent data. Here, the protocol version, protocol type and data length are spliced. The specific content subclass will splice them again.
  73. * Splice in order
  74. *
  75. * @return  
  76. */
  77. public byte[] genContentData() {
  78. byte[] length = SocketUtil.int2ByteArrays(getLength());
  79. byte reserved = (byte) getReserved();
  80. byte version = (byte) getVersion();
  81. byte[] ver = {(byte) getVer(reserved, version, 5)};
  82. byte[] type = {(byte) getProtocolType()};
  83.  
  84. ByteArrayOutputStream baos = new ByteArrayOutputStream(LENGTH_LEN + VER_LEN + TYPE_LEN);
  85. baos.write(length, 0, LENGTH_LEN);
  86. baos.write(ver, 0, VER_LEN);
  87. baos.write(type, 0, TYPE_LEN);
  88. return baos.toByteArray();
  89. }
  90.  
  91. /**
  92. * Parse the entire data length
  93. *
  94. * @param data
  95. * @return  
  96. */
  97. protected int parseLength(byte[] data) {
  98. return SocketUtil.byteArrayToInt(data, 0, LENGTH_LEN);
  99. }
  100.  
  101. /**
  102. * Parse the reserved position
  103. *
  104. * @param data
  105. * @return  
  106. */
  107. protected int parseReserved(byte[] data) {
  108. byte r = data[LENGTH_LEN]; //The first 4 bytes (0, 1, 2, 3) are the int value of the data length, which together with the version number form a byte
  109. return (r >> 5) & 0xFF;
  110. }
  111.  
  112. /**
  113. * Parse the version number
  114. *
  115. * @param data
  116. * @return  
  117. */
  118. protected int parseVersion(byte[] data) {
  119. byte v = data[LENGTH_LEN]; //Combined with the reserved bit to form a byte
  120. return ((v << 3) & 0xFF) >> 3;
  121. }
  122.  
  123. /**
  124. * Parse the protocol type
  125. *
  126. * @param data
  127. * @return  
  128. */
  129. public   static   int parseType(byte[] data) {
  130. byte t = data[LENGTH_LEN + VER_LEN]; //The first 4 bytes (0, 1, 2, 3) are the int value of the data length, and ver occupies one byte
  131. return t & 0xFF;
  132. }
  133.  
  134. /**
  135. * Parse the received data. Here, the protocol version, protocol type and data length are parsed. The specific content subclass will parse it again.
  136. *
  137. * @param data
  138. * @return  
  139. * @throws ProtocolException If the protocol version is inconsistent, an exception is thrown
  140. */
  141. public   int parseContentData(byte[] data) throws ProtocolException {
  142. int reserved = parseReserved(data);
  143. int version = parseVersion(data);
  144. int protocolType = parseType(data);
  145. if (version != getVersion()) {
  146. throw new ProtocolException( "input version is error: " + version);
  147. }
  148. return LENGTH_LEN + VER_LEN + TYPE_LEN;
  149. }
  150.  
  151. @Override
  152. public String toString() {
  153. return   "Version: " + getVersion() + ", Type: " + getProtocolType();
  154. }
  155. }

The Config class and SocketUtil class involved above are as follows:

  1. /**
  2. * Created by meishan on 16/12/2.
  3. */
  4. public class Config {
  5.  
  6. public   static final int VERSION = 1; //Protocol version number
  7. public   static final String ADDRESS = "10.17.64.237" ; //Server address
  8. public   static final int PORT = 9013; //Server port number
  9.      
  10. }
  1. import java.io.BufferedInputStream;
  2. import java.io.BufferedOutputStream;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.io.OutputStream;
  6. import java.nio.ByteBuffer;
  7. import java.util.HashMap;
  8. import java.util.Map;
  9.  
  10. /**
  11. * Created by meishan on 16/12/1.
  12. */
  13. public class SocketUtil {
  14.  
  15. private static Map< Integer , String> msgImp = new HashMap<>();
  16.  
  17. static {
  18. msgImp.put(DataProtocol.PROTOCOL_TYPE, "com.shandiangou.sdgprotocol.lib.protocol.DataProtocol" ); //0
  19. msgImp.put(DataAckProtocol.PROTOCOL_TYPE, "com.shandiangou.sdgprotocol.lib.protocol.DataAckProtocol" ); //1
  20. msgImp.put(PingProtocol.PROTOCOL_TYPE, "com.shandiangou.sdgprotocol.lib.protocol.PingProtocol" ); //2
  21. msgImp.put(PingAckProtocol.PROTOCOL_TYPE, "com.shandiangou.sdgprotocol.lib.protocol.PingAckProtocol" ); //3
  22. }
  23.  
  24. /**
  25. * Parse data content
  26. *
  27. * @param data
  28. * @return  
  29. */
  30. public   static BasicProtocol parseContentMsg(byte[] data) {
  31. int protocolType = BasicProtocol.parseType(data);
  32. String className = msgImp.get(protocolType);
  33. BasicProtocol basicProtocol;
  34. try {
  35. basicProtocol = (BasicProtocol) Class.forName(className).newInstance();
  36. basicProtocol.parseContentData(data);
  37. } catch (Exception e) {
  38. basicProtocol = null ;
  39. e.printStackTrace();
  40. }
  41. return basicProtocol;
  42. }
  43.  
  44. /**
  45. * Read data
  46. *
  47. * @param inputStream
  48. * @return  
  49. * @throws SocketExceptions
  50. */
  51. public   static BasicProtocol readFromStream(InputStream inputStream) {
  52. BasicProtocol protocol;
  53. BufferedInputStream bis;
  54.          
  55. //The header stores the length of the entire data, represented by 4 bytes. In the following write2Stream method, the header is written first
  56. byte[] header = new byte[BasicProtocol.LENGTH_LEN];
  57.  
  58. try {
  59. bis = new BufferedInputStream(inputStream);
  60.  
  61. int   temp ;
  62. int len ​​= 0;
  63. while (len < header.length) {
  64. temp = bis.read (header, len, header.length - len) ;
  65. if ( temp > 0) {
  66. len += temp ;
  67. } else if ( temp == -1) {
  68. bis.close ();
  69. return   null ;
  70. }
  71. }
  72.  
  73. len = 0;
  74. int length = byteArrayToInt(header); //data length
  75. byte[] content = new byte[length];
  76. while (len < length) {
  77. temp = bis.read (content, len, length - len) ;
  78.  
  79. if ( temp > 0) {
  80. len += temp ;
  81. }
  82. }
  83.  
  84. protocol = parseContentMsg(content);
  85. } catch (IOException e) {
  86. e.printStackTrace();
  87. return   null ;
  88. }
  89.  
  90. return protocol;
  91. }
  92.  
  93. /**
  94. * Write data
  95. *
  96. * @param protocol
  97. * @param outputStream
  98. */
  99. public   static void write2Stream(BasicProtocol protocol, OutputStream outputStream) {
  100. BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
  101. byte[] buffData = protocol.genContentData();
  102. byte[] header = int2ByteArrays(buffData.length);
  103. try {
  104. bufferedOutputStream.write(header);
  105. bufferedOutputStream.write(buffData);
  106. bufferedOutputStream.flush();
  107. } catch (IOException e) {
  108. e.printStackTrace();
  109. }
  110. }
  111.  
  112. /**
  113. * Close the input stream
  114. *
  115. * @param is  
  116. */
  117. public   static void closeInputStream(InputStream is ) {
  118. try {
  119. if ( is != null ) {
  120. is . close ();
  121. }
  122. } catch (IOException e) {
  123. e.printStackTrace();
  124. }
  125. }
  126.  
  127. /**
  128. * Close the output stream
  129. *
  130. * @param os
  131. */
  132. public   static void closeOutputStream(OutputStream os) {
  133. try {
  134. if (os != null ) {
  135. os.close ();
  136. }
  137. } catch (IOException e) {
  138. e.printStackTrace();
  139. }
  140. }
  141.  
  142. public   static byte[] int2ByteArrays( int i) {
  143. byte[] result = new byte[4];
  144. result[0] = (byte) ((i >> 24) & 0xFF);
  145. result[1] = (byte) ((i >> 16) & 0xFF);
  146. result[2] = (byte) ((i >> 8) & 0xFF);
  147. result[3] = (byte) (i & 0xFF);
  148. return result;
  149. }
  150.  
  151. public   static   int byteArrayToInt(byte[] b) {
  152. int intValue = 0;
  153. for ( int i = 0; i < b.length; i++) {
  154. intValue += (b[i] & 0xFF) << (8 * (3 - i)); // int occupies 4 bytes (0, 1, 2, 3)
  155. }
  156. return intValue;
  157. }
  158.  
  159. public   static   int byteArrayToInt(byte[] b, int byteOffset, int byteCount) {
  160. int intValue = 0;
  161. for ( int i = byteOffset; i < (byteOffset + byteCount); i++) {
  162. intValue += (b[i] & 0xFF) << (8 * (3 - (i - byteOffset)));
  163. }
  164. return intValue;
  165. }
  166.  
  167. public   static   int bytes2Int(byte[] b, int byteOffset) {
  168. ByteBuffer byteBuffer = ByteBuffer.allocate( Integer . SIZE / Byte. SIZE );
  169. byteBuffer.put(b, byteOffset, 4); //occupies 4 bytes
  170. byteBuffer.flip();
  171. return byteBuffer.getInt();
  172. }
  173. }

Next we implement the specific protocol.

2. DataProtocol

  1. import android.util.Log;
  2.  
  3. import com.shandiangou.sdgprotocol.lib.ProtocolException;
  4. import com.shandiangou.sdgprotocol.lib.SocketUtil;
  5.  
  6. import java.io.ByteArrayOutputStream;
  7. import java.io.Serializable ;
  8. import java.io.UnsupportedEncodingException;
  9.  
  10. /**
  11. * Created by meishan on 16/12/1.
  12. */
  13. public class DataProtocol extends BasicProtocol implements Serializable {
  14.  
  15. public   static final int PROTOCOL_TYPE = 0;
  16.  
  17. private static final int PATTION_LEN = 1;
  18. private static final int DTYPE_LEN = 1;
  19. private static final int MSGID_LEN = 4;
  20.  
  21. private int pattion;
  22. private int dtype;
  23. private int msgId;
  24.  
  25. private String data;
  26.  
  27. @Override
  28. public   int getLength() {
  29. return super.getLength() + PATTION_LEN + DTYPE_LEN + MSGID_LEN + data.getBytes().length;
  30. }
  31.  
  32. @Override
  33. public   int getProtocolType() {
  34. return PROTOCOL_TYPE;
  35. }
  36.  
  37. public   int getPattion() {
  38. return pattion;
  39. }
  40.  
  41. public void setPattion( int pattion) {
  42. this.pattion = pattion;
  43. }
  44.  
  45. public   int getDtype() {
  46. return dtype;
  47. }
  48.  
  49. public void setDtype( int dtype) {
  50. this.dtype = dtype;
  51. }
  52.  
  53. public void setMsgId( int msgId) {
  54. this.msgId = msgId;
  55. }
  56.  
  57. public   int getMsgId() {
  58. return msgId;
  59. }
  60.  
  61. public String getData() {
  62. return data;
  63. }
  64.  
  65. public void setData(String data) {
  66. this.data = data;
  67. }
  68.  
  69. /**
  70. * Splice and send data
  71. *
  72. * @return  
  73. */
  74. @Override
  75. public byte[] genContentData() {
  76. byte[] base = super.genContentData();
  77. byte[] pattion = {(byte) this.pattion};
  78. byte[] dtype = {(byte) this.dtype};
  79. byte[] msgid = SocketUtil.int2ByteArrays(this.msgId);
  80. byte[] data = this.data.getBytes();
  81.  
  82. ByteArrayOutputStream baos = new ByteArrayOutputStream(getLength());
  83. baos.write(base, 0, base.length); //protocol version + data type + data length + message id
  84. baos.write(pattion, 0, PATTION_LEN); //Business type
  85. baos.write(dtype, 0, DTYPE_LEN); //Business data format
  86. baos.write(msgid, 0, MSGID_LEN); //Message id
  87. baos.write(data, 0, data.length); //Business data
  88. return baos.toByteArray();
  89. }
  90.  
  91. /**
  92. * Parse the received data in order
  93. *
  94. * @param data
  95. * @return  
  96. * @throws ProtocolException
  97. */
  98. @Override
  99. public   int parseContentData(byte[] data) throws ProtocolException {
  100. int pos = super.parseContentData(data);
  101.  
  102. //Analysis of pattion
  103. pattion = data[pos] & 0xFF;
  104. pos += PATTION_LEN;
  105.  
  106. //parse dtype
  107. dtype = data[pos] & 0xFF;
  108. pos += DTYPE_LEN;
  109.  
  110. //Parse msgId
  111. msgId = SocketUtil.byteArrayToInt(data, pos, MSGID_LEN);
  112. pos += MSGID_LEN;
  113.  
  114. //Parse data
  115. try {
  116. this.data = new String(data, pos, data.length - pos, "utf-8" );
  117. } catch (UnsupportedEncodingException e) {
  118. e.printStackTrace();
  119. }
  120.  
  121. return pos;
  122. }
  123.  
  124. @Override
  125. public String toString() {
  126. return   "data: " + data;
  127. }
  128. }

3. DataAckProtocol

  1. import com.shandiangou.sdgprotocol.lib.ProtocolException;
  2. import com.shandiangou.sdgprotocol.lib.SocketUtil;
  3.  
  4. import java.io.ByteArrayOutputStream;
  5. import java.io.UnsupportedEncodingException;
  6.  
  7. /**
  8. * Created by meishan on 16/12/1.
  9. */
  10. public class DataAckProtocol extends BasicProtocol {
  11.  
  12. public   static final int PROTOCOL_TYPE = 1;
  13.  
  14. private static final int ACKMSGID_LEN = 4;
  15.  
  16. private int ackMsgId;
  17.  
  18. private String unused;
  19.  
  20. @Override
  21. public   int getLength() {
  22. return super.getLength() + ACKMSGID_LEN + unused.getBytes().length;
  23. }
  24.  
  25. @Override
  26. public   int getProtocolType() {
  27. return PROTOCOL_TYPE;
  28. }
  29.  
  30. public   int getAckMsgId() {
  31. return ackMsgId;
  32. }
  33.  
  34. public void setAckMsgId( int ackMsgId) {
  35. this.ackMsgId = ackMsgId;
  36. }
  37.  
  38. public String getUnused() {
  39. return unused;
  40. }
  41.  
  42. public void setUnused(String unused) {
  43. this.unused = unused;
  44. }
  45.  
  46. /**
  47. * Splice and send data
  48. *
  49. * @return  
  50. */
  51. @Override
  52. public byte[] genContentData() {
  53. byte[] base = super.genContentData();
  54. byte[] ackMsgId = SocketUtil.int2ByteArrays(this.ackMsgId);
  55. byte[] unused = this.unused.getBytes();
  56.  
  57. ByteArrayOutputStream baos = new ByteArrayOutputStream(getLength());
  58. baos.write(base, 0, base.length); //protocol version + data type + data length + message id
  59. baos.write(ackMsgId, 0, ACKMSGID_LEN); //Message id
  60. baos.write(unused, 0, unused.length); //unused
  61. return baos.toByteArray();
  62. }
  63.  
  64. @Override
  65. public   int parseContentData(byte[] data) throws ProtocolException {
  66. int pos = super.parseContentData(data);
  67.  
  68. //Parse ackMsgId
  69. ackMsgId = SocketUtil.byteArrayToInt(data, pos, ACKMSGID_LEN);
  70. pos += ACKMSGID_LEN;
  71.  
  72. //Parse unused
  73. try {
  74. unused = new String(data, pos, data.length - pos, "utf-8" );
  75. } catch (UnsupportedEncodingException e) {
  76. e.printStackTrace();
  77. }
  78.  
  79. return pos;
  80. }
  81.  
  82. }

4. PingProtocol

  1. import com.shandiangou.sdgprotocol.lib.ProtocolException;
  2. import com.shandiangou.sdgprotocol.lib.SocketUtil;
  3.  
  4. import java.io.ByteArrayOutputStream;
  5. import java.io.UnsupportedEncodingException;
  6.  
  7. /**
  8. * Created by meishan on 16/12/1.
  9. */
  10. public class PingProtocol extends BasicProtocol {
  11.  
  12. public   static final int PROTOCOL_TYPE = 2;
  13.  
  14. private static final int PINGID_LEN = 4;
  15.  
  16. private int pingId;
  17.  
  18. private String unused;
  19.  
  20. @Override
  21. public   int getLength() {
  22. return super.getLength() + PINGID_LEN + unused.getBytes().length;
  23. }
  24.  
  25. @Override
  26. public   int getProtocolType() {
  27. return PROTOCOL_TYPE;
  28. }
  29.  
  30. public   int getPingId() {
  31. return pingId;
  32. }
  33.  
  34. public void setPingId( int pingId) {
  35. this.pingId = pingId;
  36. }
  37.  
  38. public String getUnused() {
  39. return unused;
  40. }
  41.  
  42. public void setUnused(String unused) {
  43. this.unused = unused;
  44. }
  45.  
  46. /**
  47. * Splice and send data
  48. *
  49. * @return  
  50. */
  51. @Override
  52. public byte[] genContentData() {
  53. byte[] base = super.genContentData();
  54. byte[] pingId = SocketUtil.int2ByteArrays(this.pingId);
  55. byte[] unused = this.unused.getBytes();
  56.  
  57. ByteArrayOutputStream baos = new ByteArrayOutputStream(getLength());
  58. baos.write(base, 0, base.length); //protocol version + data type + data length + message id
  59. baos.write(pingId, 0, PINGID_LEN); //Message id
  60. baos.write(unused, 0, unused.length); //unused
  61. return baos.toByteArray();
  62. }
  63.  
  64. @Override
  65. public   int parseContentData(byte[] data) throws ProtocolException {
  66. int pos = super.parseContentData(data);
  67.  
  68. //Parse pingId
  69. pingId = SocketUtil.byteArrayToInt(data, pos, PINGID_LEN);
  70. pos += PINGID_LEN;
  71.  
  72. try {
  73. unused = new String(data, pos, data.length - pos, "utf-8" );
  74. } catch (UnsupportedEncodingException e) {
  75. e.printStackTrace();
  76. }
  77.  
  78. return pos;
  79. }
  80.  
  81. }

5. PingAckProtocol

  1. import com.shandiangou.sdgprotocol.lib.ProtocolException;
  2. import com.shandiangou.sdgprotocol.lib.SocketUtil;
  3.  
  4. import java.io.ByteArrayOutputStream;
  5. import java.io.UnsupportedEncodingException;
  6.  
  7. /**
  8. * Created by meishan on 16/12/1.
  9. */
  10. public class PingAckProtocol extends BasicProtocol {
  11.  
  12. public   static final int PROTOCOL_TYPE = 3;
  13.  
  14. private static final int ACKPINGID_LEN = 4;
  15.  
  16. private int ackPingId;
  17.  
  18. private String unused;
  19.  
  20. @Override
  21. public   int getLength() {
  22. return super.getLength() + ACKPINGID_LEN + unused.getBytes().length;
  23. }
  24.  
  25. @Override
  26. public   int getProtocolType() {
  27. return PROTOCOL_TYPE;
  28. }
  29.  
  30. public   int getAckPingId() {
  31. return ackPingId;
  32. }
  33.  
  34. public void setAckPingId( int ackPingId) {
  35. this.ackPingId = ackPingId;
  36. }
  37.  
  38. public String getUnused() {
  39. return unused;
  40. }
  41.  
  42. public void setUnused(String unused) {
  43. this.unused = unused;
  44. }
  45.  
  46. /**
  47. * Splice and send data
  48. *
  49. * @return  
  50. */
  51. @Override
  52. public byte[] genContentData() {
  53. byte[] base = super.genContentData();
  54. byte[] ackPingId = SocketUtil.int2ByteArrays(this.ackPingId);
  55. byte[] unused = this.unused.getBytes();
  56.  
  57. ByteArrayOutputStream baos = new ByteArrayOutputStream(getLength());
  58. baos.write(base, 0, base.length); //protocol version + data type + data length + message id
  59. baos.write(ackPingId, 0, ACKPINGID_LEN); //message id
  60. baos.write(unused, 0, unused.length); //unused
  61. return baos.toByteArray();
  62. }
  63.  
  64. @Override
  65. public   int parseContentData(byte[] data) throws ProtocolException {
  66. int pos = super.parseContentData(data);
  67.  
  68. //Parse ackPingId
  69. ackPingId = SocketUtil.byteArrayToInt(data, pos, ACKPINGID_LEN);
  70. pos += ACKPINGID_LEN;
  71.  
  72. //Parse unused
  73. try {
  74. unused = new String(data, pos, data.length - pos, "utf-8" );
  75. } catch (UnsupportedEncodingException e) {
  76. e.printStackTrace();
  77. }
  78.  
  79. return pos;
  80. }
  81.  
  82. }

3. Task Scheduling

The above four protocols have been implemented. Next, we will use them to implement the communication between the app and the server. Here we use one thread to implement data sending, receiving and heartbeat respectively, as follows:

1. Client

  1. import android.os.Handler;
  2. import android.os.Looper;
  3. import android.os.Message;
  4. import android.util.Log;
  5.  
  6. import com.shandiangou.sdgprotocol.lib.protocol.BasicProtocol;
  7. import com.shandiangou.sdgprotocol.lib.protocol.DataProtocol;
  8. import com.shandiangou.sdgprotocol.lib.protocol.PingProtocol;
  9.  
  10. import java.io.IOException;
  11. import java.io.InputStream;
  12. import java.io.OutputStream;
  13. import java.net.ConnectException;
  14. import java.net.Socket;
  15. import java.util.concurrent.ConcurrentLinkedQueue;
  16.  
  17. import javax.net.SocketFactory;
  18.  
  19. /**
  20. * Write data in an infinite loop, wait when there is no data, and notify when there is new message
  21. * <p>
  22. * Created by meishan on 16/12/1.
  23. */
  24. public class ClientRequestTask implements Runnable {
  25.  
  26. private static final int SUCCESS = 100;
  27. private static final int FAILED = -1;
  28.  
  29. private boolean isLongConnection = true ;
  30. private Handler mHandler;
  31. private SendTask mSendTask;
  32. private ReciveTask mReciveTask;
  33. private HeartBeatTask mHeartBeatTask;
  34. private Socket mSocket;
  35.  
  36. private boolean isSocketAvailable;
  37. private boolean closeSendTask;
  38.  
  39. protected volatile ConcurrentLinkedQueue<BasicProtocol> dataQueue = new ConcurrentLinkedQueue<>();
  40.  
  41. public ClientRequestTask(RequestCallBack requestCallBacks) {
  42. mHandler = new MyHandler(requestCallBacks);
  43. }
  44.  
  45. @Override
  46. public void run() {
  47. try {
  48. try {
  49. mSocket = SocketFactory.getDefault().createSocket(Config.ADDRESS, Config.PORT);
  50. // mSocket.setSoTimeout(10);
  51. } catch (ConnectException e) {
  52. failedMessage(-1, "Server connection abnormality, please check the network" );
  53. return ;
  54. }
  55.  
  56. isSocketAvailable = true ;
  57.  
  58. //Start receiving thread
  59. mReciveTask = new ReciveTask();
  60. mReciveTask.inputStream = mSocket.getInputStream();
  61. mReciveTask.start();
  62.  
  63. //Start sending thread
  64. mSendTask = new SendTask();
  65. mSendTask.outputStream = mSocket.getOutputStream();
  66. mSendTask.start();
  67.  
  68. //Start the heartbeat thread
  69. if (isLongConnection) {
  70. mHeartBeatTask = new HeartBeatTask();
  71. mHeartBeatTask.outputStream = mSocket.getOutputStream();
  72. mHeartBeatTask.start();
  73. }
  74. } catch (IOException e) {
  75. failedMessage(-1, "Network exception, please try again later" );
  76. e.printStackTrace();
  77. }
  78. }
  79.  
  80. public void addRequest(DataProtocol data) {
  81. dataQueue.add (data);
  82. toNotifyAll(dataQueue); //If there is new data to be sent, wake up the sending thread
  83. }
  84.  
  85. public synchronized void stop() {
  86.  
  87. //Close the receiving thread
  88. closeReciveTask();
  89.  
  90. //Close the sending thread
  91. closeSendTask = true ;
  92. toNotifyAll(dataQueue);
  93.  
  94. //Close the heartbeat thread
  95. closeHeartBeatTask();
  96.  
  97. //Close the socket
  98. closeSocket();
  99.  
  100. // Clear data
  101. clearData();
  102.  
  103. failedMessage(-1, "disconnected" );
  104. }
  105.  
  106. /**
  107. * Close the receiving thread
  108. */
  109. private void closeReciveTask() {
  110. if (mReciveTask != null ) {
  111. mReciveTask.interrupt();
  112. mReciveTask.isCancle = true ;
  113. if (mReciveTask.inputStream != null ) {
  114. try {
  115. if (isSocketAvailable && !mSocket.isClosed() && mSocket.isConnected()) {
  116. mSocket.shutdownInput(); //To solve the java.net.SocketException problem, you need to shut downInput first
  117. }
  118. } catch (IOException e) {
  119. e.printStackTrace();
  120. }
  121. SocketUtil.closeInputStream(mReciveTask.inputStream);
  122. mReciveTask.inputStream = null ;
  123. }
  124. mReciveTask = null ;
  125. }
  126. }
  127.  
  128. /**
  129. * Close the sending thread
  130. */
  131. private void closeSendTask() {
  132. if (mSendTask != null ) {
  133. mSendTask.isCancle = true ;
  134. mSendTask.interrupt();
  135. if (mSendTask.outputStream != null ) {
  136. synchronized (mSendTask.outputStream) {//Prevent stopping when writing data, stop after writing
  137. SocketUtil.closeOutputStream(mSendTask.outputStream);
  138. mSendTask.outputStream = null ;
  139. }
  140. }
  141. mSendTask = null ;
  142. }
  143. }
  144.  
  145. /**
  146. * Close the heartbeat thread
  147. */
  148. private void closeHeartBeatTask() {
  149. if (mHeartBeatTask != null ) {
  150. mHeartBeatTask.isCancle = true ;
  151. if (mHeartBeatTask.outputStream != null ) {
  152. SocketUtil.closeOutputStream(mHeartBeatTask.outputStream);
  153. mHeartBeatTask.outputStream = null ;
  154. }
  155. mHeartBeatTask = null ;
  156. }
  157. }
  158.  
  159. /**
  160. * Close the socket
  161. */
  162. private void closeSocket() {
  163. if (mSocket != null ) {
  164. try {
  165. mSocket.close ();
  166. isSocketAvailable = false ;
  167. } catch (IOException e) {
  168. e.printStackTrace();
  169. }
  170. }
  171. }
  172.  
  173. /**
  174. * Clear data
  175. */
  176. private void clearData() {
  177. dataQueue.clear();
  178. isLongConnection = false ;
  179. }
  180.  
  181. private void toWait(Object o) {
  182. synchronized (o) {
  183. try {
  184. o.wait();
  185. } catch (InterruptedException e) {
  186. e.printStackTrace();
  187. }
  188. }
  189. }
  190.  
  191. /**
  192. * After notify() is called, the object lock is not released immediately, but after the corresponding synchronized(){} statement block is executed, the lock is automatically released.
  193. *
  194. * @param o
  195. */
  196. protected void toNotifyAll(Object o) {
  197. synchronized (o) {
  198. o.notifyAll();
  199. }
  200. }
  201.  
  202. private void failedMessage( int code, String msg) {
  203. Message message = mHandler.obtainMessage(FAILED);
  204. message.what = FAILED;
  205. message.arg1 = code;
  206. message.obj = msg;
  207. mHandler.sendMessage(message);
  208. }
  209.  
  210. private void successMessage(BasicProtocol protocol) {
  211. Message message = mHandler.obtainMessage(SUCCESS);
  212. message.what = SUCCESS;
  213. message.obj = protocol;
  214. mHandler.sendMessage(message);
  215. }
  216.  
  217. private boolean isConnected() {
  218. if (mSocket.isClosed() || !mSocket.isConnected()) {
  219. ClientRequestTask.this.stop();
  220. return   false ;
  221. }
  222. return   true ;
  223. }
  224.  
  225. /**
  226. * Server returns processing, the main thread runs
  227. */
  228. public class MyHandler extends Handler {
  229.  
  230. private RequestCallBack mRequestCallBack;
  231.  
  232. public MyHandler(RequestCallBack callBack) {
  233. super(Looper.getMainLooper());
  234. this.mRequestCallBack = callBack;
  235. }
  236.  
  237. @Override
  238. public void handleMessage(Message msg) {
  239. super.handleMessage(msg);
  240. switch (msg.what) {
  241. case SUCCESS:
  242. mRequestCallBack.onSuccess((BasicProtocol) msg.obj);
  243. break;
  244. case FAILED:
  245. mRequestCallBack.onFailed(msg.arg1, (String) msg.obj);
  246. break;
  247. default :
  248. break;
  249. }
  250. }
  251. }
  252.  
  253. /**
  254. * Data receiving thread
  255. */
  256. public class ReciveTask extends Thread {
  257.  
  258. private boolean isCancle = false ;
  259. private InputStream inputStream;
  260.  
  261. @Override
  262. public void run() {
  263. while (!isCancle) {
  264. if (!isConnected()) {
  265. break;
  266. }
  267.  
  268. if (inputStream != null ) {
  269. BasicProtocol reciverData = SocketUtil.readFromStream(inputStream);
  270. if (reciverData != null ) {
  271. if (reciverData.getProtocolType() == 1 || reciverData.getProtocolType() == 3) {
  272. successMessage(reciverData);
  273. }
  274. } else {
  275. break;
  276. }
  277. }
  278. }
  279.  
  280. SocketUtil.closeInputStream(inputStream); // Exit the input stream when the loop ends
  281. }
  282. }
  283.  
  284. /**
  285. * Data sending thread
  286. * Make the thread wait when no data is sent
  287. */
  288. public class SendTask extends Thread {
  289.  
  290. private boolean isCancle = false ;
  291. private OutputStream outputStream;
  292.  
  293. @Override
  294. public void run() {
  295. while (!isCancle) {
  296. if (!isConnected()) {
  297. break;
  298. }
  299.  
  300. BasicProtocol dataContent = dataQueue.poll();
  301. if (dataContent == null ) {
  302. toWait(dataQueue); //Wait if no data is sent
  303. if (closeSendTask) {
  304. closeSendTask(); //After notify() is called, the object lock is not released immediately, so the sending thread is interrupted here
  305. }
  306. } else if (outputStream != null ) {
  307. synchronized (outputStream) {
  308. SocketUtil.write2Stream(dataContent, outputStream);
  309. }
  310. }
  311. }
  312.  
  313. SocketUtil.closeOutputStream(outputStream); //Exit the output stream when the loop ends
  314. }
  315. }
  316.  
  317. /**
  318. * Heartbeat implementation, frequency 5 seconds
  319. * Created by meishan on 16/12/1.
  320. */
  321. public class HeartBeatTask extends Thread {
  322.  
  323. private static final int REPEATTIME = 5000;
  324. private boolean isCancle = false ;
  325. private OutputStream outputStream;
  326. private int pingId;
  327.  
  328. @Override
  329. public void run() {
  330. pingId = 1;
  331. while (!isCancle) {
  332. if (!isConnected()) {
  333. break;
  334. }
  335.  
  336. try {
  337. mSocket.sendUrgentData(0xFF);
  338. } catch (IOException e) {
  339. isSocketAvailable = false ;
  340. ClientRequestTask.this.stop();
  341. break;
  342. }
  343.  
  344. if (outputStream != null ) {
  345. PingProtocol pingProtocol = new PingProtocol();
  346. pingProtocol.setPingId(pingId);
  347. pingProtocol.setUnused( "ping..." );
  348. SocketUtil.write2Stream(pingProtocol, outputStream);
  349. pingId = pingId + 2;
  350. }
  351.  
  352. try {
  353. Thread.sleep(REPEATTIME);
  354. } catch (InterruptedException e) {
  355. e.printStackTrace();
  356. }
  357. }
  358.  
  359. SocketUtil.closeOutputStream(outputStream);
  360. }
  361. }
  362. }

The RequestCallBack interface involved is as follows:

  1. /**
  2. * Created by meishan on 16/12/1.
  3. */
  4. public interface RequestCallBack {
  5.  
  6. void onSuccess(BasicProtocol msg);
  7.  
  8. void onFailed( int errorCode, String msg);
  9. }

2. Server

  1. import java.io.DataInputStream;
  2. import java.io.DataOutputStream;
  3. import java.net.Socket;
  4. import java.util.Iterator;
  5. import java.util.concurrent.ConcurrentHashMap;
  6. import java.util.concurrent.ConcurrentLinkedQueue;
  7.  
  8. /**
  9. * Created by meishan on 16/12/1.
  10. */
  11. public class ServerResponseTask implements Runnable {
  12.  
  13. private ReciveTask reciveTask;
  14. private SendTask sendTask;
  15. private Socket socket;
  16. private ResponseCallback tBack;
  17.  
  18. private volatile ConcurrentLinkedQueue<BasicProtocol> dataQueue = new ConcurrentLinkedQueue<>();
  19. private static ConcurrentHashMap<String, Socket> onLineClient = new ConcurrentHashMap<>();
  20.  
  21. private String userIP;
  22.  
  23. public String getUserIP() {
  24. return userIP;
  25. }
  26.  
  27. public ServerResponseTask(Socket socket, ResponseCallback tBack) {
  28. this.socket = socket;
  29. this.tBack = tBack;
  30. this.userIP = socket.getInetAddress().getHostAddress();
  31. System.out.println ( "User IP address: " + userIP);
  32. }
  33.  
  34. @Override
  35. public void run() {
  36. try {
  37. //Start receiving thread
  38. reciveTask = new ReciveTask();
  39. reciveTask.inputStream = new DataInputStream(socket.getInputStream());
  40. reciveTask.start();
  41.  
  42. //Start sending thread
  43. sendTask = new SendTask();
  44. sendTask.outputStream = new DataOutputStream(socket.getOutputStream());
  45. sendTask.start();
  46. } catch (Exception e) {
  47. e.printStackTrace();
  48. }
  49. }
  50.  
  51. public void stop() {
  52. if (reciveTask != null ) {
  53. reciveTask.isCancle = true ;
  54. reciveTask.interrupt();
  55. if (reciveTask.inputStream != null ) {
  56. SocketUtil.closeInputStream(reciveTask.inputStream);
  57. reciveTask.inputStream = null ;
  58. }
  59. reciveTask = null ;
  60. }
  61.  
  62. if (sendTask != null ) {
  63. sendTask.isCancle = true ;
  64. sendTask.interrupt();
  65. if (sendTask.outputStream != null ) {
  66. synchronized (sendTask.outputStream) {//Prevent stopping when writing data, stop after writing
  67. sendTask.outputStream = null ;
  68. }
  69. }
  70. sendTask = null ;
  71. }
  72. }
  73.  
  74. public void addMessage(BasicProtocol data) {
  75. if (!isConnected()) {
  76. return ;
  77. }
  78.  
  79. dataQueue.offer(data);
  80. toNotifyAll(dataQueue); //If there is new data to be sent, wake up the sending thread
  81. }
  82.  
  83. public Socket getConnectdClient(String clientID) {
  84. return onLineClient.get(clientID);
  85. }
  86.  
  87. /**
  88. * Print the connected clients
  89. */
  90. public   static void printAllClient() {
  91. if (onLineClient == null ) {
  92. return ;
  93. }
  94. Iterator<String> inter = onLineClient.keySet().iterator();
  95. while (inter.hasNext()) {
  96. System. out .println( "client:" + inter. next ());
  97. }
  98. }
  99.  
  100. public void toWaitAll(Object o) {
  101. synchronized (o) {
  102. try {
  103. o.wait();
  104. } catch (InterruptedException e) {
  105. e.printStackTrace();
  106. }
  107. }
  108. }
  109.  
  110. public void toNotifyAll(Object obj) {
  111. synchronized (obj) {
  112. obj.notifyAll();
  113. }
  114. }
  115.  
  116. private boolean isConnected() {
  117. if (socket.isClosed() || !socket.isConnected()) {
  118. onLineClient.remove(userIP);
  119. ServerResponseTask.this.stop();
  120. System. out .println( "socket closed..." );
  121. return   false ;
  122. }
  123. return   true ;
  124. }
  125.  
  126. public class ReciveTask extends Thread {
  127.  
  128. private DataInputStream inputStream;
  129. private boolean isCancle;
  130.  
  131. @Override
  132. public void run() {
  133. while (!isCancle) {
  134. if (!isConnected()) {
  135. isCancle = true ;
  136. break;
  137. }
  138.  
  139. BasicProtocol clientData = SocketUtil.readFromStream(inputStream);
  140.  
  141. if (clientData != null ) {
  142. if (clientData.getProtocolType() == 0) {
  143. System. out .println( "dtype: " + ((DataProtocol) clientData).getDtype() + ", pattion: " + ((DataProtocol) clientData).getPattion() + ", msgId: " + ((DataProtocol) clientData).getMsgId() + ", data: " + ((DataProtocol) clientData).getData());
  144.  
  145. DataAckProtocol dataAck = new DataAckProtocol();
  146. dataAck.setUnused( "Received message: " + ((DataProtocol) clientData).getData());
  147. dataQueue.offer(dataAck);
  148. toNotifyAll(dataQueue); //Wake up the sending thread
  149.  
  150. tBack.targetIsOnline(userIP);
  151. } else if (clientData.getProtocolType() == 2) {
  152. System. out .println( "pingId: " + ((PingProtocol) clientData).getPingId());
  153.  
  154. PingAckProtocol pingAck = new PingAckProtocol();
  155. pingAck.setUnused( "heartbeat received" );
  156. dataQueue.offer(pingAck);
  157. toNotifyAll(dataQueue); //Wake up the sending thread
  158.  
  159. tBack.targetIsOnline(userIP);
  160. }
  161. } else {
  162. System. out .println( "client is offline..." );
  163. break;
  164. }
  165. }
  166.  
  167. SocketUtil.closeInputStream(inputStream);
  168. }
  169. }
  170.  
  171. public class SendTask extends Thread {
  172.  
  173. private DataOutputStream outputStream;
  174. private boolean isCancle;
  175.  
  176. @Override
  177. public void run() {
  178. while (!isCancle) {
  179. if (!isConnected()) {
  180. isCancle = true ;
  181. break;
  182. }
  183.  
  184. BasicProtocol procotol = dataQueue.poll();
  185. if (procotol == null ) {
  186. toWaitAll(dataQueue);
  187. } else if (outputStream != null ) {
  188. synchronized (outputStream) {
  189. SocketUtil.write2Stream(procotol, outputStream);
  190. }
  191. }
  192. }
  193.  
  194. SocketUtil.closeOutputStream(outputStream);
  195. }
  196. }

The ResponseCallback interface involved is as follows:

  1. /**
  2. * Created by meishan on 16/12/1.
  3. */
  4. public interface ResponseCallback {
  5.  
  6. void targetIsOffline(DataProtocol reciveMsg);
  7.  
  8. void targetIsOnline(String clientIp);
  9. }

The above code handles exceptions in several situations. For example, after the connection is established, the server stops running. At this time, the client's input stream is still blocked. How to ensure that the client does not throw an exception? These processes can be combined with the SocketUtil class.

4. Call Encapsulation

1. Client

import com.shandiangou.sdgprotocol.lib.protocol.DataProtocol;

  1. import com.shandiangou.sdgprotocol.lib.protocol.DataProtocol;
  2.  
  3. /**
  4. * Created by meishan on 16/12/1.
  5. */
  6. public class ConnectionClient {
  7.  
  8. private boolean isClosed;
  9.  
  10. private ClientRequestTask mClientRequestTask;
  11.  
  12. public ConnectionClient(RequestCallBack requestCallBack) {
  13. mClientRequestTask = new ClientRequestTask(requestCallBack);
  14. new Thread(mClientRequestTask).start();
  15. }
  16.  
  17. public void addNewRequest(DataProtocol data) {
  18. if (mClientRequestTask != null && !isClosed)
  19. mClientRequestTask.addRequest(data);
  20. }
  21.  
  22. public void closeConnect() {
  23. isClosed = true ;
  24. mClientRequestTask.stop();
  25. }
  26. }

2. Server

  1. import com.shandiangou.sdgprotocol.lib.protocol.DataProtocol;
  2.  
  3. import java.io.IOException;
  4. import java.net.ServerSocket;
  5. import java.net.Socket;
  6. import java.util.concurrent.ExecutorService;
  7. import java.util.concurrent.Executors;
  8.  
  9. /**
  10. * Created by meishan on 16/12/1.
  11. */
  12. public class ConnectionServer {
  13.  
  14. private static boolean isStart = true ;
  15. private static ServerResponseTask serverResponseTask;
  16.  
  17. public ConnectionServer() {
  18.  
  19. }
  20.  
  21. public   static void main(String[] args) {
  22.  
  23. ServerSocket serverSocket = null ;
  24. ExecutorService executorService = Executors.newCachedThreadPool();
  25. try {
  26. serverSocket = new ServerSocket(Config.PORT);
  27. while (isStart) {
  28. Socket socket = serverSocket.accept();
  29. serverResponseTask = new ServerResponseTask(socket,
  30. new ResponseCallback() {
  31.  
  32. @Override
  33. public void targetIsOffline(DataProtocol reciveMsg) {// The other party is not online
  34. if (reciveMsg != null ) {
  35. System. out .println(reciveMsg.getData());
  36. }
  37. }
  38.  
  39. @Override
  40. public void targetIsOnline(String clientIp) {
  41. System. out .println(clientIp + " is onLine" );
  42. System. out .println( "-----------------------------------------" );
  43. }
  44. });
  45.  
  46. if (socket.isConnected()) {
  47. executorService.execute (serverResponseTask);
  48. }
  49. }
  50.  
  51. serverSocket.close () ;
  52.  
  53. } catch (IOException e) {
  54. e.printStackTrace();
  55. finally
  56. if (serverSocket != null ) {
  57. try {
  58. isStart = false ;
  59. serverSocket.close () ;
  60. if (serverSocket != null )
  61. serverResponseTask.stop();
  62. } catch (IOException e) {
  63. e.printStackTrace();
  64. }
  65. }
  66. }
  67. }
  68. }

Summarize

The key to implementing a custom protocol lies in the assembly and parsing of the protocol. The key code has been given above. If you need to view the complete code and demo, you can download the source code.

Note: Run the main function of the server demo first, then check the IP address of the local machine, and then modify the IP address in Config.java in the client (android) code. Of course, make sure that the Android phone and the server are in the same local area, and then open the client.

<<:  Android dynamic loading: DL framework

>>:  Serialized storage of objects: Serializable and Parceable

Recommend

Copywriting skills: 16 common UI copywriting mistakes

There are many techniques in copywriting design, ...

Sad! The first person to study giant pandas in China has passed away...

Evening of February 16 Professor Hu Jinchu, known...

Look! This is a monkey selfie! But this macaque is critically endangered

This is probably one of the most famous selfies i...

Dapeng Education-Sketch Improvement Course

Dapeng Education-Sketch Improvement Course Resour...

Summary of iOS interview in February 2018

In mid-to-late February this year, I changed jobs...

In the first year of VLOG marketing, how can brands catch this express train?

Introduction: As a new trend, public opinion has ...

Product operation: overall operation framework ideas for content marketing!

Content marketing is divided into three aspects 1...

Three major monetization models for online education!

In terms of monetization models, especially for t...

Turritopsis dohrnii: The Fairy Dancing Between Life and Death

All tangible and existing things begin their jour...

Mid-Autumn Festival brand marketing promotion routine!

Mid-Autumn Festival, one of China's four majo...

Not all big blueberries are nutritious. Do you know which variety to look for?

Audit expert: Shi Jun Doctor of Botany, well-know...