io.netty:netty-all:4.1.33.Final
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数据包的有效载荷非常低,传递100字节的数据,需要100次TCP传送,100次ACK,在应用及时性要求不高的情况下,将这100个有效数据拼接成一个数据包,那会缩短到一个TCP数据包,以及一个ACK,有效载荷提高了,带宽也节省了
粘包现象
写入数据
+------+ +------+
| MSG1 | | MSG2 |
+------+ +------+
发送数据
+------------+
| MSG1 MSG2 |
+------------+
接收到的数据
+----+ +---------+ +-------+ +-----+
| MS | | G1MSG2 | 或者 | MSG1M | | SG2 |
+----+ +---------+ +-------+ +-----+
解决方案:通过应用协议对消息进行区分
netty针对以上的解决方案
FixedLengthFrameDecoder
:定长解码器来解决定长消息的粘包问题LineBasedFrameDecoder
:解决以回车换行符作为消息结束符的TCP粘包的问题;DelimiterBasedFrameDecoder
:特殊分隔符解码器来解决以特殊符号作为消息结束符的TCP粘包问题;LengthFieldBasedFrameDecoder
自定义长度解码器解决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());
}
}
}
输出结果
客户端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
扩展了ByteToMessageDecoder
编码的复杂度大大小于解码的复杂度,这是因为编码不需考虑TCP粘包。编解码的处理还有一个常用的类MessageToMessageCodec
用于POJO对象之间的转换
MessageToByteEncoder
框架可让用户使POJO对象编码为字节数据存储到ByteBuf
。用户只需定义自己的编码方法encode()
即可
方法 | 描述 |
---|---|
encode(ChannelHandlerContext ctx, I msg, ByteBuf out) | encode()是你需要实现的唯一抽象方法,它被调用时将会传入要被该编码为ByteBuf的消息类(类型为I)。该ByteBuf之后会被转发给ChannelPipeline 中的下一个ChannelOutboundHandler |