本文围绕TCP协议展开。首先我们回顾一下TCP协议的特点:TCP是一种面向连接的传输层协议。每个TCP连接只有两个端点,每个TCP连接只能是点对点(one-to-one)。TCP提供可靠的传递服务,保证传输的数据无差错、无丢失、不重复、有序。TCP提供全双工通信,TCP允许双方的应用进程随时通信发送数据。为此,TCP连接的两端都有发送缓冲区和接收缓冲区,用来临时存储双向通信的数据。TCP是面向字节流的。(本文的重点)虽然应用程序与TCP的交互是一次一个数据块(大小不一),但是TCP把应用程序递交的数据看成只是一系列非结构化的字节流。粘包警察的由来?粘包的由来?粘包警察,这个词最早见于v2。粘包警察认为“粘包”这个词是对TCP的侮辱,在TCP下讨论“粘包”是伪命题。相反,stickypackets认为“stickypackets”是TCP的问题。然后粘包警察频繁出现在“TCP粘包”帖子下,试图纠正这种偏见,并提醒大家:TCP是面向字节流的。粘包的由来一个小故事:据说一群基础薄弱的程序员经常用VC写各种Windows客户端程序,喜欢用UDP编程(VC的UDP编程,代码简单,且收发逻辑简单明了)。由于通信应用的复杂性和需求,他们试图在一个UDP包中发送多条数据,遇到了“粘包问题”。同时,他们开始接触和使用TCP。惯性思维套用之前的UDP编程方式来使用TCP。很容易遇到所谓的“粘包问题”。随着硬件的升级,多物理核CPU的普及,多线程和并行编程的开始,对程序员的基本功提出了更高的要求。这群人还在用串行的思维在并行程序中编程,肯定会遇到“粘包”问题。所以这群人总结了这个问题,称之为“粘包问题”。什么是粘包/拆包?所谓粘包:就是几个数据包粘在一起,如果要处理,必须先拆包所谓拆包:就是接收一批数据包分片,而这些分片必须粘在一起才能合成一个完整的数据包,例如:客户端向服务端发送数据时,可能会出现以下五种情况:栗子1:客户端分别发送完整的数据包A和B,服务端收到完整的数据包数据包A先,没有拆包/粘包问题。栗子2:客户端一次发送一个A和B粘合数据包。服务器收到这个数据包,服务器需要解析出A和B,就出现了粘包的问题。栗子3:客户端发送A|B-1数据包和B-2数据包,服务端先收到完整的A和部分B数据包B-1,服务端需要解析出完整的A,等待读取完整的B数据包,存在粘/解包问题。栗子4:客户端发送A-1数据包和B|A-2数据包,服务端收到A的一部分数据包A-1,此时需要等待完整的A待接收数据包,出现解包问题。栗子5:数据包A比较大,客户端分段发送数据包A,服务端需要多次接收数据包A,存在解包问题。总结:由于拆包/粘贴问题的存在,如何识别一个完整的数据包成为一个难题?难点在于如何定义数据包的边界。为什么有人说TCP粘包?我们先看一下应用程序使用TCPsockets的过程:对应TCP/IP第4层协议:当应用程序进程调用write时,内核将应用程序进程的buffer中的所有数据复制到写入socket的sendbuffer中区。本地TCP以MSS大小或更小的块将数据传递给IP。TCP报文段加上IP报头构成IP包,并根据其目的IP地址查找路由表条目,确定出接口,然后将数据报传递到相应的数据链路。这里是对MSS和MTU的解释:MTU(MaxitumTransmissionUnit)是链路层最大传输数据的大小。一般大小为1500byte。MSS(MaximumSegmentSize)是指TCP的最大报文段长度,是传输层一次发送的最大数据量的大小。MTU和MSS的一般计算关系是:MSS=MTU-IPheader-TCPheader。《PacketScientist》认为,TCPsticky/unpacking的原因有以下三种:应用程序写入的字节大小大于套接字发送缓冲区的大小。MSS+TCPheader+IPheader>MTU,需要TCP分段。如果以太网帧的有效载荷大于MTU,则需要进行IP分片。说白了,你应该把“粘袋科学家”认为我给你的东西还给我。《粘包警察》认为,这根本不是TCP的错:TCP面向字节流:根本没有包的概念,何必谈粘包/拆包。“粘包/拆包”的本质问题是:如何从二进制流中提取数据,如何定义数据的边界。说白了,“棒包警察”认为,如何解析数据是你应用层的问题,TCP只关心传输,提供可靠的投递服务。拓展:Nagle算法Nagle算法于1984年由福特航空通信公司定义为一种TCP/IP拥塞控制方法,使福特最早运营的专用TCP/IP网络能够减少拥塞控制,此后该方法得到广泛应用用过的。优点:避免为了发送尽可能大的数据块而用许多小数据块淹没网络。如果每次只需要发送1个字节的数据,加上20个字节的IP头和20个字节的TCP头,则每次发送的数据包大小为41个字节,但只有1个字节是有效信息,这就导致了非常大的浪费。Nagle算法的规则(参考tcp_output.c文件中tcp_nagle_check函数注释):如果包长度达到MSS,则允许发送;如果数据包中包含FIN,则允许发送;如果设置了TCP_NODELAY选项,则允许发送;如果没有设置TCP_CORK选项时,如果所有外出的小数据包(包长度小于MSS)都被确认,则允许发送;以上条件都不满足,但超时(一般为200ms),立即发送。Linux默认开启Nagle算法,在小数据包较多的场景下可以有效降低网络开销。可以通过Linux提供的TCP_NODELAY参数禁用Nagle算法。Netty中为了最小化数据传输延迟,默认禁用了Nagle算法。Tips:还有一种延迟ACK(DelayACK),TCP在发送ACK时有如下规定:当有响应数据要发送时,ACK会连同数据一起发送。.如果没有响应数据,ACK会有一个延时等待是否有响应数据一起发送,但是这个延时一般在40ms~500ms之间,一般在40ms左右。如果在等待发送ACK时第二个数据到达,那么应该立即发送ACK。扩展:为什么UDP不分段?先回顾一下UDP的特点:UDP不需要建立连接。无连接状态。包头开销很小。(Header8bytes)UDP是面向数据包的。(重要)发送方UDP将应用层递交的数据包在添加头部后传递给IP层,既不合并也不拆分,而是保留这些数据包的边界;接收方UDP将移交给IP层的UDP用户数据报去掉报头后原封不动地传递给上层应用进程,一次传递一个完整的报文。因此报文是不可分割的,是UDP数据报处理的最小单位。再看UDP数据报格式:可知一个UDP数据报最大可以承载的用户数据长度为:2^16-8=65535-8=65527(B)综上所述,UDP为什么不分片?UDP协议特点:面向消息。16位UDP长度。无分段能力:能够标记分段的先后顺序,即编号(ID)、尾号标识(Flag)UDP应用特性:常用于一次传输数据量相对较小的网络应用,比如DNS,SNMP等等。当DNS查询超过512字节时,协议的TC标志会显示删除标志,然后使用TCP发送。通常传统的UDP包一般不会大于512字节。解包/贴包解决方案从上面我们可以看出,我们需要一种方法来定义数据包的边界,这也是解决解包/贴包的唯一方法:定义应用层的通信协议。主流协议方案包括:消息长度固定特定定界符消息长度+消息内容Netty支持三种常用的封帧方式:方法解码和编码定长FixedLengthFrameDecoder简单定界符DelimiterBasedFrameDecoder简单定长字段存储内容长度LengthFieldBasedFrameDecoderLengthFieldPrependerFixed消息长度Netty提供类FixedLengthFrameDecoder:each数据包要求固定长度。当接收方累计读取到一条定长消息后,就认为得到了一条完整的消息。当发送方的数据小于固定长度时,就需要补空。#举个例子:假设固定消息长度为3字节,当你收到如下消息:+---+----+------+----+|一个|公元前|定义|HI|+---+----+-----+----+#解码成如下3个定长包:+-----+-----+-----+|美国广播公司|防御力|GHI|+-----+-----+-----+项目地址:对应代码:ServerBootstrapb=newServerBootstrap();b。group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).childHandler(newChannelInitializer
