文章目录
netty版本
- netty版本:
io.netty:netty-all:4.1.33.Final
解码
- 将字节解码为消息或者另一个字节序列是一项常见的任务,Netty提供了一个基类
ByteToMessageDecoder
,由于无法知道消息字节会发送多少次才能发送完毕,所以这个类会对入站数据进行缓冲,直到数据已经完整到达。
ByteToMessageDecoder
-
方法
方法 描述 decode(ChannelHandlerContext ctx, ByteBuf in, List out) 这是你必须实现的唯一抽象方法。decode()方法被调用时将会传入一个包含了传入数据的ByteBuf,以及一个用来添加解码消息的List。对这个方法的调用将会重复进行,直到确定没有新的元素被添加到该 List,或者该ByteBuf中没有更多可以读取的字节时为止。然后 ,如果该List不为空,那么它的内容将会被传递给 ChannelPipeline
中的下一个ChannelInboundHandler
decodeLast(ChannelHandlerContext ctx, ByteBuf in, List out) Netty提供的这个默认实现只是简单地调用了 decode()方法。 当Channel的状态变为非活动时,这个方法将会被调用一次。可以重写该方法以提供特殊的处理,比如在Channel关闭之后产生最后一个消息 -
对于编码解码器,一旦消息被编码或者解码,就会被
ReferenceCountUtil.release(msg)
调用自动释放。如果需要保留引用,可以调用ReferenceCountUtil.retain(msg)
方法来增加引用计数,防止消息被释放
TCP粘包与拆包
-
TCP是一个流协议,所谓流就是一个没有界限的一串数据,TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际数据进行包的划分,一个完整的数据包可能会被拆分成多个TCP包进行发送,也有可能把多个小的数据包封装成一个大的TCP包进行发送
-
为什么会粘包?
-
在用户数据量非常小的情况下,极端情况下,一个字节,该TCP数据包的有效载荷非常低,传递100字节的数据,需要100次TCP传送,100次ACK,在应用及时性要求不高的情况下,将这100个有效数据拼接成一个数据包,那会缩短到一个TCP数据包,以及一个ACK,有效载荷提高了,带宽也节省了
-
粘包现象
写入数据 +------+ +------+ | MSG1 | | MSG2 | +------+ +------+ 发送数据 +------------+ | MSG1 MSG2 | +------------+ 接收到的数据 +----+ +---------+ +-------+ +-----+ | MS | | G1MSG2 | 或者 | MSG1M | | SG2 | +----+ +---------+ +-------+ +-----+
-
-
解决方案:通过应用协议对消息进行区分
- 消息长度固定,累计读取到的长度总和为定长LENGTH的报文后,就认为读取到了一个完整的消息,此时将计数器置位,重新开始读取下一个数据包
- 在包尾增加一个特殊分隔符,通过这个标志进行分割,比如回车换行符
- 通过在消息头中定义长度字段来标识消息的总长度
-
netty针对以上的解决方案
FixedLengthFrameDecoder
:定长解码器来解决定长消息的粘包问题LineBasedFrameDecoder
:解决以回车换行符作为消息结束符的TCP粘包的问题;DelimiterBasedFrameDecoder
:特殊分隔符解码器来解决以特殊符号作为消息结束符的TCP粘包问题;LengthFieldBasedFrameDecoder
自定义长度解码器解决TCP粘包问题。
-
拆包的原理
- 如果当前读取的数据不足以拼接成一个完整的业务数据包,那就保留该数据,继续从tcp缓冲区中读取,直到得到一个完整的数据包
- 如果当前读到的数据加上已经读取的数据足够拼接成一个数据包,那就将已经读取的数据拼接上本次读取的数据,够成一个完整的业务数据包传递到业务逻辑,多余的数据仍然保留,以便和下次读到的数据尝试拼接
没有解码器的案例
-
客户端Handler代码
public class TimeClientHandler extends ChannelInboundHandlerAdapter { private static final String LINE = System.getProperty("line.separator"); private static final String REQUEST_DATA = "this is message from client!"; private static final Logger LOGGER = LoggerFactory.getLogger(TimeClientHandler.class); private volatile int counter; private byte[] req; public TimeClientHandler() { req = (REQUEST_DATA + LINE).getBytes(); } /** * 向服务端连续发送100 条this is message from client!\n */ @Override public void channelActive(ChannelHandlerContext ctx) { ByteBuf message = null; for (int i = 0; i < 100; i++) { message = Unpooled.buffer(req.length); message.writeBytes(req); ctx.writeAndFlush(message); } } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req, "UTF-8"); System.out.println("服务器发送给客户端的数据是:" + body + " ;总共次数是: " + (++counter)); } @Override public void exceptionCaught(ChannelHandlerCont