周末了,文章继续。在使用TCP协议进行通信时,听到最多的也就是粘包和拆包问题。本文就来看看,如何解决粘包和拆包问题。
一 TCP的粘包/拆包的问题以及解决
在解决TCP粘包和拆包,我们先看看一种思想。来看看读取一个Int数据的Demo,体会下这种思想。
1.1 ReplayingDecoder
1. 自定义解码器,从ByteBuf读取一个Int。(重点,一定要看懂这段代码)
public class IntegerHeaderFrameDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
if (buf.readableBytes() < 4) {
return;
}
buf.markReaderIndex();//标记下当前读指针。
int length = buf.readInt();//从ByteBuf读出一个int
if (buf.readableBytes() < length) {
buf.resetReaderIndex();//恢复到刚才标记的读指针
return;
}
out.add(buf.readBytes(length));
}
}
2. 使用ReplayingDecoder进行优化()
public class IntegerHeaderFrameDecoder extends ReplayingDecoder<Void> {
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
out.add(buf.readBytes(buf.readInt()));
}
}
3. ReplayingDecoder使用说明(重点,要理解的)
a 使⽤了特殊的ByteBuf,叫做ReplayingDecoderByteBuf,扩展了ByteBuf
b 重写了ByteBuf的readXxx()等⽅法,会先检查可读字节⻓度,⼀旦检测到不满⾜要求就直接抛出REPLAY(REPLAY继承ERROR)
c ReplayingDecoder重写了ByteToMessageDecoder的callDecode()⽅法,捕获Signal并在catch块中重置ByteBuf的readerIndex。
d 继续等待数据,直到有了数据后继续读取,这样就可以保证读取到需要读取的数据。
e 类定义中的泛型S是⼀个⽤于记录解码状态的状态机枚举类,在state(S s)、checkpoint(S s)等⽅法中会⽤到。在简单解码时也可以⽤java.lang.Void来占位。
总结:
ReplayingDecoder是ByteToMessageDecoder的子类,扩展了ByteBuf。从写了readXxx()等⽅法,当前ByteBuf中数据小于代取数据,等待数据满足,才能取数据。就可以省略手动实现这段代码。
4. 注意
1 buffer的部分操作(readBytes(ByteBuffer dst)、retain()、release()等⽅法会直接抛出异常)
2 在某些情况下会影响性能(如多次对同⼀段消息解码)
继承ReplayingDecoder,错误示例和修改
//这是⼀个错误的例⼦:
//消息中包含了2个integer,代码中decode⽅法会被调⽤两次,此时队列size不等于2,这段代码达不到期望结果。
public class MyDecoder extends ReplayingDecoder<Void> {
private final Queue<Integer> values = new LinkedList<Integer>();
@Override
public void decode(ByteBuf buf, List<Object> out) throws Exception {
// A message contains 2 integers.
values.offer(buf.readInt());
values.offer(buf.readInt());
assert values.size() == 2;
out.add(values.poll() + values.poll());
}
}
//正确的做法:
public class MyDecoder extends ReplayingDecoder<Void> {
private final Queue<Integer> values = new LinkedList<Integer>();
@Override
public void decode(ByteBuf buf, List<Object> out) throws Exception {
// Revert the state of the variable that might have been changed
// since the last partial decode.
values.clear();
// A message contains 2 integers.
values.offer(buf.readInt());
values.offer(buf.readInt());
// Now we know this assertion will never fail.
assert values.size() == 2;
out.add(values.poll() + values.poll());
}
}
ByteToIntegerDecoder2的实现
public class ByteToIntegerDecoder2 extends ReplayingDecoder<Void> {
/**
* @param ctx 上下⽂
* @param in 输⼊的ByteBuf消息数据
* @param out 转化后输出的容器
* @throws Exception
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
out.add(in.readInt()); //读取到int类型数据,放⼊到输出,完成数据类型的转化
}
}
1.2 拆包和粘包问题重现(客户端向服务端发送十条数据)
1. 客户端启动类
public class NettyClient {
public static void main(String[] args) throws Exception{
EventLoopGroup worker = new NioEventLoopGroup();
try {
// 服务器启动类
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ClientHandler());