tcp报文黏连及解决方法

      TCP报文粘连就是,本来发送的是多个TCP报文,但是在接收端受到的却是一个报文,把多个报文合成了一个报文。

TCP报文粘连的原因:

1.TCP协议采用了Nagle算法

    Nagle算法产生的背景是,当时为了解决发送多个非常小的数据包时(比如1字节),由于包头的存在而造成巨大的网络开销,也就是糊涂窗口综合征(silly window syndrome)。简单的讲,Nagle算法就是当有数据要发送时,先不立即发送,而是稍微等一小会,看看在这一小段时间内,还有没有其他需要发送到消息。当等过这一小会以后,再把要发送的数据一次性都发出去。这样就可以有效的减少包头的发送次数。

2.接收端从缓冲区里读消息的速度太慢

    即发送端关闭了Nagle算法,在接收端读缓冲区的速度很慢的情况下,还是会产生报文粘连的现象。接收端收到一个TCP报文后,报文放在缓冲区里,如果这个报文没有及时被读取,这时又有新的报文发送过来,然后才读取缓冲区的内容时,就会一次读取2个报文的内容,产生报文粘连的现象。

TCP报文粘连的解决方法:

    1.关闭Nagle算法。在scoket选项中,TCP_NODELAY表示是否使用Nagle算法。据说在windows中存在既是关闭了Nagle算法,Nagle算法仍然在使用的BUG。不知到现在是什么情况。

    2.接收端尽可能快速的从缓冲区读数据。可以改进算法,或者提高任务的优先级。

    3.还有一个肯定有用但是比较麻烦的方法。可以在发送的数据中,添加一个表示数据的开头和结尾的字符,在收到消息后,通过这些字符来处理报文粘连。
--------------------- 
 

TCP数据粘连初探

 

  1. 数据粘连现象及定义

在基于TCP的网络编程项目中,数据粘连的情况其实很常见,下面,就让我们来用一个很简单的实例,来重现一下数据粘连。

例子:在基于TCP的网络环境中,客户端连续发送三个数据包给服务器,

数据包的内容分别是:"1hello!"、"2hemm!"、"3star!"

程序如下所示:

case WM_SOCKET_CLIENT:

{

switch(LOWORD(lParam))

{

case FD_WRITE://发送网络数据事件

{

send(g_sClient,"1hello!",8,0);

send(g_sClient,"2hemm!",7,0);

send(g_sClient,"3star!",7,0);

break;

}

}

break;

}

服务器端接收数据:

case WM_SOCKET_SERVER:

