MTU即Max Transfer Unit,最大传输单元,那么为什么MTU是1500呢?
以太网帧是传输中的最小可识别单元,再往下就是0101所对应的光信号,所以一条带宽同时只能发送一个以太网帧。如果同时发送多个,那么对端就无法重组成一个以太网帧。
假设MTU设置为65535,在100Mbps
的带宽中(假设中间没有损耗),我们计算一下发送这一帧需要的时间:
( 65553 * 8 ) / ( 100 * 1024 * 1024 ) ≈ 0.005(s)
65553 = 65535 + 6 + 6 + 2 + 4
在100M网络下传输一帧就需要5ms,也就是说这5ms其他进程发送不了任何数据,如果是早先的电话拨号,网速只有2M的情况下:
( 65553 * 8 ) / ( 2 * 1024 * 1024 ) ≈ 0.100(s)
100ms内无法发送其他数据,简直不能接受。
假设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)
总体上时间都还能接受。
其实在网卡都会有MTU的显示,默认一般是1500,但127.0.0.1一般是65535:
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
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 |
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
整个字段描述是4字节对齐的,每行代表4个字节。
除可选字段外,IP头总长度为20字节。
https://www.rfc-editor.org/rfc/rfc2460
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| Traffic Class | Flow Label |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Length | Next Header | Hop Limit |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Source Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Destination Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
IPv6协议会有多个头部,包括一个基础头部和一个扩展头部,下面是基础头部的字段:
对于IPv6的数据而言,其一般包括扩展头部和数据两部分,数据即最终的TCP/UDP部分,但扩展头部本身格式是采用了一种链式的处理关系,即每个头部中可以包含下一个头部的类型,而下一个头部则可以包含下下个头部的类型,最终到达TCP或UDP数据部分。例如下面这三个例子:
+---------------+------------------------
| 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 |
+---------------+----------------+-----------------+-----------------
http://mirrors.nju.edu.cn/rfc/inline-errata/rfc793.html
+------+ +-----+ +-----+ +-----+
|Telnet| | FTP | |Voice| ... | | Application Level
+------+ +-----+ +-----+ +-----+
| | | |
+-----+ +-----+ +-----+
| TCP | | RTP | ... | | Host Level
+-----+ +-----+ +-----+
| | |
+-------------------------------+
| Internet Protocol & ICMP | Gateway Level
+-------------------------------+
|
+---------------------------+
| Local Network Protocol | Network Level
+---------------------------+
Protocol Relationships
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报文头最少占用20个字节。
Options为可选字段。
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。
Window Size的乘数因子,为了放大滑动窗口计算使用。
这个值是在三次握手时明确的,是发送至对端,且在握手时发送(据说在某些其他场景下也会更新)。
它的格式:
+--------+--------+---------+--------+
|00000011|00000011| shift count |
+--------+--------+---------+--------+
Kind=3 Length=3
shift count是2的n次方中的n,例如7表示要放大128倍。
客户端状态变化:
服务端状态变化:
Client端发现自己没有收到ACK,会周期性重传SYN包,直到收到Server端的SYN+ACK。
Server端发现自己没有收到ACK,会周期性重传SYN+ACK包,直到收到Client端的ACK。
此时,对于Client而言,其已经变为了ESTABLISHED状态,但Sever端目前并不是ESTABLISHED状态。会存在下面这三种情况:
连接队列,包括syns queue和accept queue两个,前者也被称为半连接队列,后者被称为全连接队列。其都是相对于服务端而言的,它们在三次握手的过程中如下:
半连接队列是用户调用listen()成功后,由内核创建的队列,它的大小由下面的公式决定:
max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)
一般/proc/sys/net/ipv4/tcp_max_syn_backlog
这个值还是挺大的。
全连接队列也是在用户调用listen()成功后,由内核创建的队列,它的大小由下面的公式决定:
min(backlog, /proc/sys/net/core/somaxconn)
其中backlog是用户在调用listen时传入的值,在go语言net网络编程中可以发现,在Linux环境下其值:
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):
一个连接处理Listen状态和其他状态(主要指的是:ESTABLISH状态)时其含义有区别:
当连接处于Listen状态下:
当连接处于ESTABLISH状态下:
Syn洪水攻击,也是一种拒绝服务攻击类型(DDos),其基本原理是:
SYN Timeout一般是31s,内核会以倍增的方式重复发送SYN+ACK消息:
1s + 2s + 4s + 8s + 16s = 31s
整个期间,服务器需要维护一个很大的半连接队列,并且需要不断重试发送SYN+ACK报文,会严重影响到正常业务,因为连接其实是一个比较复杂的对象,一般称为TCB(传输控制块,Transmission Control Block),它一般占用300字节-1500字节之间,其比较消耗内存,因此当出现Syn洪水攻击时,会导致两个后果:
一般解决方案有以下几种:
MSL被认为是一个发送的时间,那么2MSL就被认为是一个发送和回复所需的最大时间,如果直到2MSL,Client仍然没有再次FIN,那么Client推断ACK已经成功被Server端接收,则结束TCP连接。
如果Client的ACK在回复过程中丢失的话,理论上Server端需要重新发送FIN报文,以便于正常结束连接。
位置:/proc/sys/net/ipv4/tcp_fin_timeout,单位是秒,这个值是2MSL的时间,假设是60,则一个MSL就是30s。
可以通过调整该值使得一个连接更快结束。
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时间。
这类情况其实很简单,就是服务端没有发送FIN报文导致,一般来说就是程序有BUG,需要修复。一般的表现就是客户端因为时间太久发送了关闭连接的请求,但是服务端因为某些原因没有关闭该链接。
所以,找BUG吧。
对于TCP通信而言,并不是发送一个报文,接收ACK后再发送下一个,这样的效率实在是太低了,所以TCP通信时会持续发送,并持续等待应答,如下图的情况:
操作系统会进行判断,尽快采用数据捎带ACK的传输方式。
核心就是两点:
滑动窗口在每个报文中都有,用来表示此报文的发送方能够接收的数据大小(单位:字节)。此机制可以控制对方发送数据的频率,从而达到流量控制的效果,这个值是一个16bit,最大值为65535,如果超过这个值就需要使用到window scale选项。
UDP即User Datagram Protocol,它的报文格式如下:
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+
| Source | Destination |
| Port | Port |
+--------+--------+--------+--------+
| | |
| Length | Checksum |
+--------+--------+--------+--------+
|
| data octets ...
+---------------- ...
User Datagram Header Format
因为UDP数据结构太简单了,Checksum在校验时会出现一些无法识别的伪造,所以实际中校验和会讲UDP头部加上IP头的部分内容合并在一起计算校验和,一般包括了源IP、目的IP、协议号,UDP长度等信息。
要分片,首先得需要支持重组,那么对应着协议的结构,首先需要明确的是哪些层可以分片:
一般不建议IP分片,IP分片后如果在传输过程中丢失分片的话需要重传所有的数据。
IP报文的大小是根据MTU决定的,MTU其实是由双方决定的,假设在传输的过程中有一些网络设备不支持对应的MTU,那么这些设备就需要对IP报文进行分片。
假设存在这么一种情况:A ---1500---B---1024---C---1500---D
B-C间因为特殊的原因,其MTU为1024,从A发送到B的数据,必须进行分片才能发出去,因为B的出口是1024,那么B会有两种处理策略:
如果在中间的网络设备被分片了,假设丢包的话,对于目的端而言,它可以知道缺少了哪一片,但是发送方不一定知道,因为分片不是在发送方做的,就只能重传所有的数据。
TCP分片比较简单,核心就是处理MSS,发送方会按照对端的MSS对数据进行分片,如果缺少了某个分片,只需要重传这个分片即可,不需要,加快了处理效率。
实际使用中建议TCP分片,而将IP设置为不分片的模式。假设发送数据为3K字节(不考虑粘包),那么数据会被分为3片:1460/1460/80。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。