Netty 黏包半包问题与解决方案
一、黏包/半包现象分析
黏包
- 现象,发送 abc def , 接收abcdef
- 原因
- 应用层: 接收方ByteBuf设置太大(Netty默认1024)
- 滑动窗口:假设发送发256 bytes 表示一个完整报文,但由于接收方处理不及时且窗口大小足够大,这256 bytes 字节就会缓冲在接收方的滑动窗口中,当滑动窗口中缓冲了多个报文就会黏包
- Nagle算法:会造成黏包
半包
- 现象,发送 abcdef , 接收abc def
- 原因
- 应用层:接收方ByteBuf小于实际发送数据量
- 滑动窗口:假设接收方窗口只剩下了128bytes,发送方的报文大小是256 bytes,这时放不下了,只能先发送前 128 bytes,等待ack后才能发送剩余部分,这就造成了半包
- MSS限制:当发送的数据超过MSS限制后,会将数据切分发送,就会造成半包
本质是因为 TCP 是流式协议,消息无边界。
黏包问题
Server端
@Slf4j
public class NServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss,worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
//添加处理器
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
}
});
ChannelFuture channelFuture = bootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
Client端
@Slf4j
public class NClient {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// 添加处理器
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
// 会在 channel 连接成功后触发 channelActive()
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 连续向客户端 发送10次,每次长度为16个字节
for (int i = 0; i < 10; 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("127.0.0.1", 8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
Server端接收结果(黏包现象):
6:39:32.811 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xecf2bdd3, L:/127.0.0.1:8080 - R:/127.0.0.1:63274] READ: 160B
+-------------------------------------------------+
| 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 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000030| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000040| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000050| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000060| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000070| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000080| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000090| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
+--------+-------------------------------------------------+----------------+
16:39:32.811 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 160, cap: 1024) that reached at the tail of the pipeline. Please check your pipeline configuration.
16:39:32.812 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xecf2bdd3, L:/127.0.0.1:8080 - R:/127.0.0.1:63274] READ COMPLETE
从该结果发现 服务端将这10次消息组合到一起接收了,也就产生了黏包现象。
半包问题
Server端
@Slf4j
public class NServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss,worker)
.channel(NioServerSocketChannel.class)
// 调整系统的接收缓冲区(滑动窗口) 10个字节
.option(ChannelOption.SO_RCVBUF,10)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
//添加处理器
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
}
});
ChannelFuture channelFuture = bootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
Client端
@Slf4j
public class NClient {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// 添加处理器
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
// 会在 channel 连接成功后触发 channelActive()
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 连续向客户端 发送10次,每次长度为16个字节
for (int i = 0; i < 10; 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("127.0.0.1", 8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
Server端接收结果(半包现象):
16:46:20.715 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ: 36B
+-------------------------------------------------+
| 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 |.... |
+--------+-------------------------------------------------+----------------+
16:46:20.715 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 36, cap: 1024) that reached at the tail of the pipeline. Please check your pipeline configuration.
16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ COMPLETE
16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] 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 |........ |
+--------+-------------------------------------------------+----------------+
16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 40, cap: 1024) that reached at the tail of the pipeline. Please check your pipeline configuration.
16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ COMPLETE
16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ: 40B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 0c 0d 0e 0f 00 01 02 03 04 05 06 07 08 09 0a 0b |................|
|00000010| 0c 0d 0e 0f 00 01 02 03 04 05 06 07 08 09 0a 0b |................|
|00000020| 0c 0d 0e 0f 00 01 02 03 |........ |
+--------+-------------------------------------------------+----------------+
16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 40, cap: 512) that reached at the tail of the pipeline. Please check your pipeline configuration.
16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ COMPLETE
16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] 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 |........ |
+--------+-------------------------------------------------+----------------+
16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 40, cap: 512) that reached at the tail of the pipeline. Please check your pipeline configuration.
16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ COMPLETE
16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ: 4B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 0c 0d 0e 0f |.... |
+--------+-------------------------------------------------+----------------+
16:46:20.717 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 4, cap: 496) that reached at the tail of the pipeline. Please check your pipeline configuration.
二、解决方案
1. 短链接(方案一) 不推荐
短链接方式,客户端 发送完消息后 立刻主动断开连接。
Client端
@Slf4j
public class NClient {
public static void main(String[] args) {
// 连续发送10次
for (int i = 0; i < 10; i++) {
send();
}
System.out.println("finish");
}
private static void send() {
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// 添加处理器
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
// 会在 channel 连接成功后触发 channelActive()
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 连续向客户端 发送10次,每次长度为16个字节
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);
// 断开连接
ctx.channel().close();
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
2.定长解码器(方案二)
需要 客户端 和 服务端 双方 约定好消息的长度 (这也是弊端)
服务端要 使用
FixedLengthFrameDecoder
定长解码器客户端要 固定好发送数据的长度
Server端
@Slf4j
public class NServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss,worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// 添加处理器
// 添加定长解码器
ch.pipeline().addLast(new FixedLengthFrameDecoder(10));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
}
});
ChannelFuture channelFuture = bootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
Client端
@Slf4j
public class NClient {
public static void main(String[] args) {
send();
System.out.println("finish");
}
// 填充为10个字节长度的数组
public static byte[] fill0Bytes(char c,int len){
byte[] b = new byte[10];
for (int i = 0; i < 10; i++) {
if (i < len){
b[i] = (byte) c;
}else {
b[i] = '_';
}
}
System.out.println(new String(b));
return b;
}
private static void send() {
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// 添加处理器
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
// 会在 channel 连接成功后触发 channelActive()
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = ctx.alloc().buffer();
// 发送 总长度为100 的消息
char c = '0';
Random r = new Random();
for (int i = 0; i < 10; i++) {
byte[] bytes = fill0Bytes(c, r.nextInt(10) + 1);
c++;
buf.writeBytes(bytes);
}
ctx.writeAndFlush(buf);
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
结果
01:16:48.461 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] 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___ |
+--------+-------------------------------------------------+----------------+
01:16:48.461 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 10, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
01:16:48.461 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 31 31 31 31 5f 5f 5f 5f 5f 5f |1111______ |
+--------+-------------------------------------------------+----------------+
01:16:48.461 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 20, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 32 32 32 32 32 32 32 32 32 32 |2222222222 |
+--------+-------------------------------------------------+----------------+
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 30, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 33 33 33 33 33 33 5f 5f 5f 5f |333333____ |
+--------+-------------------------------------------------+----------------+
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 40, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 34 34 34 34 34 34 5f 5f 5f 5f |444444____ |
+--------+-------------------------------------------------+----------------+
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 50, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 35 35 35 35 35 35 35 5f 5f 5f |5555555___ |
+--------+-------------------------------------------------+----------------+
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 60, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 36 36 36 36 36 5f 5f 5f 5f 5f |66666_____ |
+--------+-------------------------------------------------+----------------+
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 70, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 37 37 37 37 37 37 37 5f 5f 5f |7777777___ |
+--------+-------------------------------------------------+----------------+
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 80, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 38 38 38 38 38 38 38 38 38 38 |8888888888 |
+--------+-------------------------------------------------+----------------+
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 90, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 39 39 39 39 39 39 39 39 39 5f |999999999_ |
+--------+-------------------------------------------------+----------------+
01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 100, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
缺点:
- 需要 服务端 与客户端 约定好固定消息的长度。
- 若不够长度的消息,也会造成空间的浪费。
3.分隔符解码器(方案三)
约定 :消息以分隔符作为界限 (分隔符 需要 客户端与服务端协商一致)
服务端使用
DelimiterBasedFrameDecoder
(可以传入事先约定好的分隔符作为消息分界线)这里我们使用
LineBasedFrameDecoder
( 以换行符作为消息分界线)演示
Server端
@Slf4j
public class NServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss,worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// 添加处理器
// 添加 行分隔符解码器 参数: 超过该长度还没有遇到分隔符 则抛异常
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
}
});
ChannelFuture channelFuture = bootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
Client端
@Slf4j
public class NClient {
public static void main(String[] args) {
send();
System.out.println("finish");
}
// 根据字符生成字符串 以换行符结尾
public static StringBuilder makeString(char c,int len){
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) {
sb.append(c);
}
sb.append("\n");
return sb;
}
private static void send() {
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = ctx.alloc().buffer();
// 发送 消息的组合 每个消息以换行符结尾
char c = '0';
Random r = new Random();
for (int i = 0; i < 10; i++) {
StringBuilder sb = makeString(c, r.nextInt(256) + 1);
c++;
buf.writeBytes(sb.toString().getBytes());
}
ctx.writeAndFlush(buf);
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
因为结果太长 这里就不贴出来了。
4.LTC解码器(方案四)
该方案类似于 TCP/IP 中消息传输的方式, 根据报头 来解析该报文的消息体。
服务端使用
LengthFieldBasedFrameDecoder
解码器
LTC 参数介绍
-
maxFrameLength - 若消息超过该最大长度 仍没有发现分割标准 则失败抛异常
-
lengthFieldOffset - 长度字段偏移量
-
lengthFieldLength - 长度字段长度
-
lengthAdjustment - 长度字段为基准,还有几个字节是内容
-
initialBytesToStrip - 最后解析的结果需要剥离几个字节
代码演示:
这里使用 EmbeddedChannel 方便做测试
public class TestLengthFieldDecoder {
public static void main(String[] args) {
/**
* 参数1: maxFrameLength 最大接收多少长度的限制
* 参数2: lengthFieldOffset 距离 内容长度字段 的偏移量
* 参数3: lengthFieldLength 内容长度字段多少个字节
* 参数4: lengthAdjustment 从内容长度字段结尾算起 还需要多少个字节到 主内容
* 参数5: initialBytesToStrip 最终将数据从头剥离几个字节
*/
EmbeddedChannel channel = new EmbeddedChannel(
new LengthFieldBasedFrameDecoder(1024,0,4,0,0),
new LoggingHandler(LogLevel.DEBUG)
);
// 4个字节的内容长度, 实际内容
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
send(buffer, "Hello,World");
send(buffer, "hi!");
channel.writeInbound(buffer);
}
private static void send(ByteBuf buffer, String content) {
// 实际内容
byte[] bytes = content.getBytes();
// 实际内容长度
int length = bytes.length;
// 先写入长度 再写入内容
buffer.writeInt(length);
buffer.writeBytes(bytes);
}
}
结果:
12:11:51.671 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 15B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 0b 48 65 6c 6c 6f 2c 57 6f 72 6c 64 |....Hello,World |
+--------+-------------------------------------------------+----------------+
12:11:51.671 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 7B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 03 68 69 21 |....hi! |
+--------+-------------------------------------------------+----------------+
12:11:51.671 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE
将initialBytesToStrip
参数改为4 ,就意味着 要将数据去除前4个字节,后面的为最终结果
EmbeddedChannel channel = new EmbeddedChannel(
new LengthFieldBasedFrameDecoder(1024,0,4,0,4),
new LoggingHandler(LogLevel.DEBUG)
);
结果为:
12:20:12.070 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 11B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 65 6c 6c 6f 2c 57 6f 72 6c 64 |Hello,World |
+--------+-------------------------------------------------+----------------+
12:20:12.070 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 3B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 69 21 |hi! |
+--------+-------------------------------------------------+----------------+
12:20:12.070 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE