Netty中手动处理粘包问题
前言
最近在尝试写一套RPC的框架,使用基于Netty的NIO通讯。这个问题也是我在尝试使用Netty框架的时候遇到的一个问题。
发现问题
这是一个有关客户端和服务端进行字节流传输时遇到的问题,使用了自定义的Encoder和Decoder。
- 正常情况下发送一条数据没有出现什么问题
- 隔一秒发送一条连续发送几条有时会接收不到最后一条信息
- 连续发的情况常常将只能接受到第一条信息。
思考
首先判断是否是服务端程序出现问题,使用telnet工具进行连接发送数据,发现接收正常。
客户端隔几秒发送的数据也能够接收到前几条。
这个时候就考虑到是不是服务端在接收时没有处理好数据呢,所以就想到了粘包和数据没有传输完全的情况,数据在缓冲区内没有取出来。
解决方式
其实要解决这样的问题也很简单,其实就是几条数据被连接在一起或者一条数据没有接收完整,框架没办法判断数据是否接收完了应该反馈给开发者了,这是时候我们需要完善一下自己的传输的数据结构或者说是协议,在传输的数据头部加上一个用来表示正文长度的值,方便能够正确完整的取出这条数据。
简单的设计一个传输协议
字节 | 内容 |
---|---|
前四个字节(java中1个int为4个byte) | 协议头(包含一个int类型的表示正文的长度) |
... | 正文内容 |
步骤
- 接收到数据。
- 判断可读数据长度是否大于协议头的长度。
- 去除协议头中正文的长度
- 判断后续可读数据是否大于正文长度。
- 读取正文数据
代码部分
客户端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;
}