MENU

Netty手动处理粘包问题

May 15, 2022 • 学习笔记

Netty中手动处理粘包问题

前言

最近在尝试写一套RPC的框架,使用基于Netty的NIO通讯。这个问题也是我在尝试使用Netty框架的时候遇到的一个问题。

发现问题

这是一个有关客户端和服务端进行字节流传输时遇到的问题,使用了自定义的Encoder和Decoder。

  • 正常情况下发送一条数据没有出现什么问题
  • 隔一秒发送一条连续发送几条有时会接收不到最后一条信息
  • 连续发的情况常常将只能接受到第一条信息。

思考

首先判断是否是服务端程序出现问题,使用telnet工具进行连接发送数据,发现接收正常。

客户端隔几秒发送的数据也能够接收到前几条。

这个时候就考虑到是不是服务端在接收时没有处理好数据呢,所以就想到了粘包和数据没有传输完全的情况,数据在缓冲区内没有取出来。

解决方式

其实要解决这样的问题也很简单,其实就是几条数据被连接在一起或者一条数据没有接收完整,框架没办法判断数据是否接收完了应该反馈给开发者了,这是时候我们需要完善一下自己的传输的数据结构或者说是协议,在传输的数据头部加上一个用来表示正文长度的值,方便能够正确完整的取出这条数据。

简单的设计一个传输协议

字节内容
前四个字节(java中1个int为4个byte)协议头(包含一个int类型的表示正文的长度)
...正文内容

步骤

  1. 接收到数据。
  2. 判断可读数据长度是否大于协议头的长度。
  3. 去除协议头中正文的长度
  4. 判断后续可读数据是否大于正文长度。
  5. 读取正文数据

代码部分

客户端RequestEncoder中
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, ProtocolData data, ByteBuf byteBuf) throws Exception {
    byteBuf.writeInt(data.getBodyData().length);    //写入一个正文长度
    byteBuf.writeBytes(data.getBodyData());
}
服务端RequestDecoder中
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
    byte[] msg = extractProtocolData(byteBuf);
    if(msg == null) return;
}

public byte[] extractProtocolData(ByteBuf byteBuf){
    int headDataLength = 4;     //自已定义的协议头长度 4个字节
    int protocolDataLength = byteBuf.readableBytes();      //获取当前缓冲区内可读的长度
    //判断协议头长度
    if(protocolDataLength < headDataLength)
        return null;        //长度不够,不读,返回

    //不可以使用ReadInt 这个方法会移动游标,如果此次没有达到要求,下一次读取时就会出现问题
//        int bodyDataLength = byteBuf.readInt();

    //加几取决于协议中正文长度在协议头的什么位置,目前长度存放在了数据最开始的位置
    int bodyDataLength = byteBuf.getInt(byteBuf.readerIndex() + 0);
    System.out.println("bodyDataLength ---> " + bodyDataLength);

    //如果长度小于协议的头长度和协议内正文的长度
    if(protocolDataLength < headDataLength + bodyDataLength)
        return null;        //长度不够,不读,返回

    //移动游标 移到协议头后面的正文
    byteBuf.readInt();
    byte[] bodyData = new byte[bodyDataLength];
    byteBuf.readBytes(bodyData);
    return bodyData;
}