Socket粘包问题

前言:最近作安卓端项目须要用到socket和PC端互发指令,可是期间遇到了这么一个现象,就是有时候PC端消息指令发的过快,致使安卓端接收了多条粘在一块儿的指令,这就是粘包现象。java

解析:socket在发送数据的时候会对数据进行粘包和分包处理,这致使咱们须要额外处理,虽然看上去没有必要(麻烦、懒得处理),可是这是socket作的优化。android

什么是粘包?
就是在极短期内发送了几回不多的数据量,正常是接收端应该也一样接收了好几回,可是发送端却把这几回的数据合成一块儿,一次发了过来,几个数据“粘”在了一块儿。git

什么是拆包?
当发送端一次性发送了大量的数据时,socket将数据拆分,按批次发送,数据被“拆”开发送了。github

解决方案:web

一、添加标记
能够在每条指令后面加特殊字符,好比“%#*”,这样多个特殊字符集合,而后接收端按照规定的切分就好了。
二、包头+包体
由于接受的时候是字节,因此在每条消息以前加一个定长的字节数组表明包体的长度。
网上搜了一位大神的,感受挺好的,亲测可用传送门数组

解决实例:(包头+包体)
首先推荐一个很好用的socket框架,内部封装的很好,能够监听链接状态,我是基于这个基础上修改的。传送门框架

package com.hite.communicate.socket;

import android.os.Message;
import android.util.Log;

import com.hite.communicate.CustomMessageConstant;
import com.hite.communicate.utils.DataUtils;
import com.hite.communicate.utils.EventBusUtil;
import com.koushikdutta.async.AsyncServer;
import com.koushikdutta.async.AsyncServerSocket;
import com.koushikdutta.async.AsyncSocket;
import com.koushikdutta.async.ByteBufferList;
import com.koushikdutta.async.DataEmitter;
import com.koushikdutta.async.Util;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.DataCallback;
import com.koushikdutta.async.callback.ListenCallback;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class Server {

    private InetAddress         host;
    private int                 port;
    private AsyncServerSocket   asyncServerSocket;
    private AsyncSocket         asyncsocket;
    private final int           HEADER_LENGTH = 4;
    private byte[]              mData;

    public Server(String host, int port) {
        try {
            this.host = InetAddress.getByName(host);
            this.port = port;
            setup();
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }

    private void setup() {
        asyncServerSocket = AsyncServer.getDefault().listen(host, port, new ListenCallback() {

            @Override
            public void onAccepted(final AsyncSocket socket) {
                if(asyncsocket==null) {
                    asyncsocket = socket;
                    handleAccept(socket);
                }
            }

            @Override
            public void onListening(AsyncServerSocket socket) {

            }

            @Override
            public void onCompleted(Exception ex) {
            }
        });
    }

    public void clear() {
        mData=null;
        if(asyncsocket!=null) {
            asyncsocket.end();
            asyncsocket.close();
            asyncsocket=null;
        }
        if(asyncServerSocket!=null) {
            asyncServerSocket.stop();
        }
    }

    public void sendToPcMessage(String message) {
        if(asyncsocket!=null) {
            Util.writeAll(asyncsocket, DataUtils.getSendMsg(message.getBytes()), new CompletedCallback() {
                @Override
                public void onCompleted(Exception ex) {
                }
            });
        }
    }

    private void handleAccept(final AsyncSocket socket) {
        socket.setDataCallback(new DataCallback() {
            @Override
            public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
                byte[] data = bb.getAllByteArray();
                if(mData!=null) {
                    receiveMsg(DataUtils.byteMerger(mData,data));
                    mData=null;
                } else {
                    receiveMsg(data);
                }
            }
        });

        socket.setClosedCallback(new CompletedCallback() {
            @Override
            public void onCompleted(Exception ex) {
                asyncsocket=null;
                mData=null;
            }
        });

        socket.setEndCallback(new CompletedCallback() {
            @Override
            public void onCompleted(Exception ex) {

            }
        });
    }

    private void receiveMsg(byte[] data) {

        if(data.length>HEADER_LENGTH) {

            byte[] size = new byte[HEADER_LENGTH];
            System.arraycopy(data,0,size,0,HEADER_LENGTH);

            int bodySize = DataUtils.byteArrayToInt(size);

            Log.e("threadMode",bodySize+"receiveMsg");
            int totalLength = bodySize+HEADER_LENGTH;
            //不够一个包
            if(data.length<totalLength) {
                mData=data;
                return;
            //多于一个包
            } else if(data.length>totalLength) {
                DataUtils.dispatchMsg(data,bodySize);

                byte[] otherData = new byte[data.length-totalLength];

                System.arraycopy(data,totalLength,otherData,0,data.length-totalLength);

                receiveMsg(otherData);

            //正好一个包
            } else {
                DataUtils.dispatchMsg(data,bodySize);
            }
        } else {
            mData = data;
        }
    }
}
package com.hite.communicate.utils;

import android.os.Message;

import com.hite.communicate.CustomMessageConstant;

import java.io.UnsupportedEncodingException;

public class DataUtils {

    //包头+包体
    public static byte[] getSendMsg(byte[] src) {
        byte[] head = intToByteArray(src.length);
        return byteMerger(head,src);
    }

    //字节数组转int
    public static int byteArrayToInt(byte[] b) {
        return   b[3] & 0xFF |
                (b[2] & 0xFF) << 8 |
                (b[1] & 0xFF) << 16 |
                (b[0] & 0xFF) << 24;
    }

    //int转字节数组
    public static byte[] intToByteArray(int a) {
        return new byte[] {
                (byte) ((a >> 24) & 0xFF),
                (byte) ((a >> 16) & 0xFF),
                (byte) ((a >> 8) & 0xFF),
                (byte) (a & 0xFF)
        };
    }

    //数组合并
    public static byte[] byteMerger(byte[] byte_1, byte[] byte_2){
        byte[] byte_3 = new byte[byte_1.length+byte_2.length];
        System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);
        System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);
        return byte_3;
    }

    //拆消息体
    public static void dispatchMsg(byte[] data,int bodySize) {
        byte[] msgByte = new byte[bodySize];
        System.arraycopy(data,4,msgByte,0,bodySize);


        try {
            Message message = new Message();
            message.what= CustomMessageConstant.PC_MESSAGE;
            message.obj = new String(msgByte,"UTF-8");
            EventBusUtil.sendMessage(message);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

    }
}