链路层具有最大传输单元MTU这个特性,限制了数据帧的最大长度,不同的网络类型都有一个上限值。如以太网Ethernet II和802.3规范对帧中的数据字段长度都有一个限制,其最大值分别是1500和1492个字节。
如果IP层有数据包要传,而且数据包的长度(包括IP头)超过了MTU,那么IP层就要对数据包进行分片(fragmentation)操作,使每一片的长度都小于或等于MTU。
MTU& MSS
这里以Ethernet II 为例,MAC头以下的 IP 数据报最大传输单位为 MTU ( Maximum Transmission Unit),对于大多数使用以太网的局域网来说,MTU=1500 。
Wireshark中看以太网帧的封包格式为:
Frame = Ethernet Header + IPHeader + TCP Header + TCP Segment Data
(1 )Ethernet Header = 14 Byte = Dst Physical Address (6 Byte )+ Src Physical Address (6 Byte )+ Type (2 Byte ),以太网帧头以下称之为数据帧 。
(2 )IP Header = 20 Byte (without options field ),数据在IP 层称为 Datagram ,分片称为 Fragment。
( 3 ) TCP Header
= 20 Byte ( without options field),数据在 TCP 层称为 Stream ,分段称为 Segment( UDP 中称为 Message )。
( 4 ) 54 个字节后为 TCP 数据负载部分( Data Portion ),即应用层用户数据。
TCP分段
TCP自身支持分段,当TCP要传输长度超过MSS(Maxitum Segment Size)的数据时,会先对数据进行分段,正常情况下,MSS小于MTU,因此,TCP一般不会造成IP分片。若数据过大,只会在传输层进行数据分段,到了IP层就不用分片。因此采用TCP协议进行数据传输,是不会造成IP分片的。
HTTP 请求消息一般不会很长,一个网络包就能装得下,但如果其中要提交表单
数据,长度就可能超过一个网络包所能容纳的数据量,比如在博客或者论坛上发表一篇长文就属于这种情况。这种情况下,发送缓冲区中的数据就会超过 MSS 的长度,这时我们当然不需要继续等待后面的数据了。发送缓冲区中的数据会被以 MSS 长度为单位进行拆分,拆分出来的每块数据会被放进单独的网络包中。
根据发送缓冲区中的数据拆分的情况,当判断需要发送这些数据时,就在每一块数据前面加上 TCP 头部,并根据套接字中记录的控制信息标记发送方和接收方的端口号,然后交给 IP 模块来执行发送数据的操作。
TCP 数据包每次能够传输的最大数据分段称为 MSS (Maxitum Segment Size),为了达到最佳的传输效能,在建立 TCP 连接时双方协商 MSS 值,双方提供的 MSS 值的最小值为这次连接的最大 MSS 值。MSS 往往基于 MTU 计算出来,通常 MSS=MTU-sizeof(IP Header)-sizeof(TCP
Header)=1500-20-20=1460 。如
TCP segment len 指出了应用程序数据的大小,即应用层数据经初次封装后的大小。使用wirshark看起内容如下
POST /License/client/ProcessQueryServlet
HTTP/1.1
Host: 172.24.116.249:8050
Connection: keep-alive
Content-Length: 118
Cache-Control: max-age=0
Origin:
http://172.24.116.249:8050
Upgrade-Insecure-Requests:
1
User-Agent: Mozilla/5.0
(Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/54.0.2840.99 Safari/537.36
Content-Type:
application/x-www-form-urlencoded
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer:
http://172.24.116.249:8050/License/client/PreQueryServlet
Accept-Encoding: gzip,
deflate
Accept-Language:
zh-CN,zh;q=0.8
Cookie:
JSESSIONID=67C7292511815E7AA38CE5B863FC0E02
cause=&%E5%AE%A2%E6%88%B7%E5%90%8D%E7%A7%B0=kehumingcheng&%E5%AE%A2%E6%88%B7%E5%8F%B7=kehuhao&query=%E6%9F%A5%E8%AF%A2
上述请求头加上body实体的长度就是776bytes。在nodepad中看如下
TCP/IP 可以使用一些可选参数(protocol option),如加密等,这时头部的长度会
侵占一些MSS空间,则真正用来承载数据的就剩下1500-20-20-12=1448字节了。如
IP分片
Tcp建立连接的时候,必须进行三次握手,在前两个握手包中,双方互相声明了自己的MSS。经过握手后,彼此知道自己的MSS,就会自动调整从而适配接收方的MTU。如一个IP包最多可以携带1500-20=1480字节的数据。当要传输的数据块大于1460字节时,TCP层就会把它分段,封装成多个网络包。这样,数据经过本地 TCP 层分段后,交给本地 IP 层,在本地 IP 层就不需要分片了。
从这一层意义上说,其实TCP无所谓分段,因为每个TCP数据报在组成前其大小就已经被MSS限制了,所以TCP数据报的长度是不可能大于MSS的,自然到了IP层的数据报肯定不会超过MTU,当然也就不用分片了。
然而,如果网络上有交换机之类的MTU比发送方和接收方都小,则还会有问题。实际上,在下一跳路由( Next Hop )的邻居路由器上可能发生 IP 分片。因为路由器的网卡的MTU 可能小于需要转发的 IP 数据报的大小。这时候,在路由器上可能发生两种情况:
( 1 )如果源发送端设置了这个 IP 数据包可以分片( May
Fragment , DF=0 ),路由器将 IP 数据报分片后转发。
( 2 )如果源发送端设置了这个 IP 数据报不可以分片( Don’t
Fragment , DF=1 ),路由器将 IP 数据报丢弃,并发送 ICMP 分片错误消息给源发送端。
而对于UDP数据报和ICMP,如果组成的 IP数据报长度超过了1500,那么IP数据报显然就要进行分片,因为它们不能像TCP一样自己进行分段。
UDP和ICMP认为网络层可以传输无限长(实际上有65535的限制)的数据,当这两种协议发送数据时,它们不考虑数据长度,仅在其头部添加UDP或ICMP首部,然后直接交给网络层就万事大吉了。接着网络层IP协议对这种“身长头短”的数据进行分片,不要指望IP能很“智能”地识别传给它的数据上层头部在哪里,载荷又在哪里,它会直接将整个的数据切成N个分片,这样做的结果是,只有第一个分片具有UDP或者ICMP首部,而其它分片则没有。因此UDP不会分段,而是由IP来分。
一般IP首部为20字节,UDP或ICMP首部为8字节,数据的净荷(payload)部分预留是1500-20-8=1472字节。如果数据静荷载部分大于1472字节,就会出现分片现象。
以ping 一个大包 1473字节为例,
icmp包头位于第一个分片中,其余分片只包含ip头和数据。除了最后一个分片,其它分片的数据(除去ip头)必须是8的整数倍。
第一个分片包的有效载荷为:1514-14-20-8=1472,第二个分片包的有效载荷为:35-14-20=1,可见1473大小的数据被分在了两个包里面进行传输。