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(ChannelHandlerContext ctx, Throwable cause) { LOGGER.warn(cause.getMessage(),cause.getCause()); cause.printStackTrace(); ctx.close(); } }
-
客户端启动代码
public class TimeClient { public void connect(int port, String host) { EventLoopGroup group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new TimeClientHandler()); } }); try { ChannelFuture f = b.connect(host, port).sync(); f.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } public static void main(String[] args) { new TimeClient().connect(8080, "localhost"); } }
-
服务端Handler代码
public class TimeServerHandler extends ChannelInboundHandlerAdapter { private volatile int counter; private static final String LINE = System.getProperty("line.separator"); private static final String REQUEST_DATA = "this is message from client!"; @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").substring(0, req.length - LINE.length()); System.out.println("客户端发送给服务端的数据: " + body + " ; 总共的次数是: " + (++counter)); String currentTime = REQUEST_DATA.equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "消息不完整"; currentTime = currentTime + LINE; ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); ctx.writeAndFlush(resp); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } }
-
服务端启动代码
public class TimeServer { public static void main(String[] args) { new TimeServer().bind(8080); } public void bind(int port) { EventLoopGroup boosGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(boosGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1012) .childHandler(new ChildChannelHandler()); ChannelFuture f = b.bind(port).sync(); f.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { boosGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } private class ChildChannelHandler extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new TimeServerHandler()); } } }
-
输出结果
- Server端打印(部分截图)
- client端打印(全部截图),从客户端打印3条信息可以看出,服务端只收到3条客户端发送的信息。实际上我们是发了100条信息给服务端
- Server端打印(部分截图)
使用解码器的案例
-
客户端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(ChannelHandlerContext ctx, Throwable cause) { LOGGER.warn(cause.getMessage(),cause.getCause()); cause.printStackTrace(); ctx.close(); } }
-
客户端启动代码
public class TimeClient { public void connect(int port, String host) { EventLoopGroup group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new TimeClientHandler()); } }); try { ChannelFuture f = b.connect(host, port).sync(); f.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } public static void main(String[] args) { new TimeClient().connect(8080, "localhost"); } }
-
服务端handler
public class TimeServerHandler extends ChannelInboundHandlerAdapter { private volatile int counter; private static final String LINE = System.getProperty("line.separator"); private static final String REQUEST_DATA = "this is message from client!"; @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").substring(0, req.length - LINE.length()); System.out.println("客户端发送给服务端的数据: " + body + " ; 总共的次数是: " + (++counter)); String currentTime = REQUEST_DATA.equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "消息不完整"; currentTime = currentTime + LINE; ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); ctx.writeAndFlush(resp); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } }
-
服务端启动代码
public class TimeServer { public static void main(String[] args) { new TimeServer().bind(8080); } public void bind(int port) { EventLoopGroup boosGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(boosGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1012) .childHandler(new ChildChannelHandler()); ChannelFuture f = b.bind(port).sync(); f.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { // 优雅退出,并释放线程池资源 boosGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } private class ChildChannelHandler extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new TimeServerHandler()); } } }
-
输出结果
ReplayingDecoder
- TODO…
ReplayingDecoder
扩展了ByteToMessageDecoder
编码器
MessageToByteEncoder
-
编码的复杂度大大小于解码的复杂度,这是因为编码不需考虑TCP粘包。编解码的处理还有一个常用的类
MessageToMessageCodec
用于POJO对象之间的转换 -
MessageToByteEncoder
框架可让用户使POJO对象编码为字节数据存储到ByteBuf
。用户只需定义自己的编码方法encode()
即可方法 描述 encode(ChannelHandlerContext ctx, I msg, ByteBuf out) encode()是你需要实现的唯一抽象方法,它被调用时将会传入要被该编码为ByteBuf的消息类(类型为I)。该ByteBuf之后会被转发给 ChannelPipeline
中的下一个ChannelOutboundHandler