导语:网络协议是理解网络情况的基础,当遇到网络问题时,首先可以从网络协议入手,熟悉的网络协议可以有效帮助小伙伴们排查或者说定位大概的问题方面。本文整理了目前最常用的网络通信协议,相信对小伙伴们肯定都有帮助。
1. 以太帧
1.1. 帧格式
- 还有其他的格式帧,但用的比较少;
- 所有的网络设备都需要支持以太帧格式;
- 目的地址、源地址都是指的MAC地址;
- 可能存在分片的情况,在实际中还是可能会存在一些设备(尤其是旧设备)不能支持MTU为1500的情况;
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 + 14 + 4
在100M网络下传输一帧就需要5ms,也就是说这5ms其他进程发送不了任何数据,如果是早先的电话拨号,网速只有2M的情况下:
( 65553 * 8 ) / ( 2 * 1024 * 1024 ) ≈ 0.100(s)
100ms内无法发送其他数据,简直不能接受。
1.2.2. 设置过小
假设MTU值设置为100,那么单个帧传输的时间,在2Mbps带宽下需要:
( 100 * 8 ) / ( 2 * 1024 * 1024 ) * 1000 ≈ 5(ms)
时间上已经能接受了,问题在于,不管MTU设置为多少,以太网头帧尾大小是固定的,都是14 + 4,所以在MTU为100的时候,一个以太网帧的传输效率为:
( 100 - 14 - 4 ) / 100 = 82%
写成公式就是:( T - 14 - 4 ) / T
,当T趋于无穷大的时候,效率接近100%
,也就是MTU的值越大,传输效率最高,但是基于上一点传输时间的问题,来个折中的选择,既然头加尾是18,那就凑个整来个1500,总大小就是1518,传输效率:
1500 / 1518 = 98.8%
100Mbps传输时间:
( 1518 * 8 ) / ( 100 * 1024 * 1024 ) * 1000 = 0.11(ms)
2Mbps传输时间:
( 1518 * 8 ) / ( 2 * 1024 * 1024 ) * 1000 = 5.79(ms)
总体上时间都还能接受。
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报文
http://mirrors.nju.edu.cn/rfc/inline-errata/rfc791.html
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
- Version:版本,4表示IPv4,6表示IPv6;
- IHL:报文首部长度;
- 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,则该报文丢弃(需要重新计算校验和);
- Protocol:协议,即数据部分的协议,例如:TCP、UDP等;
- Header Checksum:头部校验和;
- Source Address:源地址;
- Destination Address:目的地址;
- Options:可选字段,长度可变,例如有的命令会在每个报文中加入经过的IP;
- Padding:填充,需要4字节对齐;
除可选字段外,IP头总长度为20字节。
4. TCP报文格式
http://mirrors.nju.edu.cn/rfc/inline-errata/rfc793.html
4.1. 分层关系
+------+ +-----+ +-----+ +-----+
|Telnet| | FTP | |Voice| ... | | Application Level
+------+ +-----+ +-----+ +-----+
| | | |
+-----+ +-----+ +-----+
| TCP | | RTP | ... | | Host Level
+-----+ +-----+ +-----+
| | |
+-------------------------------+
| Internet Protocol & ICMP | Gateway Level
+-------------------------------+
|
+---------------------------+
| Local Network Protocol | Network Level
+---------------------------+
Protocol Relationships
4.2. 格式
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
TCP Header Format
- 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:紧急指针标志,1表示紧急指针有效;
- ACK:ACK标志,1表示确认号有效,0表示报文中不含有确认信息,忽略确认号字段;
- PSH:PUSH标志,1表示带有push标志的数据,指示接收方收到该数据后,需要尽快将报文交给应用程序;
- RST:重建连接标志,1表示TCP连接中出现严重错误,例如程序挂了,则会由内核TCP协议栈发送RST报文至对端,表示必须要释放连接,后续根据情况重建;
- SYN:同步序号标识,用来发起一个连接,1表示是syn报文;
- FIN:finish标识,用于释放连接,1表示发送方已经没有数据发送了,可以关闭本方连接;
- Window:滑动窗口大小,描述的是本方的拥塞情况;
- Checksum:校验和;
- Urgent Pointer:紧急指针,在URG为1时有效,具体不是很清楚做什么;
- Options:可选字段;
- Padding:填充;
- data:具体数据;
TCP报文头最少占用20个字节。
4.3. 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;
这个值是在三次握手时明确的,并且必须发送至对端,且只会在握手时发送。
它的格式:
+--------+--------+---------+--------+
|00000010|00000100| max seg size |
+--------+--------+---------+--------+
Kind=2 Length=4
因为是Option,所以允许服务器不发送给值,假设不发送的话,对端会将该值设置为:536Bytes。
4.3.2. Window Scale
Window Size的乘数因子,为了放大滑动窗口计算使用。
这个值是在三次握手时明确的,并且必须发送至对端,且只会在握手时发送。
它的格式:
+--------+--------+---------+--------+
|00000011|00000011| shift count |
+--------+--------+---------+--------+
Kind=3 Length=3
shift count是2的n次方中的n,例如7表示要放大128倍。
5. TCP三次握手
5.1. 三次握手过程
TCP协议中的 SYN 是 Synchronize 的缩写。它是 TCP 三次握手过程中的首个报文,用于在建立连接时同步序列号。具体来说,SYN 报文允许发送方告诉接收方自己希望建立连接,并同时携带自己的初始序列号。
ACK 是 Acknowledgment 的缩写,表示确认应答。在 TCP 协议中,ACK 用于确认已接收到的数据。具体来说,TCP 使用 ACK 报文来通知另一方数据包已经成功到达,从而确保数据传输的可靠性。
客户端状态变化:
- 发送syn报文后,变为SYN_SENT状态;
- 接收到SYN+ACK报文后,变为ESTABLISHED状态;
服务端状态变化:
- 默认是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状态,而是在一个中间等待状态(ACTIVE状态)。会存在下面这三种情况:
- 假设两端都没有发送数据,那么Server会周期性重传SYN+ACK包,直到收到Client的确认;
- 假设此时Client开始发送数据,Server收到了Client的数据包(包中也会有ACK),那么就会切换到ESTABLISHED状态,正常处理;
- 假设Server需要发送数据,但明显此时无法发送,则会一直周期性的重传SYN+ACK,直到搜到Client的确认报文;
5.3. 连接队列
5.3.1. 什么是连接队列
连接队列,包括syns queue和accept queue两个,前者也被称为半连接队列,后者被称为全连接队列。其都是相对于服务端而言的,它们在三次握手的过程中如下:
5.3.2. 连接队列大小
syns queue的大小 = max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)
accept queue的大小 = min(backlog, /proc/sys/net/core/somaxconn),其中backlog是用户编程时在listen函数中设置的值。
5.3.3. backlog
backlog核心是内核给用户侧提供的允许连接的控制。
对于服务端(其实客户端也存在)有两个队列,Send-Q和Recv-Q,它们的关系如下:
- Send-Q:其实就是accept queue的最大值,为min(backlog, /proc/sys/net/core/somaxconn);
- Recv-Q:指的是已经建立成功连接(ESTABLISHED状态),但还没有交付给应用的TCP连接的数量,最大值为Send-Q + 1,可以认为允许有一个连接从accept queue中取出,但还没有交由应用,处于游离状态;
5.3.4. syns queue满
处理很暴力,所有新的SYN报文都会被丢弃,直到有空位。
5.3.5. accept queue满
有两种处理方式,可以通过修改/proc/sys/net/ipv4/tcp_abort_on_overflow来配置,其有效值有两个:0或者1,具体含义如下
- 0:将该链接状态还原为SYN_RCVD状态,造成最后的ACK报文缺失的假象,等待客户端重发,然后再次尝试,这是一种修复/重试机制;
- 1:直接发送RST报文给客户端,表示终止此连接,从syns queue中移除,此时客户端会收到104 connection reset by peer错误。
有的时候,可以刻意将值改为1,通过接收到的报文来判断是否是accept queue溢出导致的。
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的问题。
这种情况一般的解决办法就是改一些内核配置:
#表示当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. 数据合并
操作系统会进行判断,尽快采用数据捎带ACK的传输方式。
7.2. seq & ack
需要注意的是:
- seq其实是相对于自己而言的
- ack是相对于对端发送的数据而言的。
7.3. 滑动窗口
对于TCP通信而言,并不是发送一个报文,接收ACK后再发送下一个,这样的效率实在是太低了,所以TCP通信时会持续发送,并持续等待应答,如下图的情况:
这样就可以同时发送多个报文,在不需要等待上个报文回复的情况下,但对应的,需要发送方维护一个信息,信息中需要包含已发送和确认的关系。
滑动窗口在每个报文中都有,用来表示此报文的发送方能够接收的数据大小(单位:字节)。此机制可以控制对方发送数据的频率,从而达到流量控制的效果,这个值是一个16bit,最大值为65535,如果超过这个值就需要使用到window scale选项。
对于接收方而言:
- 已接收已确认表示报文已经收到,并且回复了ACK,之所以还在buf中,应该是应用程序还没有取走;
- 等待接收未确认标识报文还没有收到,但也不完全是,因为报文可以不按照顺序发送过来,例如图中的8/9,由于TCP协议是基于seq和ack来确认顺序的,所以此时接收到的报文8/9不能确认,那么就只能等6/7报文到达,等6/7报文到达后,会回复ACK,此时的6/7/8/9都会变为已接收已确认的状态;
- 不能接收的表示超过了操作系统设置的最大buf,如果说应用程序将已接收已确认的数据取走,那么16就可以接收了。
Window Size在接收方中描述的就是等待接收未确认的buf大小。
对于发送方而言:
- windows大小表示的是已发送未确认和未发送可发送的大小;
- 对于已发送已确认的消息,操作系统会将其移除;
- 在传统IO模型中,用户要发送的数据会首先在用户空间,内核会根据自己的Buf大小,选择移动部分数据至自己的socker缓冲区;
8. UDP报文格式
https://www.rfc-editor.org/rfc/rfc768
UDP即User Datagram Protocol,它的报文格式如下:
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设置为不分片的模式。
9.4. UDP分片
UDP协议本身是不支持分片的,但是可以通过IP层进行分片,这样就变相实现了分片的能力。
但是考虑到IP层分片在丢包时需要整体重传的弊端,所以还是不建议分片,换句话说,在使用UDP协议时尽量发送少了的数据,避免IP分片的风险。推荐300-500字节。