准备工作
Web服务器IP(阿里云的内网IP:172.17.51.219:443)
客户端浏览器 IP:114.242.250.59
标志位(常用)
P push,立刻刷新输出buffer
. 确认应答ACK
F 确认结束
S 请求同步
步骤:
1 服务端shell中执行tcpdump启动监听
tcpdump tcp -i eth0 '((host 172.17.51.219 or 114.242.250.59) and port 443)' -nn -S
参数解释(更多参数 man 一下)
tcp : 只监听tcp协议数据包
-i eth0 : 监听网卡 eth0 上的流量(我的主机通过 eth0 跟外网通信)
-nn : 以ip:port的形式代替助记符显示形式 否则会显示类似于 www.example.com:https 形式
-S : 以绝对值形式显示 发送序列号,确认序列号,便于直观观察 SYN 与 ACK 之间的关系,默认情况下是以相对值显示
'((host 172.17.51.219 or 114.242.250.59) and port 443)' : 明确指定监控 host:172.17.51.219 与 host:114.242.250.59 之间通过 443 接收或发送的数据包
2 客户端浏览器访问web服务器
https://www.example.com?foo=foo
3 观察shell端输出后
监测到握手数据后,ctrl + c 终止监听
[root@ ~]$>tcpdump -i eth0 '((host 172.17.51.219 or 114.242.250.59) and port 443)' -nn -S
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
13:51:40.965707 IP 114.242.250.59.41032 > 172.17.51.219.443: Flags [F.], seq 3249586170, ack 137328987, win 2061, options [nop,nop,TS val 555445081 ecr 2928319486], length 0
13:51:40.965838 IP 172.17.51.219.443 > 114.242.250.59.41032: Flags [F.], seq 137328987, ack 3249586171, win 235, options [nop,nop,TS val 2928333454 ecr 555445081], length 0
13:51:40.966228 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [S], seq 1328522649, win 65535, options [mss 1360,nop,wscale 6,nop,nop,TS val 555445081 ecr 0,sackOK,eol], length 0
13:51:40.966266 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [S.], seq 466300160, ack 1328522650, win 28960, options [mss 1460,sackOK,TS val 2928333455 ecr 555445081,nop,wscale 7], length 0
13:51:40.966630 IP 114.242.250.59.41034 > 172.17.51.219.443: Flags [S], seq 3150370905, win 65535, options [mss 1360,nop,wscale 6,nop,nop,TS val 555445081 ecr 0,sackOK,eol], length 0
13:51:40.966650 IP 172.17.51.219.443 > 114.242.250.59.41034: Flags [S.], seq 3538757503, ack 3150370906, win 28960, options [mss 1460,sackOK,TS val 2928333455 ecr 555445081,nop,wscale 7], length 0
13:51:40.986348 IP 114.242.250.59.41034 > 172.17.51.219.443: Flags [.], ack 3538757504, win 2064, options [nop,nop,TS val 555445117 ecr 2928333455], length 0
13:51:40.986539 IP 114.242.250.59.41032 > 172.17.51.219.443: Flags [.], ack 137328988, win 2061, options [nop,nop,TS val 555445117 ecr 2928333454], length 0
13:51:40.991849 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [.], ack 466300161, win 2064, options [nop,nop,TS val 555445118 ecr 2928333455], length 0
13:51:40.994419 IP 114.242.250.59.41034 > 172.17.51.219.443: Flags [P.], seq 3150370906:3150371429, ack 3538757504, win 2064, options [nop,nop,TS val 555445118 ecr 2928333455], length 523
13:51:40.994460 IP 172.17.51.219.443 > 114.242.250.59.41034: Flags [.], ack 3150371429, win 235, options [nop,nop,TS val 2928333483 ecr 555445118], length 0
13:51:40.995615 IP 172.17.51.219.443 > 114.242.250.59.41034: Flags [.], seq 3538757504:3538760200, ack 3150371429, win 235, options [nop,nop,TS val 2928333484 ecr 555445118], length 2696
13:51:40.995621 IP 172.17.51.219.443 > 114.242.250.59.41034: Flags [P.], seq 3538760200:3538760565, ack 3150371429, win 235, options [nop,nop,TS val 2928333484 ecr 555445118], length 365
13:51:40.996812 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [P.], seq 1328522650:1328523173, ack 466300161, win 2064, options [nop,nop,TS val 555445118 ecr 2928333455], length 523
13:51:40.996850 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [.], ack 1328523173, win 235, options [nop,nop,TS val 2928333485 ecr 555445118], length 0
13:51:40.997984 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [.], seq 466300161:466302857, ack 1328523173, win 235, options [nop,nop,TS val 2928333487 ecr 555445118], length 2696
13:51:40.997991 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [P.], seq 466302857:466303222, ack 1328523173, win 235, options [nop,nop,TS val 2928333487 ecr 555445118], length 365
13:51:41.024807 IP 114.242.250.59.41034 > 172.17.51.219.443: Flags [.], ack 3538760200, win 2026, options [nop,nop,TS val 555445154 ecr 2928333484], length 0
13:51:41.024809 IP 114.242.250.59.41034 > 172.17.51.219.443: Flags [.], ack 3538760565, win 2021, options [nop,nop,TS val 555445154 ecr 2928333484], length 0
13:51:41.029955 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [.], ack 466302857, win 2022, options [nop,nop,TS val 555445154 ecr 2928333487], length 0
13:51:41.029957 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [.], ack 466303222, win 2016, options [nop,nop,TS val 555445154 ecr 2928333487], length 0
13:51:41.032718 IP 114.242.250.59.41034 > 172.17.51.219.443: Flags [P.], seq 3150371429:3150371522, ack 3538760565, win 2048, options [nop,nop,TS val 555445155 ecr 2928333484], length 93
13:51:41.033098 IP 172.17.51.219.443 > 114.242.250.59.41034: Flags [P.], seq 3538760565:3538760839, ack 3150371522, win 235, options [nop,nop,TS val 2928333522 ecr 555445155], length 274
13:51:41.033172 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [P.], seq 1328523173:1328523266, ack 466303222, win 2048, options [nop,nop,TS val 555445156 ecr 2928333487], length 93
13:51:41.033374 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [P.], seq 466303222:466303496, ack 1328523266, win 235, options [nop,nop,TS val 2928333522 ecr 555445156], length 274
13:51:41.035404 IP 114.242.250.59.41034 > 172.17.51.219.443: Flags [P.], seq 3150371522:3150372299, ack 3538760565, win 2048, options [nop,nop,TS val 555445156 ecr 2928333484], length 777
13:51:41.035512 IP 172.17.51.219.443 > 114.242.250.59.41034: Flags [P.], seq 3538760839:3538761043, ack 3150372299, win 247, options [nop,nop,TS val 2928333524 ecr 555445156], length 204
13:51:41.065435 IP 114.242.250.59.41034 > 172.17.51.219.443: Flags [.], ack 3538760839, win 2043, options [nop,nop,TS val 555445192 ecr 2928333522], length 0
13:51:41.065920 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [.], ack 466303496, win 2043, options [nop,nop,TS val 555445192 ecr 2928333522], length 0
13:51:41.076415 IP 114.242.250.59.41034 > 172.17.51.219.443: Flags [.], ack 3538761043, win 2044, options [nop,nop,TS val 555445200 ecr 2928333524], length 0
^C
30 packets captured
30 packets received by filter
0 packets dropped by kernel
4 清洗数据
一个TCP链接是由双端定义的 tcp := {sourceHost:port , dstHost:port },所以从上面的输出中过滤出 属于同一个tcp链接的,比较完整的往来数据,比如选择 {172.17.51.219:443 , 114.242.250.59:41033}
同时,首部中的 win部分、options部分 对于验证三次握手无关,同样可以屏蔽.屏蔽完次要信息之后通信流程变的明朗.
[root@ ~]$>tcpdump -i eth0 '((host 172.17.51.219 or 114.242.250.59) and port 443)' -nn -S
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
时间戳 发送端IP:port 接收端IP:port 标志位 发送序列,接收序列 ...
13:51:40.966228 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [S], seq 1328522649, length 0
13:51:40.966266 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [S.], seq 466300160, ack 1328522650, length 0
13:51:40.991849 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [.], ack 466300161, length 0
13:51:40.996812 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [P.], seq 1328522650:1328523173, ack 466300161, length 523
13:51:40.996850 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [.], ack 1328523173, length 0
13:51:40.997984 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [.], seq 466300161:466302857, ack 1328523173, length 2696
13:51:40.997991 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [P.], seq 466302857:466303222, ack 1328523173, length 365
13:51:41.029955 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [.], ack 466302857, length 0
13:51:41.029957 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [.], ack 466303222, length 0
13:51:41.033172 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [P.], seq 1328523173:1328523266, ack 466303222, length 93
13:51:41.033374 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [P.], seq 466303222:466303496, ack 1328523266, length 274
13:51:41.065920 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [.], ack 466303496, length 0
...
5 分析三次握手
按时间顺序逐条解释报文
13:51:40.966228 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [S], seq 1328522649, length 0
浏览器向服务器发送创建TCP链接请求, 发送序列号seq=1328522649
(逻辑上为1328522649:1328522649代表从第1328522649个字节开始到第1328522649截止),
因为本包的目的为请求创建TCP链接,所以并未在数据部分填充内容.所以 tcpdump解析出实体数据部分 length=0
并且此时将首部Flags中的SYN
位设置为1,其它五位(包括ACK
位)设为0 ,
当标志位中SYN=1 && ACK=0
时,代表本包是一个连接请求报文, tcpdump
工具只会把标志位不为0的部分解析展示在[]
中,
比如本次的标志位[S]
.然后客户端进入SYN-SENT
同步已发送
状态
13:51:40.966266 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [S.], seq 466300160, ack 1328522650, length 0
服务器收到客户端连接请求后,同意建立连接 返回响应报文,同时将标志位中 SYN位、ACK位置为1(ACk标志位用符号点表示,SYN
跟ACK
组合起来就是[S.]
),其它位置为0
当 SYN=1 && ACK=1
时,表示这个连接接收报文,此时握手还未完成 所以数据长度 length=0
此时服务器要发送确认序列号ack
(注意这个ack不是标志位中的ACK),这个ack代表的意思为,服务器期望浏览器下一个报文的发送序列号从ack开始,而这个ack等于上一条浏览器发送服务器的报文中的seq+1.与此同时,服务器也要向浏览器发送数据,所以服务器的发送序列号seq=y(这里是466300160)
代表,服务器要从自己的输出缓存中的第y个字节开始发送.此时服务器进程进入SYN-RCVD
同步收到
状态
13:51:40.991849 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [.], ack 466300161, length 0
浏览器接收到服务器的响应报文后,验证服务器的确认号ack
是否相等(1328522649 + 1 = 1328522650
) 通过验证后,
浏览器知晓服务器同意了自己的连接请求,此时浏览器还要向服务器发送应答号 ack=服务器.seq + 1
这一步是为避免第一个请求连接出现异常发生意外情况,
此时TCP连接创建完成.客户端进程进入ESTABLISHED``连接已建立
状态.当服务端收到浏览器的确认应答后,也进入ESTABLISHED
状态
13:51:40.996812 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [P.], seq 1328522650:1328523173, ack 466300161, length 523
浏览器发送完连接请求接受的确认ack之后,马上开始发送带有数据的报文,
标志位P
设为1 代表,立刻将输出buffer中的数据发送到服务器,不要等待buffer中的数据累积到一定规模再发送,
同时本报文同样要携带ack=服务器.seq + 1
此时浏览器发送的数据长度为 length = seq边界1328522650:1328523173之差:523
13:51:40.996850 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [.], ack 1328523173, length 0
13:51:40.997984 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [.], seq 466300161:466302857, ack 1328523173, length 2696
13:51:40.997991 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [P.], seq 466302857:466303222, ack 1328523173, length 365
13:51:41.029955 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [.], ack 466302857, length 0
13:51:41.029957 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [.], ack 466303222, length 0
13:51:41.033172 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [P.], seq 1328523173:1328523266, ack 466303222, length 93
13:51:41.033374 IP 172.17.51.219.443 > 114.242.250.59.41033: Flags [P.], seq 466303222:466303496, ack 1328523266, length 274
13:51:41.065920 IP 114.242.250.59.41033 > 172.17.51.219.443: Flags [.], ack 466303496, length 0
...
这一部分属于常规数据发送接收的通信了,总体来说就是 服务器向浏览器发送一个报文,里面携带数据,浏览器返回一个报文,表示已经收到你刚才发送的数据,
如此往复循环有时 发送跟确认报文可能不是紧挨着的,这是因为TCP的滑动窗口协议.TCP使用确认和重传机制,在不可靠的传输网络上实现可靠的通信
对于IM应用,一个服务端口可以同时与N个客户端口进行通讯,因为服务器端进程维护着一个TCB(Transmission Control Block),这里面存储了每一个连接的
的一些重要信息,比如TCP连接表,指向发送和接收缓存的指针等等…,而不用为每一个来自客户端的TCP连接分配一个端口.
为什么连接建立需要三次握手,而不是两次握手?
防止失效的连接请求报文段被服务端接收,从而产生错误。
PS:失效的连接请求:若客户端向服务端发送的连接请求丢失,客户端等待应答超时后就会再次发送连接请求,此时,上一个连接请求就是『失效的』。
若建立连接只需两次握手,客户端并没有太大的变化,仍然需要获得服务端的应答后才进入ESTABLISHED状态,而服务端在收到连接请求后就进入ESTABLISHED状态。此时如果网络拥塞,客户端发送的连接请求迟迟到不了服务端,客户端便超时重发请求,如果服务端正确接收并确认应答,双方便开始通信,通信结束后释放连接。此时,如果那个失效的连接请求抵达了服务端,由于只有两次握手,服务端收到请求就会进入ESTABLISHED状态,等待发送数据或主动发送数据。但此时的客户端早已进入CLOSED状态,服务端将会一直等待下去,这样浪费服务端连接资源。
TCP连接的释放
TCP连接的释放比连接创建要稍微复杂一点,通常
一个完整的释放过程要交换四组报文.
客户端tcpdump监测结果
[root@ ~]sudo tcpdump -i en0 '((host 39.106.58.35 or 114.242.249.148) and port 443)' -nn -S
20:18:58.450967 IP 192.168.43.226.54739 > 39.106.58.35.443: Flags [F.], seq 1024508534, ack 2329885927, length 0
20:18:58.520988 IP 39.106.58.35.443 > 192.168.43.226.54739: Flags [F.], seq 2329885927, ack 1024508535, length 0
20:18:58.521051 IP 192.168.43.226.54739 > 39.106.58.35.443: Flags [.], ack 2329885928, length 0
39.106.58.35是服务端的外网IP ,可以视为172.17.51.219.接下来分析一下报文
20:18:58.450967 IP 192.168.43.226.54739 > 39.106.58.35.443: Flags [F.], seq 1024508534, ack 2329885927, length 0
首先,客户端主动向服务端发送连接释放报文,标志位F
设为1,seq
为1024508534
,发送报文后,客户端从ESTABLISHED
进入FIN-WAIT-1
状态,(这一步可以捎带数据的哈).
20:18:58.520988 IP 39.106.58.35.443 > 192.168.43.226.54739: Flags [.], seq 2329885927, ack 1024508535, length 0
服务端接收到释放报文后,向客户端返回确认ack=1024508534+1
,同时消耗一个seq=2329885927
,发送确认报文后,服务端从ESTABLISHED
状态进入CLOSE-WAIT
状态
此时 从客户端到服务端
这个方向的TCPL连接就单向释放了(TCP是全双工通信,信息发送是双向的).也就是说,客户端已经没有数据
要发送到服务端(注意是不能发数据,但是需要发送ACK),
此时TCP连接处于半关闭状态(half-closed),但是服务端向客户端方向的TCP依然的连通的,仍然可以发送数据.
20:18:58.520999 IP 39.106.58.35.443 > 192.168.43.226.54739: Flags [F.], seq 2329885927, ack 1024508535, length 0
此时,服务端的上层服务进程(比如说Nginx)确定可以没有其他数据需要发送,按协议栈向下传递通知TCP通知对端(也就是客户端浏览器),此时将标志位F
置为1,ack
跟上一条服务端发送给客户端的
ACK报文中ack
值保持一致.此时服务端进入LAST-ACK
(最后确认)状态.等待客户端的确认.!!!注意!!!:紧挨着的这两条共享的ack
值的报文可以合并为一条报文,F
可以捎带上面的ACK
.
当收到客户端的确认后,服务端进入CLOSED
状态,服务端进入CLOSED
状态要早于客户端
20:18:58.521051 IP 192.168.43.226.54739 > 39.106.58.35.443: Flags [.], ack 2329885928, length 0
客户端收到服务端的确认后,客户端从FIN-WAIT-1
进入FIN-WAIT-2
状态,同时发出确认报文,必须将ACK
置为1,然后进入到TIME-WAIT
状态,等待2MSL
(2 * MSL)后,客户端才进入CLOSED
状态.
当客户端撤销了本次的TCB(传输控制块)后,本次TCP连接才真正结束.
这是对应服务端的监测结果,以供对照
[root@ ~]$>tcpdump -i eth0 '((host 172.17.51.219 or 114.242.250.59) and port 443)' -nn -S
20:18:58.495313 IP 114.242.249.148.58188 > 172.17.51.219.443: Flags [F.], seq 1024508534, ack 2329885927,length 0
20:18:58.495476 IP 172.17.51.219.443 > 114.242.249.148.58188: Flags [F.], seq 2329885927, ack 1024508535,length 0
20:18:58.527252 IP 114.242.249.148.58188 > 172.17.51.219.443: Flags [.], ack 2329885928, length 0
有的时候,客户端会因为某种故障突然失去联系,比如断电断网等等,此时服务器还在等待来自客户端的报文,这就要用到TCP的保活计时器(keepalive timer),服务器每一次收到客户端的数据,
就重置timer,通常为两小时,若两小时没有收到来自客户的数据,则发送探测报文段,每隔75秒发送一次,若10个报文段之后仍无响应,服务器就关闭这个连接.
还有一点需要注意,不论客户端还是服务端都可以执行主动关闭,但是通常都为客户端,由服务端主动关闭的例子如:HTTP1.0
重点
重点
- tcp的每一个报文发送后都需要确认(停止-等待协议).
- 按照我的理解:一个完整的TCP连接创建释放过程
至少
需要6个包(这是错误的,看下文解释). - 一个好的文章读了以后会给人一种茅塞顿开感觉,但是你要先塞住,才能顿开.
解释上文:6个包意味着挥手需要3个包,而不是4个包,我认为需要三个包是因为我认为挥手时F+ACK
是可以合并的.但是事实并非如此.
为什么挥手时需要四次呢?
关闭连接时,当收到对方的FIN
报文通知时,他仅仅表示对方没有数据发送给你了,但未必你的所有数据都全部发送给对方了.
所以你可以不是马上回关闭socket,即你可能还会发送一些数据给对方之后,在发送FIN报文给对方来表示你同意现在可以关闭连接了,
所以挥手时的ACK和FIN报文多情况下都是分开发送的.
参考资料:
- 计算机网络(第7版)
- Unix网络编程卷1