Android(安卓)P解决Socket通信Tcp粘包问题
TCP协议是一个面向流的协议,所以他会出现粘包的问题。
TCP和UDP的调查请参考:
《Android中关于TCP socket通信数据大小,内存缓冲区和数据可靠性的一点调查》
一、TCP服务端和客户端代码实现
客户端代码实现
连接服务器的代码:
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(); }}
二、TCP粘包问题重现
粘包只有在快速发送多个包的时候才会出现。
循环发送20次数据:
for(int i=0; i<20; i++){ String title; if(i%2 == 0){ title = "aaa"; }else{ title = "bbb"; } //发送字符串的数据 sendMusicData(0x01,title.getBytes());}
发送数据的代码:
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中:
socket client: send buffer=A66A030000000A01476F696E6720486F6D65socket client: send buffer=A66A0300000008014E657720536F756Csocket client: send buffer=A66A030000000A01476F696E6720486F6D65socket client: send buffer=A66A0300000008014E657720536F756Csocket client: send buffer=A66A030000000A01476F696E6720486F6D65socket client: send buffer=A66A0300000008014E657720536F756Csocket client: send buffer=A66A030000000A01476F696E6720486F6D65socket client: send buffer=A66A0300000008014E657720536F756Csocket client: send buffer=A66A030000000A01476F696E6720486F6D65socket client: send buffer=A66A0300000008014E657720536F756Csocket client: send buffer=A66A030000000A01476F696E6720486F6D65socket server: recevie buffer=A66A0300000008014E657720536F756CA66A030000000A01476F696E6720486F6D65A66A0300000008014E657720536F756CA66A030000000A01476F696E6720486F6D65socket client: send buffer=A66A0300000008014E657720536F756Csocket client: send buffer=A66A030000000A01476F696E6720486F6D65socket server: recevie buffer=A66A0300000008014E657720536F756CA66A030000000A01476F696E6720486F6D65A66A0300000008014E657720536F756CA66A030000000A01476F696E6720486F6D65socket client: send buffer=A66A0300000008014E657720536F756Csocket client: send buffer=A66A030000000A01476F696E6720486F6D65socket client: send buffer=A66A0300000008014E657720536F756Csocket server: recevie buffer=A66A030000000A01476F696E6720486F6D65A66A0300000008014E657720536F756CA66A030000000A01476F696E6720486F6D65A66A0300000008014E657720536F756CA66A030000000A01476F696E6720486F6D65A66A0300000008014E657720536F756CA66A030000000A01476F696E6720486F6D65socket client: send buffer=A66A030000000A01476F696E6720486F6D65
三、TCP粘包问题原因分析
理想状态:服务端先接受一个package1,在接受一个package2,…,一次一个包一个包的接收,则这样无问题。
粘包的三种情况:
1、先读取到package1的部分数据,然后读取package1的余下数据和package2的全部数据;
2、先读取到package1的全部数据和package2的部分数据,然后读到package2的余下数据;
3、同时一次读取到package1和package2的全部数据。
很明显,我们这个Demo中,一次读取到了两包或者多包数据,属于这里面的第三种情况。
粘包原因:
1、客户端write()时发送的数据大于socket缓冲区的数据,导致需要多次write()才能把数据写入到缓冲去,这样导致read()数据的时候,一次read()的数据不完整。需要read()多次才能将数据读完;
2、发送端write()数据的速度太快,而接收端read()数据的太慢,导致接收方来不及接收每一包数据,造成了一次读取到多包数据的情况。我们这里也是属于这种情况。
四、TCP粘包问题解决方法
1、增加一个自己定义的传输协议,即增加一个包头,将帧头信息,长度信息包含在里面。包头长度固定,接收端根据包头中的长度去解析一个完整的包数据。
在我们的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,远远比我发送的测试数据要大。所以不会产生第一种和第二种粘包的情况。
这种增加固定的协议,解析帧头和长度,应该是最为灵活的方法。
更多相关文章
- android ListView 中getview学习总结
- android 访问 OData
- 20172321 2017-2018-2 《程序设计与数据结构》第11周学习总结
- push研究――Apache Mina探索初步
- 【转】Android深入探究笔记之三 -- Intent (隐式意图和显示意图)
- Android(安卓)之使用ContentProvider(内容提供者)共享数据
- Android数据保存之文件保存
- Android内部存储和外部存储
- Android(安卓)SQLite数据库增删改查操作