知识点前文请阅读:Netty入门
粘包、半包
服务器端
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker);
// 设置服务器端接收缓冲区大小为 10 字节
serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
客户端
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 向服务器端发送 5 次数据,每次 发送16个字节
for (int i = 0; i < 5; i++) {
ByteBuf buf = ctx.alloc().buffer(16);
buf.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
ctx.writeAndFlush(buf);
}
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
worker.shutdownGracefully();
}
}
服务器端打印结果
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] REGISTERED
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] ACTIVE
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] READ: 36B // 可以看到这里接收到 36 字节,客户端每次发送 16 个字节,服务器端这里一次接收了36字节,36 > 16 出现了粘包现象,16:16:4 最后4这里出现了半包现象
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000010| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000020| 00 01 02 03 |.... |
+--------+-------------------------------------------------+----------------+
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] READ COMPLETE
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] READ: 40B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000010| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000020| 04 05 06 07 08 09 0a 0b |........ |
+--------+-------------------------------------------------+----------------+
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] READ COMPLETE
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] READ: 4B // 半包
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 0c 0d 0e 0f |.... |
+--------+-------------------------------------------------+----------------+
15:23:38 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x8fc90ea3, L:/127.0.0.1:8080 - R:/127.0.0.1:53151] READ COMPLETE
看 READ 类型,可以看到这里接收到 36 字节,客户端每次发送 16 个字节,服务器端这里一次接收了36字节,36 > 16 出现了粘包现象,16:16:4 最后4这里出现了半包现象
现象分析
粘包
现象,发送 abc def,接收 abcdef
原因
应用层:接收方 ByteBuf 设置太大(Netty 默认 1024)
滑动窗口:假设发送方 256 bytes 表示一个完整报文,但由于接收方处理不及时且窗口大小足够大,这 256 bytes 字节就会缓冲在接收方的滑动窗口中,当滑动窗口中缓冲了多个报文就会粘包
Nagle 算法:会造成粘包
半包
现象,发送 abcdef,接收 abc def
原因
应用层:接收方 ByteBuf 小于实际发送数据量
滑动窗口:假设接收方的窗口只剩了 128 bytes,发送方的报文大小是 256 bytes,这时放不下了,只能先发送前 128 bytes,等待 ack 后才能发送剩余部分,这就造成了半包
MSS 限制:当发送的数据超过 MSS 限制后,会将数据切分发送,就会造成半包
本质是因为 TCP 是流式协议,消息无边界,需要开发人员找出消息的边界
滑动窗口
TCP 以一个段(segment)为单位,每发送一个段就需要进行一次确认应答(ack)处理,但如果这么做,缺点是包的往返时间越长性能就越差
为了解决此问题,引入了窗口概念,窗口大小即决定了无需等待应答而可以继续发送的数据最大值
窗口实际就起到一个缓冲区的作用,同时也能起到流量控制的作用
图中窗口内的4段(1-1000、1001-2000、2001-3000、3001-4000)数据才允许被发送,当应答未到达前,窗口必须停止滑动如果 1-1000 这个段的数据 ack 回来了,窗口就可以向前滑动,就可以发送 4001-5000 段的数据接收方也会维护一个窗口,只有落在窗口内的数据才能允许接收
// 调整服务器端 滑动窗口大小,一般不用设置,在建立连接后系统自动设置,netty默认的是1024 serverBootstrap.option(ChannelOption.SO_RCVBUF, 10); // 调整客户端 滑动窗口大小,一般不用设置,在建立连接后系统自动设置,netty默认的是1024 bootstrap.option(ChannelOption.SO_SNDBUF, 10); // 调整 netty 的接收缓冲区大小(ByteBuf) AdaptiveRecvByteBufAllocator(最小值, 初始值, 最大值) serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16, 16));
/ 插播知识点 /
option:全局配置
childOption:单个 channel 连接的配置
MSS 限制
链路层对一次能够发送的最大数据有限制,这个限制称之为 MTU(maximum transmission unit),不同的链路设备的 MTU 值也有所不同,例如
以太网的 MTU 是 1500
FDDI(光纤分布式数据接口)的 MTU 是 4352
本地回环地址的 MTU 是 65535 - 本地测试不走网卡
MSS 是最大段长度(maximum segment size),它是 MTU 刨去 tcp 头和 ip 头后剩余能够作为数据传输的字节数
ipv4 tcp 头占用 20 bytes,ip 头占用 20 bytes,因此以太网 MSS 的值为 1500 - 40 = 1460
TCP 在传递大量数据时,会按照 MSS 大小将数据进行分割发送
MSS 的值在三次握手时通知对方自己 MSS 的值,然后在两者之间选择一个小值作为 MSS
Nagle 算法
即使发送一个字节,也需要加入 tcp(20) 头和 ip(20) 头,也就是总字节数会使用 41 bytes,非常不经济。因此为了提高网络利用率,tcp 希望尽可能发送足够大的数据,这就是 Nagle 算法产生的缘由
该算法是指发送端即使还有应该发送的数据,但如果这部分数据很少的话,则进行延迟发送
如果 SO_SNDBUF 的数据达到 MSS,则需要发送
如果 SO_SNDBUF 中含有 FIN(表示需要连接关闭)这时将剩余数据发送,再关闭
如果 TCP_NODELAY = true,则需要发送
已发送的数据都收到 ack 时,则需要发送
上述条件不满足,但发生超时(一般为 200ms)则需要发送
除上述情况,延迟发送
解决方案
短链接,发一个包建立一次连接,这样连接建立到连接断开之间就是消息的边界,缺点效率太低
每一条消息采用固定长度,缺点浪费空间
每一条消息采用分隔符,例如 n,缺点需要转义
每一条消息分为 head 和 body,head 中包含 body 的长度
- 解决方案一:短链接
服务器端
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker);
// 设置服务器端接收缓冲区大小为 10 字节
// serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
客户端
public static void main(String[] args) throws InterruptedException {
// 发送5次
for (int i = 0; i < 5; i++) {
send();
}
}
private static void send() {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = ctx.alloc().buffer(16);
buf.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
ctx.writeAndFlush(buf);
// 发送一次消息后,就关闭 channel 通道,关闭连接
ctx.channel().close();
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
worker.shutdownGracefully();
}
}
服务器端打印
16:37:52 [DEBUG] [nioEventLoopGroup-3-8] i.n.h.l.LoggingHandler - [id: 0xffa06125, L:/127.0.0.1:8080 - R:/127.0.0.1:51412] REGISTERED
16:37:52 [DEBUG] [nioEventLoopGroup-3-8] i.n.h.l.LoggingHandler - [id: 0xffa06125, L:/127.0.0.1:8080 - R:/127.0.0.1:51412] ACTIVE
16:37:52 [DEBUG] [nioEventLoopGroup-3-8] i.n.h.l.LoggingHandler - [id: 0xffa06125, L:/127.0.0.1:8080 - R:/127.0.0.1:51412] READ: 16B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
+--------+-------------------------------------------------+----------------+
16:37:52 [DEBUG] [nioEventLoopGroup-3-8] i.n.h.l.LoggingHandler - [id: 0xffa06125, L:/127.0.0.1:8080 - R:/127.0.0.1:51412] READ COMPLETE
16:37:52 [DEBUG] [nioEventLoopGroup-3-8] i.n.h.l.LoggingHandler - [id: 0xffa06125, L:/127.0.0.1:8080 - R:/127.0.0.1:51412] READ COMPLETE
16:37:52 [DEBUG] [nioEventLoopGroup-3-8] i.n.h.l.LoggingHandler - [id: 0xffa06125, L:/127.0.0.1:8080 ! R:/127.0.0.1:51412] INACTIVE
16:37:52 [DEBUG] [nioEventLoopGroup-3-8] i.n.h.l.LoggingHandler - [id: 0xffa06125, L:/127.0.0.1:8080 ! R:/127.0.0.1:51412] UNREGISTERED
16:37:52 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x3f21d899, L:/127.0.0.1:8080 - R:/127.0.0.1:51429] REGISTERED
16:37:52 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x3f21d899, L:/127.0.0.1:8080 - R:/127.0.0.1:51429] ACTIVE
16:37:52 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x3f21d899, L:/127.0.0.1:8080 - R:/127.0.0.1:51429] READ: 16B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
+--------+-------------------------------------------------+----------------+
16:37:52 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x3f21d899, L:/127.0.0.1:8080 - R:/127.0.0.1:51429] READ COMPLETE
16:37:52 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x3f21d899, L:/127.0.0.1:8080 - R:/127.0.0.1:51429] READ COMPLETE
16:37:52 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x3f21d899, L:/127.0.0.1:8080 ! R:/127.0.0.1:51429] INACTIVE
16:37:52 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x3f21d899, L:/127.0.0.1:8080 ! R:/127.0.0.1:51429] UNREGISTERED
......
可以看到可以数据没有粘在一起了,解决了粘包问题
但是短链接不能解决半包问题
测试短链接半包现象代码改造,调整服务器端netty接收缓冲区大小为16个字节,netty最小16个字节,因为取16的倍数(serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16, 16));)------- 客户端一次发送超过16个字节的数据即可。这里就不再重复贴相似的代码了
- 解决方案二:固定长度
FixedLengthframeDecoder:netty 提供的定长解码器,构造方法参数:和客户端约定传递消息的长度
服务器端
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// FixedLengthframeDecoder:netty 提供的定长解码器,构造方法参数:和客户端约定传递消息的长度
ch.pipeline().addLast(new FixedLengthframeDecoder(10));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
客户端
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = ctx.alloc().buffer();
char c = '0';
for (int i = 0; i < 5; i++) {
byte[] bytes = fill10Bytes(c, new Random().nextInt(10) + 1);
c++;
buf.writeBytes(bytes);
}
ctx.writeAndFlush(buf);
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
worker.shutdownGracefully();
}
}
public static byte[] fill10Bytes(char c, int len) {
byte[] bytes = new byte[10];
Arrays.fill(bytes, (byte) '_');
for (int i = 0; i < len; i++) {
bytes[i] = (byte) c;
}
System.out.println(new String(bytes));
return bytes;
}
服务器端打印
17:22:44 [DEBUG] [nioEventLoopGroup-3-3] i.n.h.l.LoggingHandler - [id: 0x565dcfb4, L:/127.0.0.1:8080 - R:/127.0.0.1:53524] REGISTERED
17:22:44 [DEBUG] [nioEventLoopGroup-3-3] i.n.h.l.LoggingHandler - [id: 0x565dcfb4, L:/127.0.0.1:8080 - R:/127.0.0.1:53524] ACTIVE
17:22:44 [DEBUG] [nioEventLoopGroup-3-3] i.n.h.l.LoggingHandler - [id: 0x565dcfb4, L:/127.0.0.1:8080 - R:/127.0.0.1:53524] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 30 30 30 30 30 30 30 5f 5f 5f |0000000___ |
+--------+-------------------------------------------------+----------------+
17:22:44 [DEBUG] [nioEventLoopGroup-3-3] i.n.h.l.LoggingHandler - [id: 0x565dcfb4, L:/127.0.0.1:8080 - R:/127.0.0.1:53524] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 31 31 31 5f 5f 5f 5f 5f 5f 5f |111_______ |
+--------+-------------------------------------------------+----------------+
17:22:44 [DEBUG] [nioEventLoopGroup-3-3] i.n.h.l.LoggingHandler - [id: 0x565dcfb4, L:/127.0.0.1:8080 - R:/127.0.0.1:53524] READ: 10B
......
- 解决方案三:分隔符
LinebasedframeDecoder:netty 提供的支持 n 和 rn 符号的解码器,构造参数传遇到换行符的最大长度,当读了指定长度的字节时,还没有遇到换行符,将抛出异常DelimiterbasedframeDecoder:netty 提供的支持 自定义符号的解码器,构造参数传符号的最大长度,和ByteBuf类型的自定义符号
服务器端
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// ch.pipeline().addLast(new LinebasedframeDecoder(1024));
ch.pipeline().addLast(new DelimiterbasedframeDecoder(1024, ByteBufAllocator.DEFAULT.buffer().writeBytes("|".getBytes(StandardCharsets.UTF_8))));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
客户端
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = ctx.alloc().buffer();
//buf.writeBytes("hellonworldnhinzhang sann".getBytes(StandardCharsets.UTF_8));
buf.writeBytes("hello|world|hi|zhang san|".getBytes(StandardCharsets.UTF_8));
ctx.writeAndFlush(buf);
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
worker.shutdownGracefully();
}
}
服务器端打印
17:44:53 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x4cd3ff09, L:/127.0.0.1:8080 - R:/127.0.0.1:53948] REGISTERED
17:44:53 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x4cd3ff09, L:/127.0.0.1:8080 - R:/127.0.0.1:53948] ACTIVE
17:44:53 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x4cd3ff09, L:/127.0.0.1:8080 - R:/127.0.0.1:53948] READ: 5B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 65 6c 6c 6f |hello |
+--------+-------------------------------------------------+----------------+
17:44:53 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x4cd3ff09, L:/127.0.0.1:8080 - R:/127.0.0.1:53948] READ: 5B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 77 6f 72 6c 64 |world |
+--------+-------------------------------------------------+----------------+
17:44:53 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x4cd3ff09, L:/127.0.0.1:8080 - R:/127.0.0.1:53948] READ: 2B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 69 |hi |
+--------+-------------------------------------------------+----------------+
17:44:53 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x4cd3ff09, L:/127.0.0.1:8080 - R:/127.0.0.1:53948] READ: 9B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 7a 68 61 6e 67 20 73 61 6e |zhang san |
+--------+-------------------------------------------------+----------------+
17:44:53 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x4cd3ff09, L:/127.0.0.1:8080 - R:/127.0.0.1:53948] READ COMPLETE
- 解决方案四:预设长度 LTC解码器
LengthFieldbasedframeDecoder:构造参数传(最大长度,记录长度偏移量,长度占用字节数,长度调整,剥离字节数)
服务器端
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// 参数一:最大字节数,
// 参数二:长度在buf中的偏移量,因为我在buf中先写的是长度,所以长度的偏移量是0,如果先写入2个字节的数据,再写入长度,那么长度的偏移量就是2
// 参数三:长度占用的字节,我写的是一个int类型,占用4个字节
// 参数四:需要调整的长度
// 参数五:数据从头开始需要剥离的长度,现在buf中的数据是 ....hello, world 和 ....hi!,前面的4个点是长度,我们不需要长度,所以需要把长度剥离出去
ch.pipeline().addLast(new LengthFieldbasedframeDecoder(1024, 0, 4, 0, 4));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
客户端
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = ctx.alloc().buffer();
msg(buf, "hello, world");
msg(buf, "hi!");
ctx.writeAndFlush(buf);
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
worker.shutdownGracefully();
}
}
private static void msg(ByteBuf buf, String content) {
byte[] bytes = content.getBytes(StandardCharsets.UTF_8); // 实际内容
int length = bytes.length; // 实际内容长度
buf.writeInt(length); // int的长度是4个字节,把长度写入
buf.writeBytes(bytes);
}
服务器端打印
18:36:43 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x21c2c017, L:/127.0.0.1:8080 - R:/127.0.0.1:59190] REGISTERED
18:36:43 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x21c2c017, L:/127.0.0.1:8080 - R:/127.0.0.1:59190] ACTIVE
18:36:43 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x21c2c017, L:/127.0.0.1:8080 - R:/127.0.0.1:59190] READ: 12B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 65 6c 6c 6f 2c 20 77 6f 72 6c 64 |hello, world |
+--------+-------------------------------------------------+----------------+
18:36:43 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x21c2c017, L:/127.0.0.1:8080 - R:/127.0.0.1:59190] READ: 3B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 69 21 |hi! |
+--------+-------------------------------------------------+----------------+
18:36:43 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x21c2c017, L:/127.0.0.1:8080 - R:/127.0.0.1:59190] READ COMPLETE
加深 LengthFieldbasedframeDecoder 解码器的理解,使用 EmbeddedChannel 工具类调试
public static void main(String[] args) {
EmbeddedChannel embeddedChannel = new EmbeddedChannel(
// 参数一:最大字节数,
// 参数二:长度在buf中的偏移量,因为我在buf中先写的是长度,所以长度的偏移量是0,如果先写入2个字节的数据,再写入长度,那么长度的偏移量就是2
// 参数三:长度占用的字节,我写的是一个int类型,占用4个字节
// 参数四:需要调整的长度,现在除了内容以外,在buf中又加了1个字节长度的版本号,所以需要调整的长度是1
// 参数五:数据从头开始需要剥离的长度,现在buf中的数据是 .....hello, world 和 .....hi!,前面的4个点是长度,第5个点是版本号,我们不需要长度,所以需要把长度剥离出去,最后的数据是 .hello, world 和 .hi!
new LengthFieldbasedframeDecoder(1024, 0, 4, 1, 4)
, new LoggingHandler(LogLevel.DEBUG)
);
// 4 个字节的长度,然后是实际内容
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
msg(buf, "hello, world");
msg(buf, "hi!");
embeddedChannel.writeInbound(buf);
}
private static void msg(ByteBuf buf, String content) {
byte[] bytes = content.getBytes(StandardCharsets.UTF_8); // 实际内容
int length = bytes.length; // 实际内容长度
// buf.writeBytes(new byte[]{1,2}); // 加入在长度之前多写入了两个字节的数据,那么LengthFieldbasedframeDecoder的第二个参数就是2
buf.writeInt(length); // int的长度是4个字节,把长度写入
buf.writeByte(1); // 传入其他数据,例如版本号
buf.writeBytes(bytes);
}
打印
18:50:42 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] REGISTERED
18:50:42 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] ACTIVE
18:50:42 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 13B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 68 65 6c 6c 6f 2c 20 77 6f 72 6c 64 |.hello, world |
+--------+-------------------------------------------------+----------------+
18:50:42 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 4B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 68 69 21 |.hi! |
+--------+-------------------------------------------------+----------------+
18:50:42 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE



