抓包示例
root@python:~# tcpdump -n -S tcp port 5009 # -S 参数的目标是取得 ack 的绝对值,不加该参数,第三次握手的 ack 为相对值 1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
17:35:43.707223 IP 122.235.85.89.49302 > 172.16.10.164.5009: Flags [S], seq 267775201, win 8192, options [mss 1452,nop,wscale 2,nop,nop,sackOK], length 0 ** 客户端发动申请, 第一次握手 **
17:35:43.707299 IP 172.16.10.164.5009 > 122.235.85.89.49302: Flags [S.], seq 3046340720, ack 267775202, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
17:35:43.729057 IP 122.235.85.89.49302 > 172.16.10.164.5009: Flags [.], ack 3046340721, win 16698, length 0 ** 三次握手完结 **
17:35:43.778243 IP 122.235.85.89.49302 > 172.16.10.164.5009: Flags [P.], seq 267775202:267775428, ack 3046340721, win 16698, length 226 ** 开始传送数据 **
17:35:43.778299 IP 172.16.10.164.5009 > 122.235.85.89.49302: Flags [.], ack 267775428, win 237, length 0
17:35:43.780225 IP 172.16.10.164.5009 > 122.235.85.89.49302: Flags [.], seq 3046340721:3046343625, ack 267775428, win 237, length 2904
17:35:43.780254 IP 172.16.10.164.5009 > 122.235.85.89.49302: Flags [P.], seq 3046343625:3046343827, ack 267775428, win 237, length 202
17:35:43.802585 IP 122.235.85.89.49302 > 172.16.10.164.5009: Flags [.], ack 3046343827, win 16698, length 0
17:35:43.803603 IP 122.235.85.89.49302 > 172.16.10.164.5009: Flags [P.], seq 267775428:267775619, ack 3046343827, win 16698, length 191
17:35:43.803988 IP 172.16.10.164.5009 > 122.235.85.89.49302: Flags [P.], seq 3046343827:3046343878, ack 267775619, win 245, length 51
17:35:43.825824 IP 122.235.85.89.49302 > 172.16.10.164.5009: Flags [P.], seq 267775619:267775895, ack 3046343878, win 16685, length 276
17:35:43.865696 IP 172.16.10.164.5009 > 122.235.85.89.49302: Flags [.], ack 267775895, win 254, length 0
17:35:44.314454 IP 172.16.10.164.5009 > 122.235.85.89.49302: Flags [.], seq 3046343878:3046346782, ack 267775895, win 254, length 2904
17:35:44.314472 IP 172.16.10.164.5009 > 122.235.85.89.49302: Flags [.], seq 3046346782:3046349686, ack 267775895, win 254, length 2904
17:35:44.314477 IP 172.16.10.164.5009 > 122.235.85.89.49302: Flags [.], seq 3046349686:3046352590, ack 267775895, win 254, length 2904
17:35:44.314482 IP 172.16.10.164.5009 > 122.235.85.89.49302: Flags [P.], seq 3046352590:3046352839, ack 267775895, win 254, length 249
17:35:44.336458 IP 122.235.85.89.49302 > 172.16.10.164.5009: Flags [.], ack 3046352839, win 16698, length 0
17:35:44.402718 IP 122.235.85.89.49302 > 172.16.10.164.5009: Flags [P.], seq 267775895:267775926, ack 3046352839, win 16698, length 31
17:35:44.402758 IP 172.16.10.164.5009 > 122.235.85.89.49302: Flags [.], ack 267775926, win 254, length 0
17:35:44.402869 IP 172.16.10.164.5009 > 122.235.85.89.49302: Flags [F.], seq 3046352839, ack 267775926, win 254, length 0 ** 四次挥手开始 **
17:35:44.404033 IP 122.235.85.89.49302 > 172.16.10.164.5009: Flags [F.], seq 267775926, ack 3046352839, win 16698, length 0
17:35:44.404047 IP 172.16.10.164.5009 > 122.235.85.89.49302: Flags [.], ack 267775927, win 254, length 0
17:35:44.424589 IP 122.235.85.89.49302 > 172.16.10.164.5009: Flags [.], ack 3046352840, win 16698, length 0 ** 四次挥手完结 **
Flags 包标记:
S=SYN 发动连贯标记。
P=PUSH 传送数据标记。
F=FIN 敞开连贯标记。
ACK 示意确认包。
RST=RESET 异样敞开连贯。
. 示意没有任何标记。
TCP 连贯建设(三次握手)
客户端 A,服务器 B,初始序号 seq,确认号 ack
初始状态:B 处于监听状态,A 处于关上状态
A -> B : seq = x(A 向 B 发送连贯申请报文段,A 进入同步发送状态 SYN-SENT)
B -> A : ack = x + 1,seq = y(B 收到报文段,向 A 发送确认,B 进入同步收到状态 SYN-RCVD)
A -> B : ack = y+1(A 收到 B 的确认后,再次确认,A 进入连贯状态 ESTABLISHED)
连贯后的状态:B 收到 A 的确认后,进入连贯状态 ESTABLISHED
TCP 连贯开释(四次挥手)
A -> B : seq = u(A 收回连贯开释报文段,进入终止期待 1 状态 FIN-WAIT-1)
B -> A : ack = u + 1,seq = v(B 收到报文段,收回确认,TCP 处于半敞开,B 还可向 A 发数据,B 进入敞开期待状态 WAIT)
B -> A : ack = u + 1,seq = w(B 反复发送确认号,进入最初确认状态 LAST-ACK)
A -> B : ack = w + 1,seq = u + 1(A 收回确认,进入工夫期待状态 TIME-WAIT)
通过工夫期待计时器设置的工夫 2MSL 后,A 才进入 CLOSED 状态
为什么建设连贯是三次握手,而敞开连贯却是四次挥手呢?
三次握手的最次要目标是保障连贯是双工的,牢靠更多的是通过重传机制来保障的。这是因为服务端在 LISTEN 状态下,收到建设连贯申请的 SYN 报文后,把 ACK 和 SYN 放在一个报文里发送给客户端。而敞开连贯时,当收到对方的 FIN 报文时,
仅仅示意对方不再发送数据了然而还能接收数据,咱们也未必全副数据都发送给对方了,所以咱们不能够立刻 close,也能够发送一些数据给对方后,
再发送 FIN 报文给对方来表示同意当初敞开连贯,因而,咱们的 ACK 和 FIN 个别都会离开发送。
TCP 牢靠传输的实现
TCP 连贯的每一端都必须设有两个窗口——一个发送窗口和一个接管窗口。TCP 的牢靠传输机制用字节的序号进行管制。TCP 所有的确认都是基于序号而不是基于报文段。
发送过的数据未收到确认之前必须保留,以便超时重传时应用。发送窗口没收到确认不动,和收到新的确认后前移。
发送缓存用来临时寄存:发送应用程序传送给发送方 TCP 筹备发送的数据;TCP 已发送出但尚未收到确认的数据。
接管缓存用来临时寄存:按序达到的、但尚未被接管应用程序读取的数据;不按序达到的数据。
RST 标记
RST 示意连贯重置,用于敞开那些曾经没有必要持续存在的连贯。个别状况下示意异样敞开连贯,区别与四次离别失常敞开连贯。
产生 RST 的三个条件是:
- 目的地为某端口的 SYN 达到,然而在该端口上并没有正在监听的服务器;
- TCP 想勾销一个已有连贯;
- TCP 接管到一个基本不存在的连贯上的分节;
几种场景:
- 端口未关上, 客户端向服务端某端口发动连贯申请 SYN,然而目标服务端主机不存在该端口,此时向客户端回应 RST,中断连贯申请。
- 申请超时, 常见的有 tw_bucket 满了、tcp 连贯队列爆满且开启 tcp_abort_on_overflow、配置 so_linger、敞开未清空读缓冲区的连贯。
- 提前敞开,TCP 应用程序接收数据是从操作系统中接管的 TCP 数据,如果数据达到了操作系统然而我利用数据不想持续接收数据了,此时 RST 中断连贯。
- 在一个已敞开的 socket 上收到数据, 显然是异样的,此时应 RST 中断连贯。
配置与坑
大多都写开启 net.ipv4.tcp_tw_recycle 这个开关,能够疾速回收处于 TIME_WAIT 状态的 socket(针对 Server 端而言)。
而实际上,这个开关,须要 net.ipv4.tcp_timestamps(默认开启) 这个开关开启才有成果。
更被提到却很重要的一个信息是:当 tcp_tw_recycle 开启时(tcp_timestamps 同时开启,疾速回收 socket 的成果达到),对于位于 NAT 设施前面的 Client 来说,
是一场劫难——会导到 NAT 设施前面的 Client 连贯 Server 不稳固(有的 Client 能连贯 server,有的 Client 不能连贯 server)。
也就是说,tcp_tw_recycle 这个性能,是为“外部网络”(网络环境本人可控——不存在 NAT 的状况)设计的,对于公网不宜应用。
拓展浏览
- 图解 TCP 非常适合入门
- Linux 实例罕用内核网络参数介绍与常见问题解决 内容详实残缺, 倡议浏览
- 应用 ping 命令丢包或不通时的链路测试方法 场景问题的解决示例, 倡议浏览
- Linux 建设 TCP 连贯的超时工夫剖析
- TCP 重置报文段及 RST 常见场景剖析
- net.ipv4.tcp_timestamps 引发的 tcp syn 无响应案
- tcp_timestamps 和 tcp_tw_recycle
- Go http client 连接池不复用的问题