Netty服务端源码阅读笔记(五)AdaptiveRecvByteBufAllocator
上篇在看服务端读NioSocketChannel的源码的时候,读入时的缓冲区ByteBuf是由AdaptiveRecvByteBufAllocator对象动态调整的,那么存在拆包粘包问题
目录
2.1 LengthFieldBasedFrameDecoder
2.2 DelimiterBasedFrameDecoder
1、示例
1.1、粘包示例
客户端Main代码:
public class Client {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ClientHandler());
}
});
ChannelFuture future = bootstrap.connect("localhost", 8888).sync();
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
客户端handler代码:
public class ClientHandler extends ChannelInboundHandlerAdapter {
private int count = 1;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buffer = null;
for(int i=0; i<100; i++) {
String message = "=Hello "+ count++ +" World=";
byte[] bytes = message.getBytes();
// 申请缓存空间buff
buffer = Unpooled.buffer(bytes.length);
// 将数据写入到缓存
buffer.writeBytes(bytes);
// 将缓存中的数据写入到Channel
ctx.writeAndFlush(buffer);
}
}
}
服务端Main代码:
public class Server {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup parentGroup = new NioEventLoopGroup();
NioEventLoopGroup childGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new ServerHandler());
}
});
ChannelFuture future = bootstrap.bind(8888).sync();
System.out.println("服务器已启动");
future.channel().closeFuture().sync();
} finally {
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
}
}
服务端handler代码:
public class ServerHandler extends SimpleChannelInboundHandler<String> {
private int counter;
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("第【" + ++counter + "】个数据包:" + msg);
}
}
执行结果:每次都不太一样
2.2、拆包示例
只改动客户端handler代码
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
StringBuffer bf = new StringBuffer();
// 1024个s
for (int i = 0; i < 1024; i++) {
bf.append("s");
}
// + 1个d
bf.append("d");
byte[] bytes = bf.toString().getBytes();
ByteBuf buffer = null;
// 申请缓存空间
buffer = Unpooled.buffer(bytes.length);
// 将数据写入到缓存
buffer.writeBytes(bytes);
// 将缓存中的数据写入到Channel
ctx.writeAndFlush(buffer);
}
消息为1024个s和一个d的字符串,因为上篇看源码,服务端接收消息时初始byteBuf大小是1024,预想情况应该是服务端输出两次,一次1024个s,一次一个d
执行结果和预想的一致
如果放2049个字符,是不会读三次的哈,因为第一次发现没读完,缓冲区就扩容了,扩2*4倍到16384,测试一下
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
StringBuffer bf = new StringBuffer();
int size = 16384 + 1024;
for (int i = 0; i < size; i++) {
bf.append("s");
}
bf.append("d");
byte[] bytes = bf.toString().getBytes();
ByteBuf buffer = null;
// 申请缓存空间
buffer = Unpooled.buffer(bytes.length);
// 将数据写入到缓存
buffer.writeBytes(bytes);
// 将缓存中的数据写入到Channel
ctx.writeAndFlush(buffer);
}
执行结果很完美:
客户端ctx.writeAndFlush(msg)的一次消息msg过大,会被拆分,小了服务端读到的就是客户端多次写的消息,如何让服务端每次读到的数据和客户端每次writeAndFlush()是一致的呢
2、解决方法
2.1 LengthFieldBasedFrameDecoder
基于长度域的帧解码器,用于对 LengthFieldPrepender 编码器编码后的数据进行解码的
本文1中示例粘包和拆包代码,只修改客户端Main和服务端Main
客户端Main:输出处理器增加LengthFieldPrepender,为消息加上长度信息供服务端识别,因为出去的消息调用handler加工是从后往前依次处理,所以得第一个添加
服务端Main:输入处理器头增加LengthFieldBasedFrameDecoder解码器,进来的从前往后,也加在第一个
public class Client3 {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new SomeClientHandler());
}
});
ChannelFuture future = bootstrap.connect("localhost", 8888).sync();
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
public class Server3 {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup parentGroup = new NioEventLoopGroup();
NioEventLoopGroup childGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(1029, 0, 4, 0, 4));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new SomeServerHandler());
}
});
ChannelFuture future = bootstrap.bind(8888).sync();
System.out.println("服务器已启动");
future.channel().closeFuture().sync();
} finally {
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
}
LengthFieldPrepender,构造方法参数4表示为消息加上四位的长度标识
LengthFieldBasedFrameDecoder服务端解码,有个1029的参数表示能接收的最大消息长度,这里的1029是随便设的,为了测试结果方便,示例拆包代码里边1024个s加1个d再加4位信息标识 = 1029。超过长度会报错
执行结果:
把消息加长了一位s 超长报错:
LengthFieldBasedFrameDecoder 源码分析:Netty服务端源码阅读笔记(六)服务端拆包粘包--LengthFieldBasedFrameDecoder
2.2 DelimiterBasedFrameDecoder
![](https://img-blog.csdnimg.cn/20210412194512305.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h5ankxMQ==,size_16,color_FFFFFF,t_70)
ByteBuf delimiter = Unpooled.copiedBuffer("###".getBytes());
pipeline.addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
执行结果:
2.3 FixedLengthFrameDecoder
pipeline.addLast(new FixedLengthFrameDecoder(1024));
固定长度1024,对于示例粘包代码来说,没解决问题,还必须是读到这个长度才调用一次。得设置为 15才能一一对应,感觉不太方便,不看了
![](https://img-blog.csdnimg.cn/20210412192932718.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h5ankxMQ==,size_16,color_FFFFFF,t_70)
2.4 LineBasedFrameDecoder
![](https://img-blog.csdnimg.cn/2021041213415811.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h5ankxMQ==,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20210412134211353.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h5ankxMQ==,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/2021041213442752.png)
![](https://img-blog.csdnimg.cn/20210412134524585.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h5ankxMQ==,size_16,color_FFFFFF,t_70)