学习网络应用开发的时候最大的疑惑是“分片”。几乎在TCP/IP的每一层都有这个概念,由于专注这方面的资料非常少所以对这部分内容很多朋友多云山雾绕的,这篇文章总结了我关于TCP/IP分片、重组的一些认识,希望对大家有帮助。
MTU
按不同的网络传输介质和传输算法网络有不同的种类,比如令牌环、FDDI、ATM、以太网。其中以太网最为普遍——几乎成为了网络的代名词,所以我们所能接触到的所有网络几乎都是以太网。以太网的传输介质是铜缆、双绞线、光纤;传输算法是CSMA/CD,按照这个算法规定数据并不是“源源不断”的发送出去的,而是每次发送“一小段”,发送完毕后要检测是否冲突。MTU(Maximum Transmission Unit,最大传输单元)就是指“一小段”数据有多大。按照IEEE802.3(以太网技术的标准化名称)规范MTU的最大值是64字节、最大长度是1518字节。
IEEE之所以选择这两个值是为了考虑到线路的利用率,以10Mbps为例(以太网最早的标准),最大传输距离是500m,做多允许中继4次,所以它最大允许2500m的传输距离。
数据在这个距离上跑个一来一回需要57.6μs,在这个时间内A一共可以发送576bit也就是72字节。去掉8个字节的前导码和帧开始符(一个帧以7个字节的前导码和1个字节的帧开始符作为帧的开始)也就是64字节。最大值1518字节有点“拍脑袋”的意思,采用这个值可以让线路利用率更高,至于为什么会这样几乎没有人能回答上来(包括以太网之父Bob Metcalfe也说不清楚)
以太网最大MTU是1518,(去掉以太网的源MAC地址(6字节)+目标MAC地址(6字节)+类型(2字节)+冗余校验(4字节);所以能够给上层协议用的最大值是1500)基于以太网的IP协议无论多大,在物理传输的时候都会被切分成1500个字节“按块传输”。
虽然是“按块传输”但是数据链路协议并没有分块和重组的概念,你可以想想发送端有一个“大水池”,数据链路每次从水池里取1518个字节后仍出去;接收端也有一个大水池,数据链路协议收到数据包后一股脑全部扔进去。(“大水池”是所有进程共享的,暂且忽略“溢出的”可能)
IP重组
数据链路层协议考虑到的是“两块网卡怎么传输数据”,两端的“大水池”相当于网卡的内存空间。数据运到“大水池”里面后要有人来“分拣”,完成这项工作的就是IP协议,它会对数据包进行分拣,首先扔掉那些IP地址不是本机的数据;然后根据端口来对数据包进行分类。
每个IP端口都有一个大水池(其实所有的大水池都是链表),IP协议把分拣的数据放到每个端口对应的大水池里面。如果IP协议的大小是1500那么我们就不用考虑重组的问题了,但是TCP/IP协议设计的时候不是针对以太网设计的。IP协议中表示长度的字段是16位,那么单个IP数据包大小最大可以达到——65536字节。这意味着一个IP数据包可能会被拆分成多个以太网数据帧(MTU1500)传送,所以IP数据包必须考虑能够把N个1500字节的帧组装起来,这就是IP重组。(IP协议从来不会主动“分片”,它只是被迫重组,受限于MTU所以有重组机制)
我们可以做个试验,ICMP(ping)协议是基于IP的,我们模拟从172.16.46.141发送一个大小为1500的数据包到172.16.46.142。一个1500字节+ICMP头部(8字节)=1508;会被分成两个IP数据包,一个大小是1480(+IP头20字节刚好是1500),一个是28(+IP头20字节是48字节)。
可以看到截图中两个IP数据包的id都是22828,其中第一个的flags部分是[+],表示有IP分片并且这是第一个分片。
- 第一个IP分片完整大小是1500(MTU),去掉IP头(20字节),实际大小是1480(ICMP数据包,包含头部)
- 第二个IP分片完整大小是48,去掉IP头部(20字节),实际大小是28字节
- 1480+28=1508,刚好是我们ICMP数据包的大小
这里需要解释IP重组的几个问题
- IP只有重组,没有分片。IP数据包头部的MF标志位主要用于解决MTU和IP大小不匹配的问题,用于IP数据包重组,IP数据包从来不会主动分片。
- IP没有重传,IP数据包被分为多个帧传输,**如果任何一个帧丢失IP数据包都会重组失败那么整个数据包都会被丢弃**。所以基于IP协议的上层协议一般不会发送超过1500大小的数据包(考虑一下如果用UDP协议发送65535个字节,被拆分成43个MTU大小的帧,收到了42个,其中一个没有收到那么IP数据包重组失败,数据包被彻底丢弃,是不是很浪费带宽?)
TCP的MSS
TCP协议格式中没有一个字段表示数据包大小,它被设计成一个“流式”协议,所以在三次握手的时候会相互交换MSS(Maximum Segment Size 最大分段大小),表示一个TCP分片是多大。
那么MSS取值多少合适呢?有了上面的结论IP没有重传不难得出答案——1500最合适。如果TCP数据分片超过这个值会被拆分成多个MTU帧,从而引起IP重组;IP重组本身是不可靠的所以很有可能丢失数据包(最重要的是IP重组失败后不会报告上层协议)要命的是TCP协议不会知道重组失败,也没有办法重传。
所以如果我们用tcpdump抓包所有的TCP三次握手MSS都是等于MTU值的。
UDP呢?
长期以来我一直疑惑单个UDP数据包最大值是多少(调用send/recv函数时传递的大小),UDP头部有2个字节表示长度理论上一个UDP数据包最大能够传输65536字节,这个也是IP数据包的最大理论值。有些系统定义了SO_MAX_MSG_SIZE宏来表示这个限制。
但是如果UDP数据包真的用这个值那么一定会触发IP重组从而又回到了我们上面的结论IP只有重组没有重传。一个UDP数据包被拆分成43个MTU大小的帧,对端收到了42个,其中一个没有收到那么IP数据包重组失败,数据包被彻底丢弃,如果网络质量不是特别好UDP数据包会经常“丢包”。
所以UDP最大数据包值合适的大小是1500-8(UDP包头)=1492。
总结
IP只有重组没有重传,如果任何一个IP包丢失那么就会把整个数据包丢弃。所以TCP用MTU值作为MSS;UDP用1492作为最大值以此来避免IP重组。
【本文是51CTO专栏作者“邢森”的原创文章,转载请联系作者本人获取授权】