目录
1. 通过netstat来分析服务器和客户端的TCP状态
2.通过tcpdump抓包分析服务器和客户端的TCP状态
2.1 语法
2.2 抓包返回格式
2.3 抓包的FLags标记
2.4 分析三次握手,数据收发,四次挥手的状态
2.4.1三次握手
2.4.2数据发送
2.4.3 四次挥手
理论篇:
(一)深入浅出TCPIP之理解TCP报文格式和交互流程
(二)深入浅出TCPIP之再识TCP,理解TCP三次握手(上)
(三)深入浅出TCPIP之再识TCP,理解TCP四次挥手(上)
(四)深入浅出TCPIP之TCP三次握手和四次挥手(下)的抓包分析
(五)深入浅出TCPIP之TCP流量控制
(六)深入浅出TCPIP之TCP拥塞控制
(七)深入浅出TCPIP之深入浅出TCPIP之TCP重传机制
(八)深入浅出TCPIP之TCP长连接与短连接详解
(九)深入浅出TCPIP之网络同步异步
(十)深入浅出TCPIP之网络阻塞和非阻塞
(十一)深入浅出TCPIP之TCP粘包问题
(十二)深入浅出TCPIP之Nagle算法
(十三) 深入浅出TCPIP之TCP套接字参数
(十四)深入浅出TCPIP之初识UDP理解报文格式和交互流程
(十五)非常全面的TCPIP面试宝典-进入大厂必备总结
(十六)深入浅出TCPIP之Hello CDN
....
(二十)深入浅出TCPIP之epoll的一些思考
实践篇:
深入浅出TCPIP之实战篇—用c++开发一个http服务器(二十一)
其他实践篇+游戏开发中的网络问题疑难杂症解读 正在完善。。。
我们在第二章和第三章讲了三次握手和四次挥手,那么这一章节我将带领读者来通过tcpdump工具来抓包分析这两个过程。
按照我第一章文章里初识TCP,理解TCP报文格式和交互流程(一)提供的客户端和服务器代码进行本次试验。
首先编译客户端和服务器代码:
gcc -g -o server server.c
gcc -g -o client client.c
关于netstat的用法我这里不赘述,网上可以找到大量的学习资料。
1.先启动服务器:
2.查看服务器的端口状态:
此时服务器的端口在调用listen之后开始监听客户端的连接,此时服务器的连接状态变成LISTEN。
3.启动客户端:
4.查看服务器和客户端的端口状态
这里注意,我们发现多了两行记录,第二行代表的是服务器本地已经连接到客户端,并且监听的客户端端口是58344,第三行代表的是本地客户端端口是58344,连接的是远端ip为172.16.0.9,端口为8888的服务器。既然新增的两个状态都是ESTABLISHED说明双方三次握手已经建立并正在通信。
5.主动关闭掉客户端查看端口状态
我们发现客户端当前的状态发生了变化 FIN_WAIT2,实际上这就是著名的半关闭的状态了,这是在客户端断开的时候发送了FIN包,服务端处于CLOSE_WAIT状态。为什么会发生这个变化,可以我们参考 TCP,理解TCP报文格式和交互流程(一)
存在CLOSE_WAIT的原因
CLOSE_WAIT这个状态存在于服务端,当服务端发送FIN(之前客户端已经发送过fin),请求关闭连接之后进入CLOSE_WAIT,然而没有收到客户端的响应,可能由于客户端掉线了(如网络故障或者掉电),没有及时给予客户端回复造成问题。
或者由于客户端已经调用close(socket)退出,而服务端对其监测并断开连接,这种是服务端问题。
解决方法:一般是编程问题,可用KEEP_ALIVE机制加以解决
存在FIN_WAIT2的原因
这个状态存在于主动发起断开请求的一端,如果服务器存在大量的这个状态,那么这个服务器就充当客户端的角色,如网络爬虫,出现的原因是由于客户端发起FIN请求结束连接之后,收到了服务端的应答之后进入FIN_WAIT2,之后就没收到服务端发送的FIN信号导致。
解决方法:可以配置FIN_WAIT2的时长,当超过时长后自动断开加以解决
我想这个问题你应该明白了。
6.主动关闭服务器
如果我们先关掉服务器呢,看下现在的状态:
现在客户端的状态怎么变成了CLOSE_WAIT呢,实际上产生CLOSE_WAIT状态的一方,是属于被动关闭的一方,用简单的话对解释上图(主动关闭方为A,被动关闭方为B):
A发一条FIN(关闭)请求给B,说我要关闭了; B回应一条ACK(确认)请求给A,说我知道了,你关吧,此时B就会进行CLOSE_WAIT状态; B发送一条FIN(关闭)请给A,说我要关闭了; A收到发送一条ACK(确认)消息说,你关闭吧。 |
上面四次握手完成后,双方的连接就都关闭了,但是这里在客户端产生了CLOSE_WAIT现象,首先可以确定的是服务端主动关闭的连接,且客户端没有给服务端发送关闭的请求(第三次握手请求),就会一直处在CLOSE_WAIT的状态,可是客户端为什么不向服务端发送关闭的请求,它当时在忙什么呢,原来服务端在调用关闭时,而客户端正在执行RECV(数据接收),这时候有可能服务端发送的FIN包客户端接收出错,就是由TCP代回了一个ACK包,所以客户端就会处在CLOSE_WAIT的状态中。
类型的关键字
host(缺省类型): 指明一台主机,如:tcpdump host 210.27.48.2
net: 指明一个网络地址,如:tcpdump net 202.0.0.0
port: 指明端口号,如:tcpdump port 23
确定方向的关键字
src: src 210.27.48.2, IP包源地址是210.27.48.2
dst: dst net 202.0.0.0, 目标网络地址是202.0.0.0
dst or src(缺省值)
dst and src
协议的关键字:缺省值是监听所有协议的信息包
其他更多用法通过tcpdump用法查看。
16:38:11.960274 IP 106.53.211.6.54296 > VM_0_9_centos.ddi-tcp-1: Flags [P.], seq 2552352431:2552352437, ack 1375985570, win 229, options [nop,nop,TS val 585232425 ecr 585202018], length 6
16:38:11.960274
●网络包发生的时间
IP 106.53.211.6.54296 > VM_0_9_centos.ddi-tcp-1
●IP标识
●源ip或者源主机名和端口54296;
●>流向符,数据包从左边发往右边
●目的ip或者目的主机名和端口443
Flags [P.]
●Flags的标记,此处为[P.]数据推送
seq 2552352431:2552352437, ack 1375985570, win 229, options [nop,nop,TS val 585232425 ecr 585202018], length 6
●seq为序列号
●ack为确认码
●win为滑动窗口大小
●length为承载的数据(payload)长度length, 如果没有数据则为0
tcpdump的Flags代表了这个数据包的用途,这些标记是TCP首部的内容
[S]: SYN同步标识
[.]: .表示ACK确认标识
[S.]: SYN同步标识,以及确认[S]的ACK
[P.]: PSH,push推送, 数据传输
[R.]: RST,连接重置
[F]: FIN结束连接
[DF]: Don't Fragment ,当DF=0时, 示允许分片,一般-v时才有这个标识
[FP.]:标记FIN、PUSH、 ACK组合,这样做是为了提升网络效率,减少数据来回确认等
客户端和服务器启动:
服务器端:
[root@VM_0_9_centos unit_test]# tcpdump -i eth0 port 8888
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
17:38:17.816221 IP 120.132.11.203.48096 > VM_0_9_centos.ddi-tcp-1: Flags [S], seq 734888645, win 65044, options [mss 1412,sackOK,TS val 1928888411 ecr 0,nop,wscale 7], length 0
17:38:17.816395 IP VM_0_9_centos.ddi-tcp-1 > 120.132.11.203.48096: Flags [S.], seq 1632166146, ack 734888646, win 28960, options [mss 1460,sackOK,TS val 588838281 ecr 1928888411,nop,wscale 7], length 0
17:38:17.848272 IP 120.132.11.203.48096 > VM_0_9_centos.ddi-tcp-1: Flags [.], ack 1, win 509, options [nop,nop,TS val 1928888444 ecr 588838281], length 0
客户端:
root@10-23-185-135:/home/ubuntu# tcpdump -i eth0 port 8888
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
17:38:17.797373 IP 10.23.185.135.48096 > 106.53.211.6.8888: Flags [S], seq 734888645, win 65044, options [mss 1414,sackOK,TS val 1928888411 ecr 0,nop,wscale 7], length 0
17:38:17.830081 IP 106.53.211.6.8888 > 10.23.185.135.48096: Flags [S.], seq 1632166146, ack 734888646, win 28960, options [mss 1412,sackOK,TS val 588838281 ecr 1928888411,nop,wscale 7], length 0
17:38:17.830139 IP 10.23.185.135.48096 > 106.53.211.6.8888: Flags [.], ack 1, win 509, options [nop,nop,TS val 1928888444 ecr 588838281], length 0
通过发送时间上看到:
1.先是在17:38:17.797373的时候客户端向服务器发送SYN序列,序列号是734888645( Falgs[S] 表明是SYN同步标识 )
2.服务器在17:38:17.816221的时候收到客户端的请求SYN序列734888645( Falgs[S] 表明是SYN同步标识 ),接着服务器向客户端发送序列号seq 1632166146, ack 734888646, 此时客户端在17:38:17.830081的时候收到了该次SYN同步标识以及确认[S]的ACK,seq 1632166146, ack 734888646。
3.客户端收到SYN同步标识以及确认[S]的ACK之后,在17:38:17.830139向服务器发送ACK确认,ack 1。 此时服务器也收到该次确认17:38:17.848272 IP 120.132.11.203.48096 > VM_0_9_centos.ddi-tcp-1: Flags [.], ack 1
整个三次握手就是这样建立的。
客户端:
17:38:46.885233 IP 10.23.185.135.48096 > 106.53.211.6.8888: Flags [P.], seq 1:11, ack 1, win 509, options [nop,nop,TS val 1928917499 ecr 588838281], length 10
17:38:46.918039 IP 106.53.211.6.8888 > 10.23.185.135.48096: Flags [.], ack 11, win 227, options [nop,nop,TS val 588867369 ecr 1928917499], length 0
服务器:
17:38:46.904117 IP 120.132.11.203.48096 > VM_0_9_centos.ddi-tcp-1: Flags [P.], seq 1:11, ack 1, win 509, options [nop,nop,TS val 1928917499 ecr 588838281], length 10
17:38:46.904182 IP VM_0_9_centos.ddi-tcp-1 > 120.132.11.203.48096: Flags [.], ack 11, win 227, options [nop,nop,TS val 588867369 ecr 1928917499], length 0
顺水推舟,这里就不赘述了,想必大家也根据Flags看到当前的状态,以及发送的包长 (发送的是”helloworld“ ,所以length是10)
客户端:
18:08:10.116986 IP 10.23.185.135.48206 > 106.53.211.6.8888: Flags [F.], seq 1, ack 1, win 509, options [nop,nop,TS val 1930680740 ecr 590615825], length 0
18:08:10.147985 IP 106.53.211.6.8888 > 10.23.185.135.48206: Flags [.], ack 2, win 227, options [nop,nop,TS val 590630604 ecr 1930680740], length 0
服务器:
18:08:10.138686 IP 120.132.11.203.48206 > VM_0_9_centos.ddi-tcp-1: Flags [F.], seq 1, ack 1, win 509, options [nop,nop,TS val 1930680740 ecr 590615825], length 0
18:08:10.139077 IP VM_0_9_centos.ddi-tcp-1 > 120.132.11.203.48206: Flags [.], ack 2, win 227, options [nop,nop,TS val 590630604 ecr 1930680740], length 0
由于此时是客户端主动断开连接,向服务器发送了FIN结束连接 ,而且服务器端也没有需要给客户端发送的数据处理,因此直接断开连接发送ACK确认。