Netty(三):网络传输中的拆包、粘包问题

来源:互联网 发布:多因子选股 知乎 编辑:程序博客网 时间:2024/06/08 12:49

问题来源:

 

    在Netty中对于TCP传输的默认数据大小为1024字节,当数据包不超过1024时,会一次接收完毕,当超过1024时,首先会进行拆分,即分成几次传输,等到下次连接时将会改变一次发包的数据大小为2048字节,然后下次增长为3072字节,然后下次增长为4096字节;然而,再往下增长,则增长了2048字节变为6144字节,然后变为8192字节(8KB),当数据再大时,接收到的数据包也不再改变。此后,只要发送的数据包大小不超过8192字节,便不再分包。

    当一次连接的数据被分成几次进行传输时就带来一个问题:channelPipeline会把数据储存起来,然后每一次收到服务端发回来的数据时,将会触发一次channelRead方法,然后就会对收到的数据进行解析(因为此刻认为数据已经传输完毕,然而实际并没有),那么因为是拆分的数据所以就会导致解析失败!


 解决方案:


    在初始化bootstrap(启动器)时,要进行handler的注册,handler是以责任链的设计模式设计的,即channel读取时按照头handler——尾handler的流程,channel写入时按照尾handler——头handler的流程。所以我们必须在触发channelRead方法前先对数据进行处理,所以提出以下解决方案:在处理数据的handler之前注册一个decode的handler来保证接受到的数据的完整性。

    这里的decode方法继承自ByteToMessageDecoder类。因为项目开发基于腾讯的Tars框架,从Tars框架源码(https://github.com/Tencent/Tars)中我们可以看到:

public static IoBuffer encodeResponse(TarsServantResponse response, String charsetName) throws ProtocolException {    ……    ……    ByteBuffer buffer = jos.getByteBuffer();    int datalen = buffer.position();    buffer.position(0);    buffer.putInt(datalen);    buffer.position(datalen);    return IoBuffer.wrap(jos.toByteArray());}

    在encodeResponse方法中,最后将整个数据长度放入了缓冲区的开头。那么我们在decode的时候就可以先读取数据的长度,然后限制数据的读入长度,得到完整的数据后再进行处理。处理框图如下:

    

    具体代码如下:

     

public class CommunicateDecoder extends ByteToMessageDecoder {    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {        int length = in.getInt(0);//        Logs.access.info(">>>>>>>>> length is : {}", length);        if(in.readableBytes() >= length) {//            Logs.access.info(">>>>>>>>> in is : {}", in);            ByteBuf unPool = Unpooled.buffer(length);            in.readBytes(unPool, length);//            Logs.access.info(">>>>>>>>> in after read is : {}", in);//            Logs.access.info(">>>>>>>>> unPool is : {}", unPool);            out.add(unPool); // unPool//            Logs.access.info(">>>>>>>>> ready to send!");        }    }}