Socket TCP通信 分包问题

Posted by Packy on April 21, 2018

标签(空格分隔): 插值

Socket TCP通信

今天在做毕设的时候,通过线性插值的方法,把人脸特征点扩充到111个点,其中包含特征点,与插值点。这样一个数据包(包头2 bytes, 包尾 3 bytes, 数据 111个点,即222个double变量,222*8 bytes, 合计1781 bytes。通过socket tcp方式直接发送一个包给服务端。 服务端接收的数据包格式也更改为与客户端相同,而且缓冲buffer设置成8192字节,但是在接收过程中,发现只能接收到1460字节,甚是不解,明明发送的是1781字节,为什么不偏不倚就被砍成了1460字节。在查看了一番资料过后,在知道这是TCP通信中会出现的分包现象,之前好像某门课上也有提过,老师也跟我讲了会遇到这个问题,没想到以这种方式遇到了。

TCP通信方式会将大的数据包拆分成几个包发出去,这时候要在接收方做一定的处理,不然收到的数据是不完整的,只有一个大小为1460字节的包。

概述

在进行TCP Socket开发时,都需要处理数据包粘包和分包的情况。本文详细讲解解决该问题的步骤。使用的语言是Python。实际上解决该问题很简单,在应用层下,定义一个协议:消息头部+消息长度+消息正文即可。

但是由于本次的通信较为简单,都是定长的包,所以在设计协议的时候只是简单的加了一个包头包尾校验,接收定长数据。

分包和粘包

粘包:发送方发送两个字符串”hello”+”world”,接收方却一次性接收到了”helloworld”。

分包:发送方发送字符串”helloworld”,接收方却接收到了两个字符串”hello”和”world”。

虽然socket环境有以上问题,但是TCP传输数据能保证几点:

顺序不变。例如发送方发送hello,接收方也一定顺序接收到hello,这个是TCP协议承诺的,因此这点成为我们解决分包、黏包问题的关键。 分割的包中间不会插入其他数据。 因此如果要使用socket通信,就一定要自己定义一份协议。目前最常用的协议标准是:消息头部(包头)+消息长度+消息正文

TCP分包

TCP是以段(Segment)为单位发送数据的,建立TCP链接后,有一个最大消息长度(MSS)。如果应用层数据包超过MSS,就会把应用层数据包拆分,分成两个段来发送。这个时候接收端的应用层就要拼接这两个TCP包,才能正确处理数据。 相关的,路由器有一个最大传输单元(Maximum Transmission Unit,MTU),是指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位)。最大传输单元这个参数通常与通信接口有关(网络接口卡、串口等)。(1500左右)再减去一些包头之类的所占的长度后得到的。在一般的局域网上大概是一个TCP/UDP报能够传送的最大数据长度。 一般是1500字节,除去IP头部20字节,留给TCP的就只有MTU-20字节。所以一般TCP的MSS为MTU-20=1460字节。

由于以太网EthernetII最大的数据帧是1518Bytes这样,刨去以太网帧的帧头(DMAC目的MAC地址48bit=6Bytes+SMAC源MAC地址48bit=6Bytes+Type域2bytes)14Bytes和帧尾CRC校验部分4Bytes那么剩下承载上层协议的地方也就是Data域最大就只能有1500Bytes这个值我们就把它称之为MTU。

当应用层数据超过1460字节时,TCP会分多个数据包来发送。

UDP 包的大小就应该是 1500 - IP头(20) - UDP头(8) = 1472(BYTES) TCP 包的大小就应该是 1500 - IP头(20) - TCP头(20) = 1460 (BYTES)

在本次遇到的问题中,1781 bytes 的数据被分为了两个包,一个1460 bytes,一个321 bytes,解决方式也很简单,只需要在接收完成后,对接收的数据包的长度进行校验,如果接收的长度小于我们发送的数据包的长度,则继续接收,如果收到的所有包已经大于了数据包,则进行一次处理,处理完成后继续接收数据包。

伪代码~~:

while(true)
	bytesRead = get_msg(buffer); //但是这样有一个问题,如何不让之前的数据覆盖掉 要有一个记录此次接收包大小的变量,下次接收的初始位置从上一次接收的末尾开始。
	if(bytesRead < packet_length) contiue;
	else 
	/*process*/
	break;

或者简单粗暴一点

	bytesRead = get_msg(buffer);
	bytesRead2 = get_msg(buffer2); //接受两次 如果数据包被分成了两个
	if(bytesRead + bytesRead2 < packet_length) contiue;
	else{
		buffer += buffer2;
		//do something
	}
	

TCP粘包

有时候,TCP为了提高网络的利用率,会使用一个叫做Nagle的算法。该算法是指,发送端即使有要发送的数据,如果很少的话,会延迟发送。如果应用层给TCP传送数据很快的话,就会把两个应用层数据包“粘”在一起,TCP最后只发一个TCP数据包给接收端。

Reference

  1. 【Python】TCP Socket的粘包和分包的处理
  2. SOCKET通信中TCP、UDP数据包大小的确定