{

SOCKET pSock = (SOCKET)wParam;

switch(LOWORD(lParam))

{

……………..

case FD_READ://网络数据包到达事件

{

recv(pSock,recvbuf,1000,0);

break;

}

……………..

break;

}

在recv()处设置断点,调试跟踪代码后,发现接收的数据为:

从上述现象中可以发现,当我们在服务器端接收数据的大小设置成足够大时,我们就会将客户端连续三次发送的数据,一次性读入了进来,这种现象就是数据粘连。

数据粘连的定义:发送方发送的若干包数据,在接收方接收到时成一包,即后一包数据的头紧接着前一包数据的尾。

2 数据粘连的原因

数据粘连既可能由数据发送方产生,也可能由数据接收方产生。

   2.1 由发送方引起的报文粘连

    发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。

   2.2由接收方引起的报文粘连

    接收方引起的粘包是由于数据接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就紧接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据(图1所示)。

  粘连的情况

粘包情况有两种,一种是粘在一起的包都是完整的数据包(图1、图2所示),另一种情况是粘在一起的包有不完整的包(图3所示)。此处假设用户接收缓冲区长度为m个字节。

   根本原因

     无论是由于数据发送端的原因还是由于数据接收端的原因抑或是交换机的原因造成的数据粘连,其实问题的本质集中在一点,那就是数据接收端在接受数据的时候,TCP没有提供保护消息边界的措施

     所谓保护消息边界,就是指传输协议把数据当作一条独立的消息在网络上传输,接收端只能接收独立的消息。也就是说接收端一次只能接收发送端发出的一个数据包。

   而TCP(transport control protocol,传输控制协议)是一种面向连接的、流传输的协议, 是无保护消息边界的, 它把数据当作一串数据流,而不认为数据是一个一个独立的消息。这样就会出现如果发送端连续发送数据,接收端有可能在一次接收动作中,会接收两个或者更多的数据包。

   UDP(user datagram protocol,用户数据报协议)是面向非连接的,是保护消息边界的,它会把每一个发送的数据包都认为是独立的消息。也就是说在UDP协议中,是不会存在数据粘连的情况的。

举个例子来说,例如,我们连续发送三个数据包,大小分别是2k, 4k , 8k,这三个数据包,都已经到达了接收端的网络堆栈中,如果使用UDP协议,不管我们使用多大的接收缓冲区去接收数据,我们必须有三次接收动作,才能够把所有的数据包接收完;而使用TCP协议,我们只要把接收的缓冲区大小设置在14k以上,我们就能够一次把所有的数据包接收下来,只需要有一次接收动作。

3  数据粘连的解决方案

     虽然并不是所有的粘包现象都需要处理,如传输的数据为不带结构的连续流数据(如文件传输),则不必把粘连的包分开(简称分包)。但在实际工程应用中,传输的数据一般为带结构的数据,这时就需要做分包处理。

为了避免粘包现象,可采取以下几种措施。

3.1 发送方的解决方法

对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;

3.2 接收方的解决方法

(1)及时接收数据

 通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据,从而尽量避免出现粘包现象;

 (2) 将粘连在一起的报文进行分包处理

          此方法是规定报文数据某一位的内容为该帧报文数据的总长度,接收方先提取出此内容,如果缓冲区中的数据氏度大于等于该长度,则按该内容的长度从缓冲区中提取数据;如果长度不够则不提取数据,等到长度达到要求时再提取数据。这样即使m现报文粘连现象,应用程序也会将粘连在一起的数据进行分包处理,不会出现数据丢失无法识别报文ID的情况。下面通过一个具体例子进行详细说明。

           在实验线上MCU发送给DCC的状态报文长度为84字节(报文ID为91H),应答报文长度为20字节(报文ID为81H),接收缓冲区为90字节。如果状态报文粘连在应答报文之后,则将使DCC无法收到完整的状态报文。这种情况连续发生3次之后,DCC将认为任务MCU发生故障,系统将停机,因而结果必然是错误的。如果将报文长度放在报文的第一位中,报文ID放在第二位中,则进行分包处理后就不会出现上述的诊断错误。处理过程如图2所示。

3.3 解决方案分析

以上提到的三种措施,都有其不足之处。

第一种编程设置方法虽然可以避免发送方引起的粘包,但它关闭了优化算法,降低了网络发送效率,影响应用程序的性能,一般不建议使用。

第二种方法只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较高时,或由于网络突发可能使某个时间段数据包到达接收方较快,接收方还是有可能来不及接收,从而导致粘包。

  第三种分包处理的方法虽然不能防止粘连的发生,但是可以完全防止报文粘连对系统产生的影响。

4. 我们的解决方案

在最近的一个基于TCP协议的项目中,我们需要通过网络来传输图片,而一般的图片均是大于4k的,所以我们必须通过分包的形式来传输数据,由于项目的需要,我们定义了一套自己的数据传输协议,所以每发送一个数据包出去之前,我们都会加上自定义的协议头部信息,刚开始的时候,我们的处理方法是:

保证每一个发送出去的数据包大小不超过4k,这样,当数据发送端连续发送多个数据包时,如果数据接收端按照4K的大小来接收数据并进行去除头部进行解析的话,就会出现将第二个包的前一部分当成第一个包的数据进行读取,从而会在重组图片信息的时候,出现错误。

针对上述情况,我们采用以下两种方法:

  1. 数据发送端发送的数据包大小均不大于4k,数据发送端每发送一个数据包,就进入等待状态,直到数据接收端接收到数据包,并发送确认包后,再继续发送下个数据包,这样就可以保证数据接收方用户进程及时从系统接收缓冲区取走数据,有效的保证了在下一包数据到达前数据都被用户进程取走,下一包数据放到系统接收缓冲区时就不会紧接到前一包数据之后,从而有效的避免了数据包粘连的情况出现,但是,频繁的交互会增加网络负担,
  2. 数据发送端发送的每个数据包大小均为4k,当发送的数据不足4k时,直接填充0,这样,数据发送端不需要等待数据接收端的确认包,可以连续发送数据包,数据接收端每次直接以4k的大小读取数据进行解析,信息重组。

 

猜你喜欢

转载自blog.csdn.net/lusa1314/article/details/83306774
今日推荐