1. 以太帧
1.1. 帧格式
- MTU内容为空时,填充位会填满,即46个字节,大小为:6 6 2 0 46 4 = 64;
- MTU为1500时,填充位为空,大小为:6 6 2 1500 0 4 = 1518;
- 还有其他的格式帧,但用的比较少;
- 所有的网络设备都需要支持以太帧格式;
- 目的地址、源地址都是指的MAC地址;
- 可能存在分片的情况;
1.2. MTU
MTU即Max Transfer Unit,最大传输单元,那么为什么MTU是1500呢?
以太网帧是传输中的最小可识别单元,再往下就是0101所对应的光信号,所以一条带宽同时只能发送一个以太网帧。如果同时发送多个,那么对端就无法重组成一个以太网帧。
1.2.1. 设置过大
假设MTU设置为65535,在100Mbps
的带宽中(假设中间没有损耗),我们计算一下发送这一帧需要的时间:
( 65553 * 8 ) / ( 100 * 1024 * 1024 ) ≈ 0.005(s)
65553 = 65535 6 6 2 4
在100M网络下传输一帧就需要5ms,也就是说这5ms其他进程发送不了任何数据,如果是早先的电话拨号,网速只有2M的情况下:
代码语言:txt复制( 65553 * 8 ) / ( 2 * 1024 * 1024 ) ≈ 0.100(s)
100ms内无法发送其他数据,简直不能接受。
1.2.2. 设置过小
假设MTU值设置为100,那么单个帧传输的时间,在2Mbps带宽下需要:
代码语言:txt复制( 100 * 8 ) / ( 2 * 1024 * 1024 ) * 1000 ≈ 5(ms)
时间上已经能接受了,问题在于,不管MTU设置为多少,以太网头帧尾大小是固定的,都是14 4,所以在MTU为100的时候,一个以太网帧的传输效率为:
代码语言:txt复制( 100 - 14 - 4 ) / 100 = 82%
写成公式就是:( T - 14 - 4 ) / T
,当T趋于无穷大的时候,效率接近100%
,也就是MTU的值越大,传输效率最高,但是基于上一点传输时间的问题,来个折中的选择,既然头加尾是18,那就凑个整来个1500,总大小就是1518,传输效率:
1500 / 1518 = 98.8%
100Mbps传输时间:
代码语言:txt复制( 1518 * 8 ) / ( 100 * 1024 * 1024 ) * 1000 = 0.11(ms)
2Mbps传输时间:
代码语言:txt复制( 1518 * 8 ) / ( 2 * 1024 * 1024 ) * 1000 = 5.79(ms)
总体上时间都还能接受。
1.2.3. 127.0.0.1
其实在网卡都会有MTU的显示,默认一般是1500,但127.0.0.1一般是65535:
代码语言:shell复制docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 192.168.10.1 netmask 255.255.255.0 broadcast 192.168.10.255
ether 02:42:75:79:7f:39 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet xxxxx netmask 255.255.252.0 broadcast xxxxx
ether 52:54:00:b2:bf:fa txqueuelen 1000 (Ethernet)
RX packets 6407630 bytes 3040763484 (2.8 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 5590594 bytes 772489409 (736.7 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 0 (Local Loopback)
RX packets 2410 bytes 125460 (122.5 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2410 bytes 125460 (122.5 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
2. RFC
RFC:Request For Comments(RFC),是一系列以编号排定的文件,基本的互联网通信协议都有在RFC文件内详细说明。地址:
https://www.rfc-editor.org/rfc,如果访问速度慢,可以使用国内的一个镜像:http://mirrors.nju.edu.cn/rfc/inline-errata/ 里面有各种协议的文档,(看名字来源于南京大学,但是不全)。
协议 | 编号 | 说明 |
---|---|---|
IP | 791 | |
IPV6 | 2460 | |
TCP | 793 | |
UDP | 768 | |
ICMP | 777、792 | |
DNS | 1035 | |
FTP | 959 | |
SNMP | 1067、1098、1157 | |
HTTP1.0 | 1945 | |
HTTP1.1 | 2616、2617 | |
NAT | 1631 | rfc3022淘汰了rfc1631 |
3. IP报文
3.1. IPv4格式
http://mirrors.nju.edu.cn/rfc/inline-errata/rfc791.html
代码语言:txt复制 0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1...
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|Version| IHL |Type of Service| Total Length |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Identification |Flags| Fragment Offset |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Time to Live | Protocol | Header Checksum |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Source Address |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Destination Address |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Options | Padding |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Example Internet Datagram Header
整个字段描述是4字节对齐的,每行代表4个字节。
- Version:版本,4表示IPv4;
- IHL:报文首部长度,IP Header Length;
- Type Of Service:服务类型,用于指示服务质量;
- Total Length:总长度,含报文首部和数据部分;
- Identification:标识,发送方分配,用于标识数据包,具有相同标识字段的分片报文会被接收方重组成一个完整的数据包;
- Flags:标志,3bit,bit0为预留,bit1为(DF,Don't Fragment,不分片),0表示可能分片,1表示不分片,bit2(MF,More Fragment,更多分片)0表示最后的分片,即没有更多分片,1表示会有更多的分片;
- Fragment Offset:片偏移,13bit,表示该IP包在分片前的原始IP包中的位置,单位是8字节;
- Time to Live:TTL,生存时间,每经过一个网络设备,这个值-1,如果到0,则该报文丢弃,因为TTL的变化,需要网络设备重新计算校验和;
- Protocol:协议,即数据部分的协议,例如:TCP、UDP等;
- Header Checksum:头部校验和;
- Source Address:源地址,4字节(IPv4);
- Destination Address:目的地址,4字节(IPv4);
- Options:可选字段,长度可变,例如有的命令会在每个报文中加入经过的IP;
- Padding:填充,需要4字节对齐;
除可选字段外,IP头总长度为20字节。
3.2. IPv6格式
https://www.rfc-editor.org/rfc/rfc2460
代码语言:txt复制 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|Version| Traffic Class | Flow Label |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Payload Length | Next Header | Hop Limit |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| |
| |
Source Address
| |
| |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| |
| |
Destination Address
| |
| |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IPv6协议会有多个头部,包括一个基础头部和一个扩展头部,下面是基础头部的字段:
- Version:4字节,IP协议版本,此处为6;
- Traffic Class:8字节,流量类别,类似于IPv4中的Type Of Service,一般用于对报文进行差异化控制;
- Flow Label :20字节,流标签,用于指示路由器经过时做一些操作;
- Payload Length:16字节,数据段长度;
- Next Header:存在两种情况,第一种是没有后续的扩展头部,则表示协议,与IPv4中的协议字段一致(TCP、UDP),否则表示下一个头部的类型;
- Hop Limit:类似于TTL,8 位无符号整数, 每个转发数据包的节点减 1。 如果 Hop Limit 减少到零,则丢弃该数据包;
- Source Address:源地址,16字节;
- Destination Address:目的地址,16字节;
对于IPv6的数据而言,其一般包括扩展头部和数据两部分,数据即最终的TCP/UDP部分,但扩展头部本身格式是采用了一种链式的处理关系,即每个头部中可以包含下一个头部的类型,而下一个头部则可以包含下下个头部的类型,最终到达TCP或UDP数据部分。例如下面这三个例子:
代码语言:txt复制 --------------- ------------------------
| IPv6 header | TCP header data
| |
| Next Header = |
| TCP |
--------------- ------------------------
--------------- ---------------- ------------------------
| IPv6 header | Routing header | TCP header data
| | |
| Next Header = | Next Header = |
| Routing | TCP |
--------------- ---------------- ------------------------
--------------- ---------------- ----------------- -----------------
| IPv6 header | Routing header | Fragment header | fragment of TCP
| | | | header data
| Next Header = | Next Header = | Next Header = |
| Routing | Fragment | TCP |
--------------- ---------------- ----------------- -----------------
4. TCP报文格式
http://mirrors.nju.edu.cn/rfc/inline-errata/rfc793.html
4.1. 分层关系
代码语言:txt复制 ------ ----- ----- -----
|Telnet| | FTP | |Voice| ... | | Application Level
------ ----- ----- -----
| | | |
----- ----- -----
| TCP | | RTP | ... | | Host Level
----- ----- -----
| | |
-------------------------------
| Internet Protocol & ICMP | Gateway Level
-------------------------------
|
---------------------------
| Local Network Protocol | Network Level
---------------------------
Protocol Relationships
4.2. 格式
代码语言:txt复制 0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1...
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Source Port | Destination Port |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Sequence Number |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Acknowledgment Number |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Checksum | Urgent Pointer |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Options | Padding |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| data |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Source Port:源端口,2字节,这就是为什么TCP协议中端口最多有65535个;
- Destination Port:目的端口;
- Sequence Number:序号,TCP中传输的数据流中每个字节都会有一个编号,序号字段的值指的是本报文段所发送的数据的第一个字节所在整个数据流的编号;
- Acknowledgment Number:确认号,表示期望收到对方的下一个报文段的数据的第1个字节的序号,也可以描述为上次已经成功接收到的数据字节序号 1,只有ACK标识为1,该值才有效;
- Data Offset:数据开始的位置,这里的数据指的是data,就是要发送的具体内容,需要注意的是,TCP头也是4字节对齐的,也就是data一定是4字节的倍数开始;
- Reserved:预留;
- URG:1bit,紧急指针标志,1表示紧急指针有效;
- ACK:1bit,ACK标志,1表示确认号有效,0表示报文中不含有确认信息,忽略确认号字段;
- PSH:1bit,PUSH标志,1表示带有push标志的数据,指示接收方收到该数据后,需要尽快将报文交给应用程序;
- RST:1bit,重建连接标志,1表示TCP连接中出现严重错误,例如程序挂了,则会由内核TCP协议栈发送RST报文至对端,表示必须要释放连接,后续根据情况重建;
- SYN:1bit,同步序号标识,用来发起一个连接,1表示是syn报文;
- FIN:1bit,finish标识,用于释放连接,1表示发送方已经没有数据发送了,可以关闭本方连接;
- Window:滑动窗口大小,描述的是本方的拥塞情况;
- Checksum:校验和;
- Urgent Pointer:紧急指针,在URG为1时有效,具体不是很清楚做什么;
- Options:可选字段;
- Padding:填充;
- data:具体数据;
TCP报文头最少占用20个字节。
4.3. Options
Options为可选字段。
4.3.1. MSS
MSS即Max Segment Size,它指明本段可以接受的最大TCP分段的长度(Payload,不含TCP Header),也就是说,对端发送的每个分段的长度都不应该大于MSS(单位Byte)。
MSS占用两个字节,所以其最大值可以为65535。
但一般而言:MSS = MTU(1500) - IP Header(20) - TCP Header(20) = 1460;
这个值是在三次握手时明确的,是发送至对端,且在握手时发送(据说在某些其他场景下也会更新)。
它的格式:
代码语言:txt复制 -------- -------- --------- --------
|00000010|00000100| max seg size |
-------- -------- --------- --------
Kind=2 Length=4
因为是Option,所以允许服务器不发送给值,假设不发送的话,对端会将该值设置为:536Bytes。
4.3.2. Window Scale
Window Size的乘数因子,为了放大滑动窗口计算使用。
这个值是在三次握手时明确的,是发送至对端,且在握手时发送(据说在某些其他场景下也会更新)。
它的格式:
代码语言:txt复制 -------- -------- --------- --------
|00000011|00000011| shift count |
-------- -------- --------- --------
Kind=3 Length=3
shift count是2的n次方中的n,例如7表示要放大128倍。
5. TCP三次握手
5.1. 三次握手过程
客户端状态变化:
- 用户调用connect()后,内核会发送syn报文后,变为SYN_SENT状态;
- 内核收到SYN ACK报文后,变为ESTABLISHED状态;
- 用户后面就可以通过write()调用发送数据;
服务端状态变化:
- 默认是LISTEN状态,此时是节点启动监听后的状态;
- 收到客户端发送的SYN报文后,会变为SYN_RCVD状态;
- 收到客户端的ACK报文后,变为ESTABLISHED状态;
5.2. 丢包咋办
5.2.1. 客户端SYN包丢失
Client端发现自己没有收到ACK,会周期性重传SYN包,直到收到Server端的SYN ACK。
5.2.2. 服务端回复的SYN ACK丢失
Server端发现自己没有收到ACK,会周期性重传SYN ACK包,直到收到Client端的ACK。
5.2.3. 客户端回复的ACK丢失
此时,对于Client而言,其已经变为了ESTABLISHED状态,但Sever端目前并不是ESTABLISHED状态。会存在下面这三种情况:
- 假设两端都没有开始发送具体的数据,那么Server会周期性重传SYN ACK包,直到收到Client的ack报文;
- 假设此时Client开始发送具体的数据,Server收到了Client的数据包(包中也会有ACK),那么就会切换到ESTABLISHED状态,后续正常处理即可;
- 假设Server需要发送具体的数据,但明显此时无法发送,则会一直周期性的重传SYN ACK,直到收到Client的确认报文;
5.3. 连接队列
5.3.1. 什么是连接队列
连接队列,包括syns queue和accept queue两个,前者也被称为半连接队列,后者被称为全连接队列。其都是相对于服务端而言的,它们在三次握手的过程中如下:
5.3.2. 半连接队列
半连接队列是用户调用listen()成功后,由内核创建的队列,它的大小由下面的公式决定:
代码语言:shell复制max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)
一般/proc/sys/net/ipv4/tcp_max_syn_backlog
这个值还是挺大的。
- 当内核回复syn ack报文后,即状态变为SYN_RCVD后,会将该对象加入到半连接队列中;
- 当内核收到客户端回复的ack后,会将连接对象从半连接队列中移出到全连接队列中;
- 当半连接队列满时,如果有更多的syn报文发送,内核会将其丢弃;
5.3.3. 全连接队列
全连接队列也是在用户调用listen()成功后,由内核创建的队列,它的大小由下面的公式决定:
代码语言:shell复制min(backlog, /proc/sys/net/core/somaxconn)
其中backlog是用户在调用listen时传入的值,在go语言net网络编程中可以发现,在Linux环境下其值:
代码语言:go复制func maxListenerBacklog() int {
fd, err := open("/proc/sys/net/core/somaxconn")
if err != nil {
return syscall.SOMAXCONN
}
defer fd.close()
l, ok := fd.readLine()
if !ok {
return syscall.SOMAXCONN
}
f := getFields(l)
n, _, ok := dtoi(f[0])
if n == 0 || !ok {
return syscall.SOMAXCONN
}
if n > 1<<16-1 {
return maxAckBacklog(n)
}
return n
}
全连接队列是在内核中维护的,当用户调用accept()时,会将连接交由用户空间,从而实现队列释放。
当全连接队列满时,通常会有两种处理方案(依赖于/proc/sys/net/ipv4/tcp_abort_on_overflow的值,它有两个值:0和1,默认为0):
- 0:将该链接状态还原为SYN_RCVD状态,造成最后的ACK报文缺失的假象,等待客户端重发(可能是伴随数据,依赖于前面说过的ack丢包处理机制),然后再次尝试,这是一种修复/重试机制;
- 1:直接发送RST报文给客户端,表示终止此连接,从syns queue中移除,此时客户端会收到104 connection reset by peer错误。
5.3.4. Send-Q/Recv-Q
一个连接处理Listen状态和其他状态(主要指的是:ESTABLISH状态)时其含义有区别:
当连接处于Listen状态下:
- Send-Q:全连接队列(也就是 accept 队列)的总大小;
- Recv-Q:表示全连接队列排队的连接个数,也就是未被用户取走的连接数量,其最大值为Send-Q 1;
当连接处于ESTABLISH状态下:
- Send-Q:发送缓冲区中已经被发送,但尚未被确认(没有收到ack)的大小;
- Recv-Q:接收缓冲区中还没有被copy到用户空间的大小(单位:字节),即接收到,但用户尚未read的部分;
5.3.4. Syn-Flood
Syn洪水攻击,也是一种拒绝服务攻击类型(DDos),其基本原理是:
- 客户端向服务器发送了SYN报文后突然死机或掉线;
- 服务器在发出SYN ACK应答报文后无法收到客户端的ACK报文;
- 此时就会导致第三次握手无法完成;
- 服务器一般会重试再次发送SYN ACK给客户端,并等待一段时间(SYN timeout)后丢弃这个未完成的连接;
SYN Timeout一般是31s,内核会以倍增的方式重复发送SYN ACK消息:
代码语言:go复制1s 2s 4s 8s 16s = 31s
整个期间,服务器需要维护一个很大的半连接队列,并且需要不断重试发送SYN ACK报文,会严重影响到正常业务,因为连接其实是一个比较复杂的对象,一般称为TCB(传输控制块,Transmission Control Block),它一般占用300字节-1500字节之间,其比较消耗内存,因此当出现Syn洪水攻击时,会导致两个后果:
- 如果服务器的TCP/IP栈不够强大,最后的结果往往是堆栈溢出崩溃;
- 如果服务器端的系统足够强大,也将忙于处理攻击者伪造的TCP连接请求而无暇理睬客户的正常请求;
一般解决方案有以下几种:
- 监听释放:不停监视系统的半开连接和不活动连接,当达到一定阈值时拆除这些连接,从而释放系统资源。但这种方法对于所有的连接一视同仁,而且由于SYN Flood造成的半开连接数量很大,正常连接请求也被淹没在其中被这种方式误释放掉,因此这种方法属于入门级的SYN Flood方法;
- 延缓TCB分配:
- Syn Cache:不生成TCB,而是回复syn ack,并用hash表维护,当收到正确的ack时再创建TCB,这样虽然也有开销,但是相当于TCB会小很多;
- Syn Cookie:Syn Cache还是会维护一些信息,但Syn Cookie技术则完全不使用任何存储资源,这种方法比较巧妙,它使用一种特殊的算法生成Sequence Number,这种算法考虑到了对方的IP、端口、己方IP、端口的固定信息,以及对方无法知道而己方比较固定的一些信息,如MSS、时间等,在收到对方的ACK报文后,重新计算一遍,看其是否与对方回应报文中的(Sequence Number-1)相同,从而决定是否分配TCB资源;
- 代理:在服务器前面增加一层代理,当三次握手创建成功后,在发送至服务器建立连接。
6. TCP四次挥手
6.1. 四次挥手过程
6.2. 为什么要2MSL?
MSL被认为是一个发送的时间,那么2MSL就被认为是一个发送和回复所需的最大时间,如果直到2MSL,Client仍然没有再次FIN,那么Client推断ACK已经成功被Server端接收,则结束TCP连接。
如果Client的ACK在回复过程中丢失的话,理论上Server端需要重新发送FIN报文,以便于正常结束连接。
6.3. MSL配置
位置:/proc/sys/net/ipv4/tcp_fin_timeout,单位是秒,这个值是2MSL的时间,假设是60,则一个MSL就是30s。
可以通过调整该值使得一个连接更快结束。
6.4. 大面积TIME-WAIT
WEB服务器比较容易会出现这种场景,例如有很多HTTP请求,对于Server端而言,会主动去释放这些连接,作为主动释放方,需要等到2MSL才能真正的释放,但处于CLOSE之前都会被视为占用了FD,那么就可能出现open too many files的问题。
这种情况一般的解决办法就是改一些内核配置:
代码语言:shell复制#表示当keepalive启用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为300秒
net.ipv4.tcp_keepalive_time=1200
#表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数
net.ipv4.tcp_max_syn_backlog = 4096
#表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭
net.ipv4.tcp_syncookies = 1
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
net.ipv4.tcp_tw_reuse = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
net.ipv4.tcp_tw_recycle = 1
#减少超时前的探测次数
net.ipv4.tcp_keepalive_probes=5
也可以借鉴6.3章节,配置MSL时间。
6.5. 大面积CLOSE-WAIT
这类情况其实很简单,就是服务端没有发送FIN报文导致,一般来说就是程序有BUG,需要修复。一般的表现就是客户端因为时间太久发送了关闭连接的请求,但是服务端因为某些原因没有关闭该链接。
所以,找BUG吧。
7. TCP数据传输
7.1. 数据合并
对于TCP通信而言,并不是发送一个报文,接收ACK后再发送下一个,这样的效率实在是太低了,所以TCP通信时会持续发送,并持续等待应答,如下图的情况:
操作系统会进行判断,尽快采用数据捎带ACK的传输方式。
7.2. seq & ack
核心就是两点:
- seq是相对于自己发送的数据而言的,下一次发送的seq = 上次seq 数据长度;
- ack是相对于对方发送的数据而言的,ack = 发送方seq 数据长度;
7.3. 滑动窗口
滑动窗口在每个报文中都有,用来表示此报文的发送方能够接收的数据大小(单位:字节)。此机制可以控制对方发送数据的频率,从而达到流量控制的效果,这个值是一个16bit,最大值为65535,如果超过这个值就需要使用到window scale选项。
8. UDP报文格式
UDP即User Datagram Protocol,它的报文格式如下:
代码语言:txt复制 0 7 8 15 16 23 24 31
-------- -------- -------- --------
| Source | Destination |
| Port | Port |
-------- -------- -------- --------
| | |
| Length | Checksum |
-------- -------- -------- --------
|
| data octets ...
---------------- ...
User Datagram Header Format
- Source Port:源端口;
- Destination Port:目的端口;
- Length:总长度,即UDP报文的长度,为IP总长度-IP首部长度,主要是为了解析方便;
- Checksum:校验和。
因为UDP数据结构太简单了,Checksum在校验时会出现一些无法识别的伪造,所以实际中校验和会讲UDP头部加上IP头的部分内容合并在一起计算校验和,一般包括了源IP、目的IP、协议号,UDP长度等信息。
9. 数据分片
9.1. 那一层能分片
要分片,首先得需要支持重组,那么对应着协议的结构,首先需要明确的是哪些层可以分片:
- 以太帧:不支持分片,没有对应的字段;
- IP报文:支持分片,有Fragment Offset、DF、MF等标识;
- TCP报文:支持分片,有Data Offset;
- UDP报文:不支持分片,没有对应的字段。
9.2. IP分片
一般不建议IP分片,IP分片后如果在传输过程中丢失分片的话需要重传所有的数据。
IP报文的大小是根据MTU决定的,MTU其实是由双方决定的,假设在传输的过程中有一些网络设备不支持对应的MTU,那么这些设备就需要对IP报文进行分片。
假设存在这么一种情况:A ---1500---B---1024---C---1500---D
B-C间因为特殊的原因,其MTU为1024,从A发送到B的数据,必须进行分片才能发出去,因为B的出口是1024,那么B会有两种处理策略:
- 假设DF == 0,表示允许分片,那么B会进行分片,到达目的地后由目的地节点重组;
- 假设DF == 1,表示不允许分片,那么B会丢弃该报文,然后应答给A一个ICMP消息,Fragment Needed But DF Set,并在信息中包含1024(就是可以发送的大小),A收到后会进行分片,然后重新发送给B,转发至D端。
如果在中间的网络设备被分片了,假设丢包的话,对于目的端而言,它可以知道缺少了哪一片,但是发送方不一定知道,因为分片不是在发送方做的,就只能重传所有的数据。
9.3. TCP分片
TCP分片比较简单,核心就是处理MSS,发送方会按照对端的MSS对数据进行分片,如果缺少了某个分片,只需要重传这个分片即可,不需要,加快了处理效率。
实际使用中建议TCP分片,而将IP设置为不分片的模式。假设发送数据为3K字节(不考虑粘包),那么数据会被分为3片:1460/1460/80。