TCP协议是一个面向流的协议,因此他会出现粘包的问题。javascript
TCP和UDP的调查请参考:
《Android中关于TCP socket通讯数据大小,内存缓冲区和数据可靠性的一点调查》java
客户端代码实现
链接服务器的代码:web
protected void connectServerWithTCPSocket() { boolean bRun = true; try { // 建立一个Socket对象,并指定服务端的IP及端口号 // 本地回路ip:127.0.0.0 mSocket = new Socket("127.0.0.1", 9897); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
服务端代码实现
在服务端实现对数据的接收:数组
public void createTcpServerSocket() { // 声明一个ServerSocket对象 ServerSocket serverSocket = null; boolean bRun = true; try { // 建立一个ServerSocket对象,并让这个Socket在8919端口监听 Log.i(TAG,"new ServerSocket"); serverSocket = new ServerSocket(9897); Log.i(TAG,"new ServerSocket,serverSocket=" + serverSocket); // 调用ServerSocket的accept()方法,接受客户端所发送的请求, // 若是客户端没有发送数据,那么该线程就停滞不继续 Socket socket = serverSocket.accept(); Log.i("sunxiaolin ServerSocket","serverSocket receive data"); //文本传输 // 从Socket当中获得InputStream对象 InputStream inputStream = socket.getInputStream(); byte buffer[] = new byte[1024]; int temp = 0; int length = 0; // 从InputStream当中读取客户端所发送的数据,阻塞状态 // 从缓冲区读出客户端发送的数据 while ((temp = inputStream.read(buffer)) != -1) { //System.out.println(new String(buffer, 0, temp)); length += temp; Log.i("tcp socket server","recevie buffer=" + printHexBinary(buffer)); } } catch (IOException e) { e.printStackTrace(); } }
粘包只有在快速发送多个包的时候才会出现。服务器
循环发送20次数据:app
for(int i=0; i<20; i++){ String title; if(i%2 == 0){ title = "aaa"; }else{ title = "bbb"; } //发送字符串的数据 sendMusicData(0x01,title.getBytes()); }
发送数据的代码:socket
public static void sendMusicData(int frameId,byte[] data) { if( mSocket != null){ //size为长度 int size = size = data.length; byte buffer[] = new byte[8 + size]; buffer[0] = (byte)0xA6; buffer[1] = 0x6A; buffer[2] = 0x03; buffer[3] = (byte)((size & 0xff000000) >> 24); buffer[4] = (byte)((size & 0x00ff0000) >> 16); buffer[5] = (byte)((size & 0x0000ff00) >> 8); buffer[6] = (byte)((size & 0x000000ff)); buffer[7] = (byte)frameId; System.arraycopy(data, 0, buffer, 8, size); Log.i("tcp socket client","send buffer=" + printHexBinary(buffer)); try { //发送一段buffer,对buffer包进行了特定协议的封包 DataOutputStream outputStream = new DataOutputStream(mSocket.getOutputStream()); outputStream.write(buffer); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } }else{ } } //打印字符数组的代码 private static final char[] hexCode = "0123456789ABCDEF".toCharArray(); public static String printHexBinary(byte[] data) { StringBuilder r = new StringBuilder(data.length * 2); for (byte b : data) { r.append(hexCode[(b >> 4) & 0xF]); r.append(hexCode[(b & 0xF)]); } return r.toString(); }
运行程序,根据打印发现,出现了粘包的问题,即把两个包读到了一个buffer中:tcp
socket client: send buffer=A66A030000000A01476F696E6720486F6D65 socket client: send buffer=A66A0300000008014E657720536F756C socket client: send buffer=A66A030000000A01476F696E6720486F6D65 socket client: send buffer=A66A0300000008014E657720536F756C socket client: send buffer=A66A030000000A01476F696E6720486F6D65 socket client: send buffer=A66A0300000008014E657720536F756C socket client: send buffer=A66A030000000A01476F696E6720486F6D65 socket client: send buffer=A66A0300000008014E657720536F756C socket client: send buffer=A66A030000000A01476F696E6720486F6D65 socket client: send buffer=A66A0300000008014E657720536F756C socket client: send buffer=A66A030000000A01476F696E6720486F6D65 socket server: recevie buffer=A66A0300000008014E657720536F756CA66A030000000A01476F696E6720486F6D65A66A0300000008014E657720536F756CA66A030000000A01476F696E6720486F6D65 socket client: send buffer=A66A0300000008014E657720536F756C socket client: send buffer=A66A030000000A01476F696E6720486F6D65 socket server: recevie buffer=A66A0300000008014E657720536F756CA66A030000000A01476F696E6720486F6D65A66A0300000008014E657720536F756CA66A030000000A01476F696E6720486F6D65 socket client: send buffer=A66A0300000008014E657720536F756C socket client: send buffer=A66A030000000A01476F696E6720486F6D65 socket client: send buffer=A66A0300000008014E657720536F756C socket server: recevie buffer=A66A030000000A01476F696E6720486F6D65A66A0300000008014E657720536F756CA66A030000000A01476F696E6720486F6D65A66A0300000008014E657720536F756CA66A030000000A01476F696E6720486F6D65A66A0300000008014E657720536F756CA66A030000000A01476F696E6720486F6D65 socket client: send buffer=A66A030000000A01476F696E6720486F6D65
理想状态:服务端先接受一个package1,在接受一个package2,…,一次一个包一个包的接收,则这样无问题。svg
粘包的三种状况:
一、先读取到package1的部分数据,而后读取package1的余下数据和package2的所有数据;
二、先读取到package1的所有数据和package2的部分数据,而后读到package2的余下数据;
三、同时一次读取到package1和package2的所有数据。测试
很明显,咱们这个Demo中,一次读取到了两包或者多包数据,属于这里面的第三种状况。
粘包缘由:
一、客户端write()时发送的数据大于socket缓冲区的数据,致使须要屡次write()才能把数据写入到缓冲去,这样致使read()数据的时候,一次read()的数据不完整。须要read()屡次才能将数据读完;
二、发送端write()数据的速度太快,而接收端read()数据的太慢,致使接收方来不及接收每一包数据,形成了一次读取到多包数据的状况。咱们这里也是属于这种状况。
一、增长一个本身定义的传输协议,即增长一个包头,将帧头信息,长度信息包含在里面。包头长度固定,接收端根据包头中的长度去解析一个完整的包数据。
在咱们的demo中,定好的协议为:
byte buffer[] = new byte[8 + size]; buffer[0] = (byte)0xA6; buffer[1] = 0x6A; buffer[2] = 0x03; buffer[3] = (byte)((size & 0xff000000) >> 24); buffer[4] = (byte)((size & 0x00ff0000) >> 16); buffer[5] = (byte)((size & 0x0000ff00) >> 8); buffer[6] = (byte)((size & 0x000000ff)); buffer[7] = (byte)frameId; System.arraycopy(data, 0, buffer, 8, size);
咱们的帧头固定为8个字节。其中buffer[3]-buffer[6]为数据的长度,因此根据这个调整一下接收端数据解析思路。接收端在服务端,修改服务端解析数据代码以下:
//文本传输 // 从Socket当中获得InputStream对象 InputStream inputStream = socket.getInputStream(); byte buffer[] = new byte[1024]; int frameSize = 0; byte[] mRecvBuffer = {0}; int mActualReadSize = 0; int readSize = 0; // 从InputStream当中读取客户端所发送的数据,阻塞状态 while ((readSize = inputStream.read(buffer)) != -1) { Log.i("TcpSocketServer","read totalSize=" + readSize ); Log.i("TcpSocketServer","read buffer=" + printHexBinary(buffer) ); mActualReadSize = 0; if( readSize > 8 ){ frameSize = ( (buffer[3] & 0xff) << 24 | (buffer[4] & 0xff) << 16 | (buffer[5] & 0xff) << 8 | (buffer[6] & 0xff) ); } while( readSize >= frameSize + 8 ){ mRecvBuffer = new byte[frameSize + 8]; System.arraycopy(buffer, mActualReadSize, mRecvBuffer, 0, frameSize + 8); mActualReadSize += frameSize + 8; Log.i("TcpSocketServer","read mRecvBuffer=" + printHexBinary(mRecvBuffer) ); int surplusLength = readSize - (frameSize + 8); readSize -= (frameSize + 8); if( surplusLength >= 8 ){ int head = ((buffer[mActualReadSize] & 0xff) << 8) | buffer[mActualReadSize + 1]; if( head == 0xA66A ){ frameSize = 0; frameSize = ( (buffer[mActualReadSize + 3] & 0xff) << 24 | (buffer[ mActualReadSize + 4] & 0xff) << 16 | (buffer[ mActualReadSize + 5] & 0xff) << 8 | (buffer[ mActualReadSize + 6] & 0xff) ); }else{ break; } } } }
打印以下:
能够看到mRecvBuffer能够读出了单个的包。
固然这里只解决了粘包的第三种状况,这种状况byte buffer[] = new byte[1024];,读出的缓冲区尽可能要大些,这里设为1024,远远比我发送的测试数据要大。因此不会产生第一种和第二种粘包的状况。
这种增长固定的协议,解析帧头和长度,应该是最为灵活的方法。