Netty4 TCP协议之自定义解码器,解决分包和粘包
socket流顾名思义它像一种流体,流体的特征是什么?不清楚它的开始和结尾嘛!TCP不了解上层业务数据的具体含义,它只会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。
好,现在我们有这样一个需求,客户端要向服务器发送两条指令分别是:
- ABCDE
- FGHIJ
那么最终我们可能会出现以下这四种情况
- 我们分别收到两条指令 ABCDE ***** FGHIJ
- 我们也能受到两条指令 ABCDEFG ***** HIJ
- 我们也能收到两条指令 ABC ***** DEFGHIJ
- 我们只能收到一条指令 ABCDEFGHIJ
当然我们肯定是希望只出现第一种情况,但是现实并没有这么美好。解决办法。。。
肯定是有的。
两种解决方法
- 在每发一条指令后我们加一个特殊字符来作为分割位。比如:\r\n但是这样的方式不太好,每次都要每个字节的去寻找这样的结束符,所以性能肯定不会好到哪里去。
- 第二种方式像下面这样
假如我们定义了一个这样的传输协议
/**
*
* +--------+----------+-----------+------+----------+
* | 包头 | 一级指令 | 二级指令 | 长度 | 数据 |
* +--------+----------+----------+-------+----------+
* | 4byte | 2byte | 2byte | 4byte |more byte |
* +--------+----------+----------+-------+----------+
*/
先解释一下:
包头:我们定义一个四个字节的包头,这个包头我们竟可能的让它特殊化比如:
01010101010101010101010101010101
这样的数据作为包头,其他的数据出现这样的数据的可能性将很低,它的特殊性使我们能很正确的找到这个包头。
一级指令和二级指令: 使我们能定义出丰富多样的指令
长度: 这个长度可以表示整个数据的长度,也可以表示剩余数据的长度。没有什么特别的。
数据:参与业务交互的实际数据。
废话不多说先上干货
代码
public class RequestDecoder extends ByteToMessageDecoder {
private static int BASE_LENTH = 11;
private static int HEAD = -1431655766;
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
while (true){
//判断当前缓存中的数据是否满足一条指令的最小基础数据
if(byteBuf.readableBytes() >= BASE_LENTH){
// int beginIndex;
//寻找包头
while (true){
//记录数据包开始的指针位置
byteBuf.markReaderIndex();
// beginIndex = byteBuf.readerIndex();
//判断是否是属于包头
if(byteBuf.readInt() == HEAD){
break;
}
//不是包头
//指针复位
byteBuf.resetReaderIndex();
// 缓存指针向后移动一个字节
byteBuf.readByte();
//判断当前缓存是否依然满足一条指令的最小长度
if(byteBuf.readableBytes() < BASE_LENTH){
return;
}
}
//找到了当前的包头
byte cmdOf1st = byteBuf.readByte();
short cmdOf2nd = byteBuf.readShort();
int dataLength = byteBuf.readInt();
//检验当前指令的数据长度
if(dataLength < 0){
channelHandlerContext.channel().close();
}
//判断当前数据是否已经到齐
if(byteBuf.readableBytes() < dataLength){
//没到齐
//复位到最开始的地方
byteBuf.resetReaderIndex();
// byteBuf.readerIndex(beginIndex);
return;
}
//数据到齐了
byte[] data = new byte[dataLength];
byteBuf.readBytes(data);
Request request = new Request();
request.setFirstOrder(cmdOf1st);
request.setSecondOrder(cmdOf2nd);
request.setData(data);
list.add(request);
}else{
break;
}
}
return;
}
}
以上的代码基于netty4。 其他的版本大致也都相同。
代码中都有注释,也就不过多的解释了。