首页 > 分享 > Netty处理半包粘包问题

Netty处理半包粘包问题

无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制。

TCP粘包/拆包发生的根本原因【流式协议,消息无边界】

粘包主要原因

发送方每次写入数据 < 套接字缓冲区大小接收方读取套接字缓冲区数据不够及时

半包的主要原因:

发送方写入数据 > 套接字缓冲区大小发送的数据大于协议的 MTU(Maximum Transmission Unit,最大传输单元),必须拆包

拆包、粘包示例

假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下5种情况。

(1)服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包;

(2)服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包;

(3)服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包;

(4)服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包。

(5)如果此时服务端TCP接收滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种可能,即服务端分多次才能将D1和D2包接收完全,期间发生多次拆包。

解决问题的根本手段:流式协议无边界,解决问题就是找出消息的边界:

(1)方式一:消息定长,例如每个报文的大小为固定长度200字节,如果不够,空位补空格;【空间浪费,不推荐】

(2)方式二:在包尾增加回车换行符进行分割,例如FTP协议;【推荐】

(3)方式三:将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度)的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度;【推荐+】

Netty处理粘包、半包

Netty针对三种封帧方式提供了对应的处理器:

LineBasedFrameDecoder:通过换行符来区分每个包DelimiterBasedFrameDecoder:通过特殊分隔符来区分每个包FixedLengthFrameDecoder:通过定长的报文来分包LengthFieldBasedFrameDecoder:跟据包头部定义的长度来区分包

服务端添加解码器:DelimiterBasedFrameDecoder

ByteBuf delimiter = Unpooled.copiedBuffer("&".getBytes());

ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,delimiter));

//1024表示单条消息的最大长度,解码器在查找分隔符的时候,达到该长度还没找到的话会抛异常

ch.pipeline().addLast(new StringDecoder());

ch.pipeline().addLast(new StringEncoder());

Netty处理粘包、半包的源码解析:

核心原理:利用ByteBuf类型的数据积累器,若为定长,读取的数据不满足长度则进行缓存积累,下一次读取数据拼接解码

处理器都拥有一个共同的父类:ByteToMessageDecoder

public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter {

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

if (msg instanceof ByteBuf) {

CodecOutputList out = CodecOutputList.newInstance();

boolean var10 = false;

try {

var10 = true;

ByteBuf data = (ByteBuf)msg;

this.first = this.cumulation == null;

if (this.first) {

this.cumulation = data;

} else {

this.cumulation = this.cumulator.cumulate(ctx.alloc(), this.cumulation, data);

}

this.callDecode(ctx, this.cumulation, out);

var10 = false;

} catch (DecoderException var11) {

throw var11;

} catch (Exception var12) {

throw new DecoderException(var12);

} finally {

if (var10) {

if (this.cumulation != null && !this.cumulation.isReadable()) {

this.numReads = 0;

this.cumulation.release();

this.cumulation = null;

} else if (++this.numReads >= this.discardAfterReads) {

this.numReads = 0;

this.discardSomeReadBytes();

}

int size = out.size();

this.decodeWasNull = !out.insertSinceRecycled();

fireChannelRead(ctx, out, size);

out.recycle();

}

}

if (this.cumulation != null && !this.cumulation.isReadable()) {

this.numReads = 0;

this.cumulation.release();

this.cumulation = null;

} else if (++this.numReads >= this.discardAfterReads) {

this.numReads = 0;

this.discardSomeReadBytes();

}

int size = out.size();

this.decodeWasNull = !out.insertSinceRecycled();

fireChannelRead(ctx, out, size);

out.recycle();

} else {

ctx.fireChannelRead(msg);

}

}

}

FixedLengthFrameDecoder的decode为例

protected Object decode(

@SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {

if (in.readableBytes() < frameLength) {

return null;

} else {

return in.readRetainedSlice(frameLength);

}

}

相关知识

我家装修的118.4平米地中海风格,半包花了4万元,值不值?-阳光台365装修
半包花7万元装修这套89平米的四居室,现代简约风格,给大家晒晒!-中德英伦联邦B区装修
我家装修的120平米现代简约风格,半包花了10万元,值不值?-和昌莱蒙都会装修
我家装修的80平米现代风格,半包花了4万元,值不值?-金地艺境装修
蝴蝶兰出现发粘水珠的原因及处理办法
我家装修的400平米其他风格,半包花了28万元,值不值?-长泰西郊别墅装修
我家装修的185平米现代风格,半包花了8万元,值不值?-兴庆宫装修
114平米三居室的现代风格案例,半包只花6万!-银仁御墅花园装修
太不敢相信了115.53平三居室,半包花了4万,还是现代风格!-大华曲江公园世家装修
78平米的房子怎么装修合适,朋友半包花了4万,大家都惊呆了!-开元四季装修

网址: Netty处理半包粘包问题 https://m.huajiangbk.com/newsview547603.html

所属分类:花卉
上一篇: 如何开具和交付给客户电子发票
下一篇: 花与剑