[基础] TCP小结
抓包示例
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 连接池不复用的问题 小结的动因