http://kibazen.cn/2015/ethernet-ip-tcp-udp-packet-length/
一、它们之间的关系
TCP/IP协议是分层的。以太网帧的数据帧在链路层,IP包在网络层,TCP/UDP在传输层,TCP/UDP中的数据在应用层。
应用层 | TCP/UDP中的数据 |
传输层 | TCP/UDP |
网络层 | IP包 |
数据链路层 | 以太网帧 |
物理层 |
上一层的数据连同该层的控制信息打包交给下一层,直到最底层。所以他们之间的关系:以太网帧{IP包{TCP/UDP{数据}}}。
二、以太网帧(数据链路层)
目前以太网帧有5种,交换机之间BPDU(桥协议数据单元)数据包使用的是IEEE802.3/LLC帧,其格式如下:
字段 | 长度(字节) | 目的 |
前导码(Preamble) | 7 | 0x55,一串1、0间隔,用于信号同步 |
帧开始符(SFD) | 1 | 1字节0xD5(10101011),表示一帧开始 |
目的MAC地址 | 6 | 指明帧的接受者 |
源MAC地址 | 6 | 指明帧的发送者 |
长度(Length)/类型(Type) | 2 | 0~1500保留为长度域值,1536~65535保留为类型域值(0x0600~0xFFFF) |
数据和填充(Data and Pad) | 46~1500 | 高层的数据,通常为3层协议数据单元。对于TCP/IP是IP数据包(注:如果帧长小于64字节,则要求“填充”,以使这个帧的长度达到64字节) |
帧校验序列(FCS) | 4 | 使用CRC计算从目的MAC到数据域这部分内容而得到的校验和 |
据RFC894的说明,以太网封装IP数据包的最大长度是1500字节(所以,数据链路层的最大传输单元(Maximum Transmission Unit,MTU)是1500字节),也就是说以太网最大帧长应该是以太网首部加上1500,再加上7字节的前导同步码和1字节的帧开始定界符,具体就是:7字节前导同步吗+1字节帧开始定界符+6字节的目的MAC+6字节的源MAC+2字节的帧类型+1500+4字节的FCS。
按照上述,最大帧应该是1526字节,但是实际上我们抓包得到的最大帧是1514字节,为什么不是1526字节呢?原因是当数据帧到达网卡时,在物理层上网卡要先去掉前导同步码和帧开始定界符,然后对帧进行CRC检验,如果帧校验和错,就丢弃此帧。如果校验和正确,就判断帧的目的硬件地址是否符合自己的接收条件(目的地址是自己的物理硬件地址、广播地址、可接收的多播硬件地址等),如果符合,就将帧交“设备驱动程序”做进一步处理。这时我们的抓包软件才能抓到数据,因此,抓包软件抓到的是去掉前导同步码、帧开始分界符、FCS之外的数据,其最大值是6+6+2+1500=1514。
以太网规定,以太网帧数据域部分最小为46字节,也就是以太网帧最小是6+6+2+46+4=64。除去4个字节的FCS,因此,抓包时就是60字节。当数据字段的长度小于46字节时,MAC子层就会在数据字段的后面填充以满足数据帧长不小于64字节。由于填充数据是由MAC子层负责,也就是设备驱动程序。不同的抓包程序和设备驱动程序所处的优先层次可能不同,抓包程序的优先级可能比设备驱动程序更高,也就是说,我们的抓包程序可能在设备驱动程序还没有填充不到64字节帧的时候,已经捕获了数据。因此不同的抓包工具抓到的数据帧的大小可能不同。(比如,wireshark抓到的可能没有填充数据段,而sniffer抓到的就有填充数据段)
而为什么以太网帧最后最小的值是64字节呢?考虑如下的情况,主机发送的帧很小,而两台冲突主机相距很远。在主机A发送的帧传输到B的前一刻,B开始发送帧。这样,当A的帧到达B时,B检测到冲突,于是发送冲突信号。假如在B的冲突信号传输到A之前,A的帧已经发送完毕,那么A将检测不到冲突而误认为已发送成功。由于信号传播是有时延的,因此检测冲突也需要一定的时间。这也是为什么必须有个最小帧长的限制。
按照标准,10Mbps以太网采用中继器时,连接的最大长度是2500米,最多经过4个中继器,因此规定对10Mbps以太网一帧的最小发送时间为51.2微秒。这段时间所能传输的数据为512位,因此也称该时间为512位时。这个时间定义为以太网时隙,或冲突时槽。512位=64字节,这就是以太网帧最小64字节的原因。
512位时是主机捕获信道的时间。如果某主机发送一个帧的64字节仍无冲突,以后也就不会再发生冲突了,称此主机捕获了信道。
由于信道是所有主机共享的,如果数据帧太长就会出现有的主机长时间不能发送数据,而且有的发送数据可能超出接收端的缓冲区大小,造成缓冲溢出。为避免单一主机占用信道时间过长,规定了以太网帧的最大帧长为1500。
100Mbps以太网的时隙仍为512位时,以太网规定一帧的最小发送时间必须为5.12μs。
1000Mbps以太网的时隙增至512字节,即4096位时,4.096μs。
三、IP包(网络层)
IP头大小最小为20字节。所以,网络层的MTU=数据链路层的MTU1500-20=1480字节。
在IP头中,用2个字节来描述报文的长度,2个字节所能表达的最大数字就是65535。所以,IP数据包的最大长度就是64K字节(65535)。
由于IP协议提供为上层协议分割和重组报文的功能,因此传输层协议的数据包长度原则上来说没有限制。实际上限制还是有的,因为IP包的标识字段终究不可能无限长,按照IPv4,好像上限应该是4G(64K*64K)。
四、TCP(传输层)
依靠IP协议提供的报文分割和重组机制,TCP包头中就没有“包长度”字段,而完全依靠IP层去处理分帧。这就是为什么TCP常常被称作一种“流协议”的原因,开发者在使用TCP服务的时候,不必去关心数据包的大小,只需讲SOCKET看作一条数据流的入口,往里面放数据就是了,TCP协议本身会进行拥塞/流量控制。
五、UDP(传输层)
UDP则与TCP不同,UDP包头内有总长度字段,同样为两个字节,因此UDP数据包的总长度被限制为65535,这样恰好可以放进一个IP包内,使得 UDP/IP协议栈的实现非常简单和高效。
所以UDP包的最大值是:IP数据包的最大长度65535-IP头的大小20-UDP头的大小=65507字节。最小值是0。
这个值也就是你在调用getsockopt()时指定SO_MAX_MSG_SIZE所得到返回值,任何使用SOCK_DGRAM属性的socket,一次send的 数据都不能超过这个值,否则必然得到一个错误。
———————————————————————————
在应用程序中我们用到的Data的长度最大是多少,直接取决于底层的限制。
我们从下到上分析一下:
1.在链路层,由以太网的物理特性决定了数据帧的长度为(46+18)-(1500+18),其中的18是数据帧的头和尾,也就是说数据帧的内容最大为1500(不包括帧头和帧尾),即MTU(Maximum Transmission Unit)为1500;
2.在网络层,因为IP包的首部要占用20字节,所以这的MTU为1500-20=1480;
3.在传输层:
UDP包的首部要占用8字节,所以UDP的MTU为1480-8=1472;
所以,在应用层,你的Data最大长度为1472。 (当我们的UDP包中的数据多于MTU(1472)时,发送方的IP层需要分片fragmentation进行传输,而在接收方IP层则需要进行数据报重组,由于UDP是不可靠的传输协议,如果分片丢失导致重组失败,将导致UDP数据包被丢弃)。
从上面的分析来看,在普通的局域网环境下,UDP的数据最大为1472字节最好(避免分片重组)。
但在网络编程中,Internet中的路由器可能有设置成不同的值(小于默认值),Internet上的标准MTU值为576,所以Internet的UDP编程时数据长度最好在576-20-8=548字节以内。
TCP包的首部要占用20字节,所以TCP的MTU为1480-20=1460;
4.如果用户使用了PPPoE协议,还应减去PPPoE的首部占用需要的8字节。即TCP的MTU应该是1452;UDP的MTU应该是1464;
1 |
const unsigned long kMinEthernetFrameSize = 64; |
2 |
const unsigned long kMaxEthernetFrameSize = 1518; |
3 |
const unsigned long kEthernetFrameHeadSize = 6 + 6 + 2; |
4 |
const unsigned long kEthernetFrameTailSize = 4; |
5 |
const unsigned long kEthernetMTU = kMaxEthernetFrameSize - kEthernetFrameHeadSize - kEthernetFrameTailSize; |
6 |
const unsigned long kPPPoEHeadSize = 8; |
7 |
const unsigned long kIPHeadSize = 20; |
8 |
const unsigned long kTCPHeadSize = 20; |
9 |
const unsigned long kUDPHeadSize = 8; |
10 |
const unsigned long kTCPMSS = kEthernetMTU - kPPPoEHeadSize - kIPHeadSize - kTCPHeadSize; |
11 |
const unsigned long kUDPMSS = kEthernetMTU - kPPPoEHeadSize - kIPHeadSize - kUDPHeadSize; |
———————————————————————————
参考:
http://blog.csdn.net/naturebe/article/details/6712153
http://blog.csdn.net/ixidof/article/details/7669422
https://www.frozentux.net/iptables-tutorial/iptables-tutorial.html