李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
01.Netty进阶之粘包与半包现象分析
Leefs
2022-06-13 PM
722℃
0条
[TOC] ### 一、粘包现象 + **服务端代码** ```java import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import lombok.extern.slf4j.Slf4j; /** * Created by lilinchao * Date 2022/6/13 * Description 服务端代码 */ @Slf4j public class HelloWorldServer { void start(){ NioEventLoopGroup boss = new NioEventLoopGroup(1); NioEventLoopGroup worker = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.channel(NioServerSocketChannel.class); // serverBootstrap.option(ChannelOption.SO_RCVBUF,10); serverBootstrap.group(boss,worker); serverBootstrap.childHandler(new ChannelInitializer
() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG)); ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){ // 连接建立时会执行该方法 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { log.debug("connected{}",ctx.channel()); super.channelActive(ctx); } // 连接断开时会执行该方法 @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { log.debug("disconnect {}", ctx.channel()); super.channelInactive(ctx); } }); } }); ChannelFuture channelFuture = serverBootstrap.bind(8080); log.debug("{} binding...",channelFuture.channel()); channelFuture.sync(); log.debug("{} bound...",channelFuture.channel()); channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { log.error("server error", e); }finally { boss.shutdownGracefully(); worker.shutdownGracefully(); log.debug("stoped"); } } public static void main(String[] args) { new HelloWorldServer().start(); } } ``` + **客户端代码** ```java import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import lombok.extern.slf4j.Slf4j; import java.util.Random; /** * Created by lilinchao * Date 2022/6/13 * Description 客户端 */ @Slf4j public class HelloWorldClient { public static void main(String[] args) { NioEventLoopGroup worker = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.channel(NioSocketChannel.class); bootstrap.group(worker); bootstrap.handler(new ChannelInitializer
() { @Override protected void initChannel(SocketChannel ch) throws Exception { log.debug("connetted..."); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { //会在连接channel建立连接后,触发active事件 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { log.debug("sending..."); Random r = new Random(); char c = 'a'; for (int i = 0; i < 10; i++) { ByteBuf buffer = ctx.alloc().buffer(); buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}); ctx.writeAndFlush(buffer); } } }); } }); ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync(); channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { log.error("client error", e); } finally { worker.shutdownGracefully(); } } } ``` **运行结果** ``` 22:51:18 [DEBUG] [main] c.l.n.p.HelloWorldServer - [id: 0x10e3b18e, L:/0:0:0:0:0:0:0:0:8080] binding... 22:51:18 [DEBUG] [main] c.l.n.p.HelloWorldServer - [id: 0x10e3b18e, L:/0:0:0:0:0:0:0:0:8080] bound... 22:51:25 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x193974e2, L:/127.0.0.1:8080 - R:/127.0.0.1:61906] REGISTERED 22:51:25 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x193974e2, L:/127.0.0.1:8080 - R:/127.0.0.1:61906] ACTIVE 22:51:25 [DEBUG] [nioEventLoopGroup-3-1] c.l.n.p.HelloWorldServer - connected[id: 0x193974e2, L:/127.0.0.1:8080 - R:/127.0.0.1:61906] 22:51:25 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x193974e2, L:/127.0.0.1:8080 - R:/127.0.0.1:61906] 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 |................| +--------+-------------------------------------------------+----------------+ 22:51:25 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x193974e2, L:/127.0.0.1:8080 - R:/127.0.0.1:61906] READ COMPLETE ``` 可见虽然客户端是分别以16字节为单位,通过channel向服务器发送了10次数据,可是**服务器端却只接收了一次,接收数据的大小为160B,即客户端发送的数据总大小,这就是粘包现象**。 ### 二、半包现象 将客户端-服务器之间的channel容量进行调整,将服务端注释掉的代码打开 + **服务端代码** ```java // 调整channel的容量 serverBootstrap.option(ChannelOption.SO_RCVBUF, 10); ``` **注意** > `serverBootstrap.option(ChannelOption.SO_RCVBUF, 10)` 影响的底层接收缓冲区(即滑动窗口)大小,仅决定了 netty 读取的最小单位,**netty 实际每次读取的一般是它的整数倍** **运行结果** ``` 22:56:15 [DEBUG] [main] c.l.n.p.HelloWorldServer - [id: 0x3185dbfc, L:/0:0:0:0:0:0:0:0:8080] binding... 22:56:15 [DEBUG] [main] c.l.n.p.HelloWorldServer - [id: 0x3185dbfc, L:/0:0:0:0:0:0:0:0:8080] bound... 22:56:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x206018a7, L:/127.0.0.1:8080 - R:/127.0.0.1:61994] REGISTERED 22:56:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x206018a7, L:/127.0.0.1:8080 - R:/127.0.0.1:61994] ACTIVE 22:56:24 [DEBUG] [nioEventLoopGroup-3-1] c.l.n.p.HelloWorldServer - connected[id: 0x206018a7, L:/127.0.0.1:8080 - R:/127.0.0.1:61994] 22:56:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x206018a7, L:/127.0.0.1:8080 - R:/127.0.0.1:61994] 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 |.... | +--------+-------------------------------------------------+----------------+ 22:56:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x206018a7, L:/127.0.0.1:8080 - R:/127.0.0.1:61994] READ COMPLETE 22:56:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x206018a7, L:/127.0.0.1:8080 - R:/127.0.0.1:61994] 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 |........ | +--------+-------------------------------------------------+----------------+ 22:56:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x206018a7, L:/127.0.0.1:8080 - R:/127.0.0.1:61994] READ COMPLETE 22:56:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x206018a7, L:/127.0.0.1:8080 - R:/127.0.0.1:61994] 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 |........ | +--------+-------------------------------------------------+----------------+ 22:56:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x206018a7, L:/127.0.0.1:8080 - R:/127.0.0.1:61994] READ COMPLETE 22:56:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x206018a7, L:/127.0.0.1:8080 - R:/127.0.0.1:61994] 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 |........ | +--------+-------------------------------------------------+----------------+ 22:56:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x206018a7, L:/127.0.0.1:8080 - R:/127.0.0.1:61994] READ COMPLETE 22:56:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x206018a7, L:/127.0.0.1:8080 - R:/127.0.0.1:61994] READ: 4B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 0c 0d 0e 0f |.... | +--------+-------------------------------------------------+----------------+ 22:56:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x206018a7, L:/127.0.0.1:8080 - R:/127.0.0.1:61994] READ COMPLETE ``` 从结果可知客户端每次发送的数据,**因channel容量不足,无法将发送的数据一次性接收**,便产生了半包现象。 ### 三、现象分析 #### 3.1 粘包 * 现象,发送 abc def,接收 abcdef * 原因 * 应用层:接收方 ByteBuf 设置太大(Netty 默认 1024) * 滑动窗口:假设发送方 256 bytes 表示一个完整报文,但由于接收方处理不及时且窗口大小足够大,这 256 bytes 字节就会缓冲在接收方的滑动窗口中,当滑动窗口中缓冲了多个报文就会粘包 * Nagle 算法:会造成粘包 #### 3.2 半包 * 现象,发送 abcdef,接收 abc def * 原因 * 应用层:接收方 ByteBuf 小于实际发送数据量 * 滑动窗口:假设接收方的窗口只剩了 128 bytes,发送方的报文大小是 256 bytes,这时放不下了,只能先发送前 128 bytes,等待 ack 后才能发送剩余部分,这就造成了半包 * MSS 限制:当发送的数据超过 MSS 限制后,会将数据切分发送,就会造成半包 本质是因为 TCP 是流式协议,消息无边界 #### 3.3 滑动窗口 + TCP 以一个段(segment)为单位,每发送一个段就需要进行一次确认应答(ack)处理,但如果这么做,缺点是包的往返时间越长性能就越差 ![01.Netty粘包与半包分析01.png](https://lilinchao.com/usr/uploads/2022/06/380873106.png) + 为了解决此问题,引入了窗口概念,窗口大小即决定了无需等待应答而可以继续发送的数据最大值 ![01.Netty粘包与半包分析02.png](https://lilinchao.com/usr/uploads/2022/06/353783105.png) + 窗口实际就起到一个缓冲区的作用,同时也能起到流量控制的作用 + 图中深色的部分即要发送的数据,高亮的部分即窗口 + 窗口内的数据才允许被发送,当应答未到达前,窗口必须停止滑动 + 如果 1001~2000 这个段的数据 ack 回来了,窗口就可以向前滑动 + 接收方也会维护一个窗口,只有落在窗口内的数据才能允许接收 #### 3.4 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 ![01.Netty粘包与半包分析03.jpg](https://lilinchao.com/usr/uploads/2022/06/189348693.jpg) #### 3.5 Nagle 算法 * 即使发送一个字节,也需要加入 tcp 头和 ip 头,也就是总字节数会使用 41 bytes,非常不经济。因此为了提高网络利用率,tcp 希望尽可能发送足够大的数据,这就是 Nagle 算法产生的缘由 * 该算法是指发送端即使还有应该发送的数据,但如果这部分数据很少的话,则进行延迟发送 * 如果 SO_SNDBUF 的数据达到 MSS,则需要发送 * 如果 SO_SNDBUF 中含有 FIN(表示需要连接关闭)这时将剩余数据发送,再关闭 * 如果 TCP_NODELAY = true,则需要发送 * 已发送的数据都收到 ack 时,则需要发送 * 上述条件不满足,但发生超时(一般为 200ms)则需要发送 * 除上述情况,延迟发送 *附参考文章:* *《黑马程序员Netty教程》*
标签:
Netty
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2164.html
上一篇
11.Netty之ByteBuf介绍(三)
下一篇
02.Netty进阶之粘包与半包解决方案
取消回复
评论啦~
提交评论
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
标签云
Http
Filter
Yarn
微服务
序列化和反序列化
Ubuntu
ClickHouse
SQL练习题
Elasticsearch
高并发
Thymeleaf
数据结构
Spark Core
数学
容器深入研究
哈希表
栈
Nacos
工具
MySQL
Jquery
并发编程
Linux
Kafka
BurpSuite
Spark Streaming
MyBatis-Plus
nginx
前端
设计模式
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