做招聘网站怎么运作,河北邯郸手机网站建设,如何提高网站的点击率,免费设计字体粘包指的是发送方在发送数据时#xff0c;多个数据包被合并成一个大的数据包发送到接收方#xff0c;接收方在接收时无法准确地区分各个数据包的边界#xff0c;从而导致数据粘在一起。
半包指的是发送方发送的数据包被拆分成了多个小的数据包#xff0c;在接收方接收时多个数据包被合并成一个大的数据包发送到接收方接收方在接收时无法准确地区分各个数据包的边界从而导致数据粘在一起。
半包指的是发送方发送的数据包被拆分成了多个小的数据包在接收方接收时无法完整地接收到一个数据包导致数据包的边界不完整出现了半个数据包。
现象分析
粘包
现象 发送 abc def接收 abcdef 原因 应用层 接收方 ByteBuf 设置太大Netty 默认 1024 传输层-网络层 滑动窗口假设发送方 256 bytes 表示一个完整报文但由于接收方处理不及时且**窗口大小足够大大于256 bytes这 256 bytes 字节就会缓冲在接收方的滑动窗口中**当滑动窗口中缓冲了多个报文就会粘包Nagle 算法会造成粘包
半包
现象 发送 abcdef接收 abc def 原因 应用层 接收方 ByteBuf 小于实际发送数据量 传输层-网络层 滑动窗口假设接收方的窗口只剩了 128 bytes发送方的报文大小是 256 bytes这时接收方窗口中无法容纳发送方的全部报文发送方只能先发送前 128 bytes等待 ack 后才能发送剩余部分这就造成了半包 数据链路层 MSS 限制当发送的数据超过 MSS 限制后会将数据切分发送就会造成半包
本质
发生粘包与半包现象的本质是因为 TCP 是流式协议消息无边界
具体原因
由于TCP协议本身的机制面向连接的可靠地协议-三次握手机制客户端与服务器会维持一个连接Channel数据在连接不断开的情况下可以持续不断地将多个数据包发往服务器但是如果发送的网络数据包太小那么他本身会启用Nagle算法可配置是否启用对较小的数据包进行合并基于此TCP的网络延迟要UDP的高些然后再发送超时或者包大小足够。那么这样的话服务器在接收到消息数据流的时候就无法区分哪些数据包是客户端自己分开发送的这样产生了粘包服务器在接收到数据库后放到缓冲区中如果消息没有被及时从缓存区取走下次在取数据的时候可能就会出现一次取出多个数据包的情况造成粘包现象。
而对于UDP本身作为无连接的不可靠的传输协议适合频繁发送较小的数据包他不会对数据包进行合并发送也就没有Nagle算法之说了他直接是一端发送什么数据直接就发出去了既然他不会对数据合并每一个数据包都是完整的数据UDP头IP头等等发一次数据封装一次也就没有粘包一说了。
半包产生的原因就简单的多可能是IP分片传输导致的也可能是传输过程中丢失部分包导致出现的半包还有可能就是一个包可能被分成了两次传输在取数据的时候先取到了一部分还可能与接收的缓冲区大小有关系总之就是一个数据包被分成了多次接收。
发生TCP粘包或拆包有很多原因但是常见原因无非就是
1、要发送的数据大于TCP发送缓冲区剩余空间大小将会发生拆包。
2、待发送数据大于MSS最大报文长度TCP在传输前将进行拆包。
3、要发送的数据小于TCP发送缓冲区的大小TCP将多次写入缓冲区的数据一次发送出去将会发生粘包。
4、接收数据端的应用层没有及时读取接收缓冲区中的数据将发生粘包。
粘包与半包的解决方法
1 短链接
客户端每次向服务器发送数据以后就与服务器断开连接此时的消息边界为连接建立到连接断开。这时便无需使用滑动窗口等技术来缓冲数据则不会发生粘包现象。但如果一次性数据发送过多接收方无法一次性容纳所有数据还是会发生半包现象所以短链接无法解决半包现象UDP)
2 使用分隔符
在数据包中添加边界在数据包中添加特殊的边界符号如换行符或者其他特殊字符接收方根据边界符号来切分数据包
行解码器
行解码器的是通过分隔符对数据进行拆分来解决粘包半包问题的
可以通过LineBasedFrameDecoder(int maxLength)来拆分以换行符(\n)为分隔符的数据也可以通过DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf... delimiters)来指定通过什么分隔符来拆分数据可以传入多个分隔符
两种解码器都需要传入数据的最大长度若超出最大长度会抛出TooLongFrameException异常
3 定长解码器
客户端于服务器约定一个最大长度保证客户端每次发送的数据长度都不会大于该长度。若发送数据长度不足则需要补齐至该长度
服务器接收数据时将接收到的数据按照约定的最大长度进行拆分即使发送过程中产生了粘包也可以通过定长解码器将数据正确地进行拆分。服务端需要用到FixedLengthFrameDecoder对数据进行定长解码具体使用方法如下
ch.pipeline().addLast(new FixedLengthFrameDecoder(16));客户端代码
客户端发送数据的代码如下
// 约定最大长度为16
final int maxLength 16;
// 被发送的数据
char c a;
// 向服务器发送10个报文
for (int i 0; i 10; i) {ByteBuf buffer ctx.alloc().buffer(maxLength);// 定长byte数组未使用部分会以0进行填充byte[] bytes new byte[maxLength];// 生成长度为0~15的数据for (int j 0; j (int)(Math.random()*(maxLength-1)); j) {bytes[j] (byte) c;}buffer.writeBytes(bytes);c;// 将数据发送给服务器ctx.writeAndFlush(buffer);
}Copy服务器代码
使用FixedLengthFrameDecoder对粘包数据进行拆分
// 通过定长解码器对粘包数据进行拆分
ch.pipeline().addLast(new FixedLengthFrameDecoder(16));长度字段解码器
在传送数据时可以在数据中添加一个用于表示有用数据长度的字段在解码时读取出这个用于表明长度的字段同时读取其他相关参数即可知道最终需要的数据是什么样子的
LengthFieldBasedFrameDecoder解码器可以提供更为丰富的拆分方法其构造方法有五个参数
public LengthFieldBasedFrameDecoder(int maxFrameLength,int lengthFieldOffset, int lengthFieldLength,int lengthAdjustment, int initialBytesToStrip)Copy参数解析
maxFrameLength 数据最大长度 表示数据的最大长度包括附加信息、长度标识等内容 lengthFieldOffset 数据长度标识的起始偏移量 用于指明数据第几个字节开始是用于标识有用字节长度的因为前面可能还有其他附加信息 lengthFieldLength 数据长度标识所占字节数用于指明有用数据的长度 数据中用于表示有用数据长度的标识所占的字节数 lengthAdjustment 长度表示与有用数据的偏移量 用于指明数据长度标识和有用数据之间的距离因为两者之间还可能有附加信息 initialBytesToStrip 数据读取起点 读取起点不读取 0 ~ initialBytesToStrip 之间的数据
参数图解 lengthFieldOffset 0
lengthFieldLength 2
lengthAdjustment 0
initialBytesToStrip 0 ( do not strip header)BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
------------------------ ------------------------
| Length | Actual Content |-----| Length | Actual Content |
| 0x000C | HELLO, WORLD | | 0x000C | HELLO, WORLD |
------------------------ ------------------------Copy从0开始即为长度标识长度标识长度为2个字节
0x000C 即为后面 HELLO, WORLD的长度 lengthFieldOffset 0
lengthFieldLength 2
lengthAdjustment 0
initialBytesToStrip 2 ( the length of the Length field)BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes)
------------------------ ----------------
| Length | Actual Content |-----| Actual Content |
| 0x000C | HELLO, WORLD | | HELLO, WORLD |
------------------------ ----------------Copy从0开始即为长度标识长度标识长度为2个字节读取时从第二个字节开始读取此处即跳过长度标识
因为跳过了用于表示长度的2个字节所以此处直接读取HELLO, WORLD lengthFieldOffset 2 ( the length of Header 1)
lengthFieldLength 3
lengthAdjustment 0
initialBytesToStrip 0BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
------------------------------------ ------------------------------------
| Header 1 | Length | Actual Content |-----| Header 1 | Length | Actual Content |
| 0xCAFE | 0x00000C | HELLO, WORLD | | 0xCAFE | 0x00000C | HELLO, WORLD |
------------------------------------ ------------------------------------Copy长度标识前面还有2个字节的其他内容0xCAFE第三个字节开始才是长度标识长度表示长度为3个字节(0x00000C)
Header1中有附加信息读取长度标识时需要跳过这些附加信息来获取长度 lengthFieldOffset 0
lengthFieldLength 3
lengthAdjustment 2 ( the length of Header 1)
initialBytesToStrip 0BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
------------------------------------ ------------------------------------
| Length | Header 1 | Actual Content |-----| Length | Header 1 | Actual Content |
| 0x00000C | 0xCAFE | HELLO, WORLD | | 0x00000C | 0xCAFE | HELLO, WORLD |
------------------------------------ ------------------------------------Copy从0开始即为长度标识长度标识长度为3个字节长度标识之后还有2个字节的其他内容0xCAFE
长度标识(0x00000C)表示的是从其后lengthAdjustment2个字节开始的数据的长度即HELLO, WORLD不包括0xCAFE lengthFieldOffset 1 ( the length of HDR1)
lengthFieldLength 2
lengthAdjustment 1 ( the length of HDR2)
initialBytesToStrip 3 ( the length of HDR1 LEN)BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)
------------------------------------ ----------------------
| HDR1 | Length | HDR2 | Actual Content |-----| HDR2 | Actual Content |
| 0xCA | 0x000C | 0xFE | HELLO, WORLD | | 0xFE | HELLO, WORLD |
------------------------------------ ----------------------Copy长度标识前面有1个字节的其他内容后面也有1个字节的其他内容读取时从长度标识之后3个字节处开始读取即读取 0xFE HELLO, WORLD