Netty进阶
1、黏包
服务端
//测试黏包服务端
@Slf4j
public class TestNianbaoServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ChannelFuture channelFuture = new ServerBootstrap()
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new LoggingHandler());
}
})
.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error");
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
客户端
//黏包测试客户端
public class TestNianbaoClient {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
ChannelFuture channelFuture = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
//连接建立成功,active事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 10; i++) {
ByteBuf buf = ctx.alloc().buffer();
buf.writeBytes("0123456789".getBytes());
ctx.writeAndFlush(buf);
}
}
});
}
})
.connect(new InetSocketAddress(8080)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
group.shutdownGracefully();
}
}
}
结果
12:46:22.133 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x9546fc94, L:/192.168.1.91:8080 - R:/192.168.1.91:11527] READ: 100B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 30 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 |0123456789012345|
|00000010| 36 37 38 39 30 31 32 33 34 35 36 37 38 39 30 31 |6789012345678901|
|00000020| 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 37 |2345678901234567|
|00000030| 38 39 30 31 32 33 34 35 36 37 38 39 30 31 32 33 |8901234567890123|
|00000040| 34 35 36 37 38 39 30 31 32 33 34 35 36 37 38 39 |4567890123456789|
|00000050| 30 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 |0123456789012345|
|00000060| 36 37 38 39 |6789 |
+--------+-------------------------------------------------+----------------+
客户端分10此发送的,每次10个字节,但服务端收到的直接100字节,就是黏包
2、半包
服务端
//测试黏包半包服务端
@Slf4j
public class TestNianbaoBanbaoServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ChannelFuture channelFuture = new ServerBootstrap()
.group(boss, worker)
.channel(NioServerSocketChannel.class)
//测试半包,ChannelOption.SO_RCVBUF 滑动窗口大小=8
.option(ChannelOption.SO_RCVBUF, 8)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new LoggingHandler());
}
})
.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error");
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
客户端
同上
结果
13:18:14.722 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x18f9db8b, L:/192.168.1.91:8080 - R:/192.168.1.91:14236] READ: 32B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 30 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 |0123456789012345|
|00000010| 36 37 38 39 30 31 32 33 34 35 36 37 38 39 30 31 |6789012345678901|
+--------+-------------------------------------------------+----------------+
..................
服务端接收到了 3 次循环的完整数据和第四次循环数据中的0 1,第四次的数据就半包了
3、现象分析
粘包
- 现象,发送 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 以一个段(segment)为单位,每发送一个段就需要进行一次确认应答(ack)处理,但如果这么做,缺点是包的往返时间越长性能就越差
- 为了解决此问题,引入了窗口概念,窗口大小即决定了无需等待应答而可以继续发送的数据最大值
- 第一个应答回来之后,窗口向下滑一位
- 窗口实际就起到一个缓冲区的作用,同时也能起到流量控制的作用
- 图中深色的部分即要发送的数据,高亮的部分即窗口
- 窗口内的数据才允许被发送,当应答未到达前,窗口必须停止滑动
- 如果 1001~2000 这个段的数据 ack 回来了,窗口就可以向前滑动
- 接收方也会维护一个窗口,只有落在窗口内的数据才能允许接收