说在前面
- 环境: ubuntu16.04、WSL
- 参考: UNIX网络编程
- 说明:本文侧重于记录协议中涉及的数据大小限制以及数据在协议栈中的传输过程;TCP等协议具体内容网上资源较多,不再记录。
首部格式
- IPv4
- IPv6
- TCP\UDP\TSCP暂不记录
缓冲区大小及限制
-
IP数据报相关
-
IPv4数据报最大大小为65535,包含40字节首部。
IPv6数据报最大大小为65535+40。首部40字节,净载荷最大为65535。 -
MTU(maximum transmission unit,最大传输单元)。参见百度百科
IPv4要求的最小链路MTU为68字节,即20字节固定长度首部+最多40字节选项部分+最小8字节的数据。
IPv6要求的最小链路MTU为1280字节(但是可以运行在小于1280的链路上,需要链路的分片和重组功能?)。 -
在两个主机之间的通信路径上可能会有多个网络,这些网络中的最小MTU称为路径MTU(path MTU)。主机A→主机B与主机B→主机A之间的路径可能不同,那么路径MTU也可能不同。
-
IP数据报向下传输给链路层时,若其大小大于MTU,那么IPv4和IPv6将执行分片。
IPv4主机对其产生的数据报执行分片,IPv4路由器对其转发的数据报执行分片;
IPv6主机对其产生的数据报执行分片,IPv6路由器不对其转发的数据报执行分片(但是IPv6路由器产生的数据报将被执行分片)。 -
IPv4首部的"不分片"(don’t fragment,即DF)位若被设置,数据报不会被分片。路由器接收到设置了DF位且超过链路MTU的IPv4数据报时,将产生ICMPv4错误"destination unreachable, fragment needed but DF bit set"。
DF位可用于路径MTU发现,如果基于IPv4的TCP使用该技术,将发送的所有数据报文设置DF位,若中间某个路由返回上述ICMP错误,TCP就减少所有数据报数据量。(路径MTU发现方法不唯一) -
IPv4和IPv6都定义了最小重组缓冲区大小(minimum reassembly buffer size),是IPv4或者IPv6的任何实现都必须保证支持的最小数据报大小。对于IPv4,576字节;对于IPv6,1500字节。(如果TCP数据只有一字节,那么IP数据报会怎样处理?填充吗?)
补充:这里我用了wireshark抓了上一节中getdaytime的数据包(局域网环境、两台机器),并没有满足这个576字节的要求啊,所以这个576是啥意思?
-
TCP有一个MSS(maximum segment size,最大分节大小),用于向对端通告每个分节中能发送的最大TCP数据量。MSS经常被设置成MTU减去IP和TCP的固定长度。
-
-
TCP输出
每一个TCP套接字都有一个发送缓冲区,可以使用SO_SNDBUF套接字选项来更改该缓冲区的大小。- 当某个应用进程调用write时,内核从该进程的缓冲区复制所有数据到该套接字的发送缓冲区。
若进程数据量大于发送缓冲区(剩余可容纳)大小,该进程进入睡眠(阻塞情况下)。此时,内核会将发送缓冲区中的数据转发,这样就有可用空间,一旦有可用空间,内核继续向发送缓冲区填入剩余的进程数据,直到进程缓冲区所有数据都复制。最后write返回。
write调用返回并不代表数据已经发送或者到达对端,仅表示我们可以重新使用原来的进程缓冲区。 - 在套接字发送缓冲区的数据在发送后并不会立即被丢弃,而是在对端对应的ACK到达后才会被丢弃。
- TCP以MSS大小或者更小的块将数据发给IP(加上TCP首部),MSS是由对端通告的值或536(对端未设置MSS,536=IPv4最小重组缓冲区字节数576-IPv4首部字节数20-TCP首部字节数20)。
- IP可能执行分片(IP报文大小,包括首部,大于MTU)。
- 每个数据链路有一个输出队列,若队列满,新的分组将被丢弃,并沿协议栈向上返回一个错误: 数据链路→IP→TCP。TCP处理该错误,重传被丢弃的分节。
- 当某个应用进程调用write时,内核从该进程的缓冲区复制所有数据到该套接字的发送缓冲区。
-
UDP输出
任何UDP套接字都有缓冲区大小(SO_SNDBUF),但是UDP套接字缓冲区实际上并不存在。缓冲区大小仅表示写到该套接字的UDP数据报大小上限。- 若一个应用进程写一个大于缓冲区大小的数据报,内核将返回一个EMSGSIZE错误。UDP是不可靠的,内核不需要保存数据的副本,因此不需要设置发送缓冲区。(内核会将进程数据复制到某个内核缓冲区,在数据被发送后,这个副本就被数据链路层丢弃)。
UDP套接字write成功返回表示数据报或者所有片段已经加入到链路层输出队列;若链路队列没有足够空间,内核将返回一个ENOBUFS错误(有些UDP实现不返回) - UDP添加首部,传递给IP。
- IP添加首部,确定是否执行分片,然后将其加入数据链路队列。
- 若一个应用进程写一个大于缓冲区大小的数据报,内核将返回一个EMSGSIZE错误。UDP是不可靠的,内核不需要保存数据的副本,因此不需要设置发送缓冲区。(内核会将进程数据复制到某个内核缓冲区,在数据被发送后,这个副本就被数据链路层丢弃)。
-
SCTP输出
SCTP与TCP类似。- 从write到SCTP的过程与TCP相似。
- SCTP是面向消息/报文(messages)的。报文数据首先会被转换成SCTP数据块(SCTP DATA chunks)。若报文数据量过小,那么会使用数据块首部将其填充至块大小;若报文数据量过大,报文将会被分割为合适的大小并填充数据块首部。通常决定是否需要分割的阈值为路径MTU。
有序报文被分割并转换为数据块后,所有相关数据块首部Stream Sequence Number(SSN)域会设置为一个相同的数,表示它们组成同一个报文。无序报文忽略SSN域。
每个数据块的首部有一个Transmission Sequence N(TSN)域,表示数据块的顺序(与TCP的Sequence Number相似)。注意,大数据报文被分割后TSN顺序应该与分割时的顺序相同。(比如 报文被分成1、2、3块,那么对应的TSN应该为x+1、x+2、x+3)
SCTP将SCTP包传递给IP层。SCTP包包含一个公共头部以及一个或者多个SCTP块(数据块即用户数据报文形成的块;控制块用于控制和维护SCTP连接)。
在SCTP层,通常由数据块队列以及控制块队列。当存在控制块的时候,SCTP会优先填充控制块,而后再考虑数据块。也就是说,数据块一定是放在控制块后面的。
为了保证传输效率,SCTP会在保证包大小不超过路径MTU的前提下尽可能地包含更多的数据块以及控制块。 - 本端SCTP必须等待SACK,在累计确定超过已发送的数据后,才可以从套接字缓冲区中删除对应数据。