
- 在windows平台下,可以使用wireshark软件来抓取网络数据包
- 在linux平台下,使用 tcpdump 来抓取网络数据,TCP和UDP的包都可以使用此命令抓取 1)万能公式:tcpdump -i any port [端口] -s 0 -w xxx.dat 2)使用root用户进行抓包 如上2)中的命令是对某个端口进行抓包,并将结果放入xxx.dat中,但我们不能直接对xxx.dat中的数据进行分析,需要借助windows平台下的wireshark软件来分析
我们使用上篇socket编程写的单进程的客户端和服务端的代码,使用tcpdump抓包分析三次握手的过程。
首先在root用户下输入 tcpdump -i any port 20000 -s 0 -w tcp.dat ,先进行抓包,再让客户端服务端程序跑起来,防止三次握手结束,没有抓到,让客户端服务端跑一会后终止程序,我们可以看到如下图所示: 可以看到一共抓到了84个数据包,并产生了一个tcp.dat文件,通过rz tcp.dat将tcp.dat传到windows下使用wireshark进行分析,如下图所示:
为什么TCP需要包序号?
本质上是为了维护可靠传输,客户端维护了一套序号,服务端也维护了一套序号
- client–>server:消耗(seq)的是客户端维护的序号,服务端告诉客户端自己收到数据的时候,是确认(ACK)客户端的序号 - server–>client:消耗(seq)的是服务端维护的序号,客户端告诉服务端自己收到数据的时候,是确认(ACK)服务端的序号。
观察如下图所示抓到的包,分析TCP包序号的变化: 分析如下图所示:
- 纯ACK数据包不消耗序号
- TCP数据也消耗序号,一个字节消耗一个序号
- 确认序号ACK=消息发送方的序号+数据长度
- 确认序号的作用是:告知消息发送方,期望下次发送数据从哪一个序号开始发送 注意:TCP三次握手中,协商双方的其实序号并不一定是从0号序号开始,可以从任意位置开始,只要双方协商好就行。 TCP可靠的原因就在于序号,丢了那个数据都可以知道,因为一个字节占一个序号。
通过数据包名和连接双方的状态分析四次挥手的过程如下图所示:
数据传输完毕以后,双方都可以释放连接。在最开始的时候,客户端与服务器都是处于ESTABLISHED状态,如果客户端主动关闭,则服务端被动关闭;若服务端主动关闭,则客户端被动关闭。
- 客户端状态转化:
- [ESTABLISHED -> FIN_WAIT_1] 客户端主动调用close时, 向服务器发送结束报文段, 同时进入FIN_WAIT_1。
- [FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认, 则进入FIN_WAIT_2, 开始等待服务器的结束报文段。
- [FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文段, 进入TIME_WAIT, 并发出LAST_ACK。
- [TIME_WAIT -> CLOSED] 客户端要等待一个2MSL(Max Segment Life, 报文最大生存时间)的时间, 才会进入CLOSED状态。
- 服务端状态转化:
- [ESTABLISHED -> CLOSE_WAIT] 当客户端主动关闭连接(调用close), 服务器会收到结束报文段, 服务器返回确认报文段并进入CLOSE_WAIT。
- [CLOSE_WAIT -> LAST_ACK] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据); 当服务器真正调用close关闭连接时, 会向客户端发送FIN, 此时服务器进入LAST_ACK状态, 等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)。
- [LAST_ACK -> CLOSED] 服务器收到了对FIN的ACK, 彻底关闭连接。
现在做一个测试,首先启动server,然后启动client,然后用Ctrl-C使server终止,这时马上再运行server, 结果是:
- TCP协议规定,主动关闭连接的一方要处于TIME_ WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态。
- 我们使用Ctrl-C终止了server, 所以server是主动关闭连接的一方, 在TIME_WAIT期间仍然不能再次监听同样的server端口。
- 主动断开方为TIME_WAIT状态为什么要等待2MSL后才变为CLOSED状态呢?
当主动断开方为TIME_WAIT状态时,如果主动断开方发送的ACK数据包丢失,则过MSL后,被动连接方会重新发送FIN数据包,让主动断开连接方重新发送ACK数据包,若主动断开连接方MSL后将状态变为CLOSED则无法收到重发的FIN数据包,无法重新发送ACK数据包。
2MSL = 丢失的ACK的MSL + 重传的FIN的MSL
本质就是为了让主动断开连接方能够接收到被动断开连接方重传的FIN报文。
- MSL是TCP报文的最大生存时间, 因此TIME_WAIT持续存在2MSL的话就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错误的)。
- 同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失, 那么服务器会再重发一个FIN. 这时虽然客户端的进程不在了, 但是TCP连接还在, 仍然可以重发LAST_ACK)。
TIME_WAIT状态存在于主动断开连接方
如果服务端位于主动断开连接方,此时一定在四次握手的过程中拥有TIME_WAIT状态;即使当服务端进程已经结束了,但是服务端之前使用TCP协议针对针对的连接还是TIME_WAIT状态,换句话说,服务端之前绑定的端口还没有被网络协议栈的TCP协议释放掉,导致服务端无法快速的重启(端口已被站用问题) 解决办法:使用setsockopt函数,让端口重用 int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); sockfd:侦听套接字listen_fd level:SOL_SOCKET 套接字选项 optname:SO_REUSEADDR 重用端口(只有当服务端的状态为TIME_WAIT时才能重用) optval:1 optlen:opt的大小
假设客户端主动断开连接,服务端被动断开连接。
1)客户端进程发出连接释放报文FIN=1,并且停止发送数据。此时,客户端进入FIN-WAIT1(终止等待1)状态。这时候客户端处于一个半关闭的状态,即客户端已经没有数据需要发送了,但是服务器若要发送数据,客户端依然需要接受。 2)服务器收到连接释放报文后,发送确认报文ACK=1。此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。进入CLOSE_WAIT后说明服务器准备关闭连接。 3)客户端收到服务器的确认请求之后,此时客户端就进入了FIN-WAIT2(终止等待2)状态,等待服务器发送连接释放报文。(在这个之前还需要接受服务器发送的最后的数据)。 4)当服务器真正调用close关闭连接时, 会向客户端发送FIN=1, 此时服务器进入LAST_ACK(最后确认)状态, 等待客户端的最后一次ACK回复。 5)客户端收到服务器的链接释放报文之后,必须发出确认报文ACK=1。此时,客户端就进入了TIME-WAIT(时间等待)状态,等待用户关闭套接字。注意此时TCP链接还没有释放,必须经过2*MSL(报文最大生命周期)的时间后,当客户端撤销相应的TCP后,才进入CLOSED状态。 6)服务器只要收到客户端发出的确认,彻底关闭连接,立即就进行CLOSED状态,于是就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
小结: 对于服务器上出现大量的 CLOSE_WAIT 状态, 原因就是服务器没有正确的关闭 socket, 导致四次挥手没有正确完成. 这是一个 BUG. 只需要加上对应的 close 即可解决问题。
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?