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,远远比我发送的测试数据要大。所以不会产生第一种和第二种粘包的情况。

这种增加固定的协议,解析帧头和长度,应该是最为灵活的方法。

更多相关文章

  1. android ListView 中getview学习总结
  2. android 访问 OData
  3. 20172321 2017-2018-2 《程序设计与数据结构》第11周学习总结
  4. push研究――Apache Mina探索初步
  5. 【转】Android深入探究笔记之三 -- Intent (隐式意图和显示意图)
  6. Android(安卓)之使用ContentProvider(内容提供者)共享数据
  7. Android数据保存之文件保存
  8. Android内部存储和外部存储
  9. Android(安卓)SQLite数据库增删改查操作

随机推荐

  1. android菜单三级树实现
  2. Transparent Activity
  3. Android(安卓)下实现带有图标和Checkbox
  4. Android(安卓)去除String中的空格等
  5. android获取本机的IP地址和mac物理地址
  6. Android(安卓)打印堆栈
  7. Android(安卓)实现再按一次后退键退出应
  8. asynchttpclient post方法使用
  9. android 多点触摸 实现图片缩放 Image Zo
  10. How to set up a link betwteen a real A