四层:链路层,网络层,传输层,应用层。
其中以太网(Ethernet)的数据帧在链路层
IP包在网络层
TCP或UDP包在传输层
TCP或UDP中的数据(Data)在应用层
关系:数据帧{IP包{TCP或UDP段{Data}}}
在应用程序中的Data的长度最大是多少,直接取决于底层的限制。
从下到上分析一下:
1、在链路层,由以太网的物理特性决定了数据帧的长度为(46+18)-(1500+18),其中的18是数据帧的头和尾,也就是说数据帧的内容最大为1500(不包括帧头和帧尾),即MTU(Maximum Transmission Unit,最大传输单元)为1500;
2、在网络层,因为IP包的首部要占用20字节,所以MTU为1500-20=1480;
3、在传输层,对于UDP包的首部要占用8字节,所以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字节以内。
IP数据包的最大长度是64K字节(65535),因为在IP包头中用2个字节描述报文长度,2个字节所能表达的最大数字就是65535。
由于IP协议提供为上层协议分割和重组报文的功能,因此传输层协议的数据包长度原则上来说没有限制。实际上限制还是有的,因为IP包的标识字段(用于重组IP报文)终究不可能无限长,按照IPv4,好像上限应该是4G(64K*64K)。依靠这种机制,TCP包头中就没有“包长度”字段,而完全依靠IP层去处理分帧。这就是为什么TCP常常被称作一种“流协议”的原因,开发者在使用TCP服务的时候,不必去关心数据包的大小,只需讲SOCKET看作一条数据流的入口,往里面放数据就是了,TCP协议本身会进行拥塞/流量控制。
UDP则与TCP不同,UDP包头内有总长度字段,同样为两个字节,因此UDP数据包的总长度被限制为65535,这样恰好可以放进一个IP包内,使得UDP/IP协议栈的实现非常简单和高效。65535再减去UDP头本身所占据的8个字节,UDP服务中的最大有效载荷长度仅为65527。这个值也就是你在调用getsockopt()时指定SO_MAX_MSG_SIZE所得到返回值,任何使用SOCK_DGRAM属性的socket,一次send的数据都不能超过这个值,否则必然得到一个错误。
那么,IP包提交给下层协议时将会得到怎样的处理呢?这就取决于数据链路层协议了,一般的数据链路层协议都会负责将IP包分割成更小的帧,然后在目的端重组它。在EtherNet上,数据链路帧的大小如以上几位大侠所言。而如果是IP over ATM,则IP包将被切分成一个一个的ATM Cell,大小为53字节。
TCP提供的是一种面向连接的,可靠的字节流服务,TCP提供可靠性的一种重要的方式就是MSS(maximum segment size,最大分节大小)。通过MSS,应用数据被分割成TCP认为最适合发送的数据块,由TCP传递给IP的信息单位称为报文段或段(segment)。代表一个TCP socket的结构体struct tcp_sock中有多个成员用于确定应用数据被分割成最大为多大的数据块较为合适(最大报文段长度MSS)。
最大报文段长度最为相关的一个参数是网络设备接口的MTU,以太网的MTU是1500,基本IP首部长度为20,TCP首部是20,所以MSS的值可达1460(MSS不包括协议首部,只包含应用数据)。
TCP三次握手协议中,通讯的双方都通过TCP选项通告了自己期望接收的MSS值,该值直接来源于struct tcp_sock的成员advmss,而这个值直接取自于网络设备接口的MTU减去IP首部和TCP首部的长度。在本地以太网中可达1460(如果首部都不含选项的话)。而成员rx_opt是一个结构体struct tcp_options_received,它记录的是来自对端的TCP选项通告,其成员mss_clamp表示mss的上限值,其来源就是对端的MSS通告,而mss_user是用户设置的mss,其优先级最高,如果有user_mss,则使用user_mss,忽略其它。
MSS是TCP里的一个概念(首部的选项字段中)。MSS是TCP数据包每次能够传输的最大数据分段,TCP报文段的长度大于MSS时,要进行分段传输。TCP协议在建立连接的时候通常要协商双方的MSS值,每一方都有用于通告它期望接收的MSS选项(MSS选项只出现在SYN报文段中,即TCP三次握手的前两次)。MSS的值一般为MTU值减去两个首部大小(需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)所以如果用链路层以太网,MSS的值往往为1460。而Internet上标准的MTU(最小的MTU,链路层网络为x2。5时)为576,那么如果不设置,则MSS的默认值就为536个字节。很多时候,MSS的值最好取512的倍数。TCP报文段的分段与重组是在运输层完成的。
MTU = MSS + TCP Header + IP Header。
分组可以发生在运输层和网络层,运输层中的TCP会分段,网络层中的IP会分片。IP层的分片更多的是为运输层的UDP服务的,由于TCP自己会避免IP的分片,所以使用TCP传输在IP层都不会发生分片的现象。
TCP报文段如果很长的话,会在发送时发生分段,在接受时进行重组,同样IP数据报在长度超过一定值时也会发生分片,在接收端再将分片重组。
由于UDP数据报不会自己进行分段,因此当长度超过了MTU时,会在网络层进行IP分片。同样,ICMP(在网络层中)同样会出现IP分片情况。
IP数据报分片后,只有第一片带有UDP首部或ICMP首部,其余的分片只有IP头部,到了端点后根据IP头部中的信息再网络层进行重组。而TCP报文段的每个分段中都有TCP首部,到了端点后根据TCP首部的信息在传输层进行重组。IP数据报分片后,只有到达目的地后才进行重组,而不是向其他网络协议,在下一站就要进行重组。
对IP分片的数据报来说,即使只丢失一片数据也要重新传整个数据报(既然有重传,说明运输层使用的是具有重传功能的协议,如TCP协议)。这是因为IP层本身没有超时重传机制------由更高层(比如TCP)来负责超时和重传。当来自TCP报文段的某一段(在IP数据报的某一片中)丢失后,TCP在超时后会重发整个TCP报文段,该报文段对应于一份IP数据报(可能有多个IP分片),没有办法只重传数据报中的一个数据分片。
拥塞控制:
1、慢启动:当新建连接时,cwnd初始化为1个最大报文段(MSS)大小,发送端开始按照拥塞窗口大小发送数据,每当有一个报文段被确认,cwnd就增加1个MSS大小,这样cwnd的值就随着网络往返时间(Round Trip Time,RTT)呈指数级增长。1 2 4 8,窗口越大增长越快。
2、拥塞窗口: 拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口。如再考虑到接收方的接收能力,则发送窗口还可能小于拥塞窗口。当cwnd达到慢启动门限值ssthresh(8)时,窗口中所有的报文段都被确认时,cwnd的大小加1,cwnd的值就随着RTT开始线性增加(8 9 10 11 12,增长速率和窗口大小无关),这样就可以避免增长过快导致网络拥塞,慢慢的增加调整到网络的最佳值。
乘法减小: 是指不论在慢开始阶段还是拥塞避免阶段,只要出现一次超时(即出现一次网络拥塞),就把慢启动门限值ssthresh设置为当前的拥塞窗口值的一半(12拥塞,设为6)。
加法增大: 是指执行拥塞避免算法后,在收到对所有报文段的确认后(即经过一个往返时间),就把拥塞窗口cwnd增加一个MSS 大小(TCP Reno版本:6 7 8 9),使拥塞窗口缓慢增大,以防止网络过早出现拥塞。
3、快重传: 快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认,这样做可以让发送方及早知道有报文段没有到达接收方,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段。
4、快恢复:当发送端收到连续三个重复的确认时,就执行“乘法减小”算法,把慢启动门限ssthresh和发送窗口减半,但接下去不执行慢开始算法(TCP Reno版本)。
路由器拥塞控制队列
网络中路由器转发队列通常实现了Random Early Dectection(RED)功能,路由器根据当前转发队列的平均长度来做丢包策略,并且随机丢弃一些TCP流量报文,而不是等待队列溢出丢弃全部报文,使一部分连接放缓,保证一部分的正常。
ECN(显式拥塞通知)设计
通过TCP发送端和接收端及中间路由器的配合,感知路径的拥塞,主动减缓TCP的发送速率,在中间路由器的转发队列上,对超过平均长度的TCP报文进行ECN标记,不再丢弃报文。
IP首部的TOS字段中第7和第8比特位为ECN字段,00不支持,路由器在发生拥塞时置位11
滑动窗口:
时间轴数据分类:
1、已发送已确认,发送方无需缓存
2、已发送未确认,发送需缓存
3、可发送且未发送
4、不可发送且未发送
tcp send函数的阻塞和非阻塞
tcp协议本身是可靠的,并不等于应用程序用tcp发送数据就一定是可靠的.不管是否阻塞,send发送的大小,并不代表对端recv到多少的数据.
在阻塞模式下, send函数的过程是将应用程序请求发送的数据拷贝到发送缓存中发送就返回.但由于发送缓存的存在,表现为:如果发送缓存大小比请求发送的大小要大,那么send函数立即返回,同时向网络中发送数据;否则,send会等待接收端对之前发送数据的确认,以便腾出缓存空间容纳新的待发送数据,再返回(接收端协议栈只要将数据收到接收缓存中,就会确认,并不一定要等待应用程序调用recv),如果一直没有空间能容纳待发送的数据,则一直阻塞;
在非阻塞模式下,send函数的过程仅仅是将数据拷贝到协议栈的缓存区而已,如果缓存区可用空间不够,则尽能力的拷贝,立即返回成功拷贝的大小;如缓存区可用空间为0,则返回-1,同时设置errno为EAGAIN.
TCP段各个字段说明:
源端口和目的端口: 各占 2 字节。端口是传输层与应用层的服务接口。传输层的复用和分用功能都要通过端口才能实现
序号: 占 4 字节。TCP 连接中传送的数据流中的每一个字节都编上一个序号。序号字段的值则指的是本报文段所发送的数据的第一个字节的序号
确认号: 占 4 字节,是期望收到对方的下一个报文段的数据的第一个字节的序号
首部长度: 占 4 位,它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远。“数据偏移”的单位是 32 位字(以 4 字节为计算单位)
保留: 占 4 位,保留为今后使用,但目前应置为 0
CWR: 拥塞窗口减少标志,发送方设置,发送端收到ECE为1时置位1,发送速率减半
ECE: 结合IP头中的ECN标志,在三次握手时表明TCP端支持ECN功能,接收端收到ECN标记11时置位1,通知发送端减速
紧急URG: 当 URG=1 时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据)
确认ACK: 只有当 ACK=1 时确认号字段才有效。当 ACK=0 时,确认号无效
PSH(PuSH):接收 TCP 收到 PSH = 1 的报文段,就尽快地交付接收应用进程,而不再等到整个缓存都填满了后再向上交付
RST (ReSeT):当 RST=1 时,表明 TCP 连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接
同步 SYN: 同步 SYN = 1 表示这是一个连接请求或连接接收报文
终止 FIN: 用来释放一个连接,FIN=1 表明此报文段的发送端的数据已发送完毕,并要求释放运输连接
窗口大小:接收数据的窗口大小
检验和: 占 2 字节。检验和字段检验的范围包括首部和数据这两部分。在计算检验和时,要在 TCP 报文段的前面加上 12 字节的伪首部(源IP4+目的IP4+保留1+协议号1+长度2)
紧急指针: 占 16 位,指出在本报文段中紧急数据共有多少个字节(紧急数据放在本报文段数据的最前面)
选项: 长度可变。TCP 最初只规定了一种选项,即最大报文段长度 MSS。MSS 告诉对方 TCP:“我的缓存所能接收的报文段的数据字段的最大长度是 MSS 个字节。” [MSS(Maximum Segment Size)是 TCP 报文段中的数据字段的最大长度。数据字段加上 TCP 首部才等于整个的 TCP 报文段]
填充: 这是为了使整个首部长度是 4 字节的整数倍
其他选项:
最大分段大小:MMS,建立连接时通知对方本端最大支持的分段大小
窗口扩大: 占 3 字节,其中有一个字节表示移位值 S。新的窗口值等于TCP 首部中的窗口位数增大到(16 + S),相当于把窗口值向左移动 S 位后获得实际的窗口大小
时间戳: 占10 字节,其中最主要的字段时间戳值字段(4字节)和时间戳回送回答字段(4字节)
选择确认: 接收方收到了和前面的字节流不连续的两2字节。如果这些字节的序号都在接收窗口之内,那么接收方就先收下这些数据,但要把这些信息准确地告诉发送方,使发送方不要再重复发送这些已收到的数据。
UDP数据报各个字段说明:
源端口号和目的端口号分表表示了发送进程和接收进程
UDP长度字段包括了UDP首部和UDP数据的字节长度
UDP检验和覆盖了UDP首部和UDP数据(IP首部检验和只覆盖了IP首部,不覆盖数据报中的任何数据)
UDP数据报的长度可以为奇数字节,但是检验和算法是把若干个16bit字相加。解决方法是必要时在最后增加填充字节0,这只是为了检验和的计算。UDP数据报和TCP段都包含一个12字节长的伪首部,它是为了计算检验和而设置的。伪首部包含IP首部一些字段。
IP数据包各个字段说明:
版本号(Version):长度4比特。标识目前采用的IP协议的版本号。一般的值为0100(IPv4),0110(IPv6)
IP包头长度(Header Length):长度4比特。这个字段的作用是为了描述IP包头的长度,因为在IP包头中有变长的可选部分。该部分占4个bit位,单位为32bit(4个字节),即本区域值= IP头部长度(单位为bit)/(84),因此,一个IP包头的长度最长为“1111”,即154=60个字节。IP包头最小长度为20字节。
服务类型(Type of Service):长度8比特。8位 按位被如下定义 PPP DTRC0
PPP:定义包的优先级,取值越大数据越重要
000 普通 (Routine)
001 优先的 (Priority)
010 立即的发送 (Immediate)
011 闪电式的 (Flash)
100 比闪电还闪电式的 (Flash Override)
101 CRI/TIC/ECP(找不到这个词的翻译)
110 网间控制 (Internetwork Control)
111 网络控制 (Network Control)
D 时延: 0:普通 1:延迟尽量小
T 吞吐量: 0:普通 1:流量尽量大
R 可靠性: 0:普通 1:可靠性尽量大
M 传输成本: 0:普通 1:成本尽量小
0 最后一位被保留,恒定为0
IP包总长(Total Length):长度16比特。 以字节为单位计算的IP包的长度 (包括头部和数据),所以IP包最大长度65535字节。
标识符(Identifier):长度16比特。该字段和Flags和Fragment Offest字段联合使用,对较大的上层数据包进行分段(fragment)操作。路由器将一个包拆分后,所有拆分开的小包被标记相同的值,以便目的端设备能够区分哪个包属于被拆分开的包的一部分。
标记(Flags):长度3比特。该字段第一位不使用。第二位是DF(Don’t Fragment)位,DF位设为1时表明路由器不能对该上层数据包分段。如果一个上层数据包无法在不分段的情况下进行转发,则路由器会丢弃该上层数据包并返回一个错误信息。第三位是MF(More Fragments)位,当路由器对一个上层数据包分段,则路由器会在除了最后一个分段的IP包的包头中将MF位设为1。
片偏移(Fragment Offset):长度13比特 * 8。表示该IP包在该组分片包中位置,接收端靠此来组装还原IP包。
生存时间(TTL):长度8比特。当IP包进行传送时,先会对该字段赋予某个特定的值。当IP包经过每一个沿途的路由器的时候,每个沿途的路由器会将IP包的TTL值减少1。如果TTL减少为0,则该IP包会被丢弃。这个字段可以防止由于路由环路而导致IP包在网络中不停被转发。
协议(Protocol):长度8比特。标识了上层所使用的协议。
以下是比较常用的协议号:
1 ICMP
2 IGMP
6 TCP
17 UDP
88 IGRP
89 OSPF
头部校验(Header Checksum):长度16位。用来做IP头部的正确性检测,但不包含数据部分。 因为每个路由器要改变TTL的值,所以路由器会为每个通过的数据包重新计算这个值。
源和目标地址(Source and Destination Addresses):这两个地段都是32比特。标识了这个IP包的起源和目标地址。要注意除非使用NAT,否则整个传输的过程中,这两个地址不会改变。
可选项(Options):这是一个可变长的字段。该字段属于可选项,主要用于测试,由源设备根据需要改写。可选项目包含以下内容:
松散源路由(Loose source routing):给出一连串路由器接口的IP地址。IP包必须沿着这些IP地址传送,但是允许在相继的两个IP地址之间跳过多个路由器。
严格源路由(Strict source routing):给出一连串路由器接口的IP地址。IP包必须沿着这些IP地址传送,如果下一跳不在IP地址表中则表示发生错误。
路由记录(Record route):当IP包离开每个路由器的时候记录路由器的出站接口的IP地址。
时间戳(Timestamps):当IP包离开每个路由器的时候记录时间。
填充(Padding):因为IP包头长度(Header Length)部分的单位为32bit,所以IP包头的长度必须为32bit的整数倍。因此,在可选项后面,IP协议会填充若干个0,以达到32bit的整数倍。