介绍三次握手的时候,着重了解的是三次握手过程中,应用层和传输层做了哪些工作;下面在了解四次挥手的时候,主要了解的是四次挥手的状态变化。
目录
一般是客户端主动断开连接,偶尔服务端也可以断开连接。假设这里是客户端先断开连接
当Client 调用close函数的时候,就是Client发送断开连接请求的时候。发送的报文中携带FIN标志位。
客户端状态:FIN_WAIT_1(等待服务端的确认应答)
服务端接收到断开连接的请求以后,服务端同意断开连接并发送确认应答ACK。发送完ACK以后,Server端变为CLOSE_WAIT状态,CLOSE_WAIT状态表明四次挥手没有完全走完,或者说连接还没有完全断开。
服务端状态:CLOSE_WAIT(连接还没有完全断开,等待服务端主动调用close函数断开连接)
客户端收到服务端返回的ACK,便认为自己已经断开连接了。状态变为 FIN_WAIT2,为什么不是CLOSED?因为此时只是客户端单方面的断开了连接,代表客户端不能给服务端发送数据,但是可以接收来自服务端的数据!因此,客户端在等待服务端主动断开连接。
客户端状态:FIN_WAIT2(等待服务端主动断开连接)
第三次挥手的时候,就是服务端主动调用close函数的时候。当服务端发送断开连接的请求时,状态变为LAST_ACK ,发送的报文中携带FIN标记位。
服务端状态:LAST_ACK
客户端终于等来了服务端的FIN请求,接收到请求以后发送确认应答ACK,此时客户端进入TIME_WAIT状态,为什么不是CLOSED? 因为此时在网络中存在服务端发给客户端的数据,客户端需要等待一段时间来把这些数据尽可能接收完。等待一定时间后,客户端就会进入CLOSED状态。
客户端状态:TIME_WAIT(等待一段时间后变为CLOSED状态)
服务端一旦接收到ACK,就会进入到CLOSED状态,此时服务端到客户端方向上的连接才算完全断开。
服务端状态:CLOSED
TCP协议规定,主动断开连接的一方,需要进入TIME_WAIT状态。就像上面那样,客户端主动断开连接后,先是等待服务端断开连接,当接收到服务端的断开连接的请求以后,需要进入TIME_WAIT状态,等待的时长为2MSL。
在客户端主动断开了一个方向上的连接以后,此时客户端只能接收来自服务端的数据;当服务端的业务处理完以后,主动调用close函数时,便会发送断开连接的请求,客户端接收到携带FIN标志位的报文并给予确认应答以后,便可以进入TIME_WAIT状态了。
此时尽管收到了服务端断开连接的请求,但是可能还有些数据还留在网络中尚未被接收,因此主动断开连接的一方要做的收尾工作就是尽可能接收还在网络中的数据。
MSL是TCP报文的最大生存时间,这个相当于经过多次测试得到的结果,你可以理解为从TCP报文从一端传递到另一端的最长所需时间。至于为什么是2MSL,原因有两个。
第一,尽量保证接收到所有的数据。断开连接的时候,可能存在未被接收的数据滞留在网络中,可能有些才刚刚被发出,2MSL正好保证了 从服务端发送 到 服务端收到应答的时间,即一来一回。
第二,尽量保证最后一个ACK被对方收到。如果在TIME_WAIT期间收到对方的第二次FIN,说明上一个发送的ACK丢了,此时处在TIME_WAIT状态,会重新发送一次ACK。
补充:MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同, 在Centos7上默认配置的值是60s
有的时候,我们因为某种原因主动关闭服务端(比如Ctrl+C发送终止进程的信号),短时间内再次运行服务端,会遇到bind error的情况。服务端主动断开的连接,此时会进入TIME_WAIT状态,端口会被一直占用。一个端口号不能绑定多个进程,若我们再次运行,就会出现绑定失败的情况。
注意:这里是服务端主动断开连接,不是客户端!客户端的端口号一般是OS随机分配的,不会出现重复占用的情况;但是服务端的端口号是固定的,如果关闭服务端又立即重启,就会出现bind error。
这就需要用到一个系统调用接口函数setsockopt,我们可以设置成地址复用,这样的话,服务端进入TIME_WAIT,那就让上一个进程继续等吧,端口号就先被下一个进程使用。
我们只需要在建立连接的之前调用 setsocket函数即可。使用方式如下:
- int listenfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字
-
- //设置地址复用
- int opt = 1;
- setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
第一个参数是设置哪个套接字绑定的端口复用;
第二个参数设置成SOL_SOCKET;
第三个参数是SO_REUSEADDR,即地址复用
第四个参数类似于一个布尔值,确认要进行地址复用
一般来说,服务端是不会主动断开连接,但不排除服务端因为连接过多挂掉的情况,若此时服务器建立了多个连接,那么这种情况下的危害无法想象。就好比双十一的时候,淘宝服务器崩溃了,此时会进入TIME_WAIT状态,短时间内无法重新启动,假设TIME_WAIT要持续两分钟,这两分钟的交易额就是一笔大损失。
当你看到CLOSE_WAIT状态时,说明此时只是断开了一个方向上的连接,四次挥手并没有完成。下面可以做一个实验,我们让客户端主动断开连接,服务端处在一个死循环中或者服务端不调用close函数。
假设现在客户端和服务端建立好连接,服务端一旦建立连接服务端到客户端方向上的连接,就会处于ESTABLISHED状态。
假设现在客户端断开连接,服务端处于死循环中。
如果我们重启客户端,然后再关闭。我们会发现,又一个进程处在CLOSE_WAIT状态,这都是四次挥手没有完成的表现,这些进程会一直占用资源,所以服务端结束服务以后一定要调用close函数,否则会发生内存泄漏。
发生CLOSE_WAIT 的原因,大致有两点猜想:
针对上述第二点,如果服务端因为忙于处理其他事务而忘记关闭连接,此时只能借助 “ 心跳检测机制 ”,即服务端每隔一定时间(默认是2小时)发送报文,判断对端是否会应答;一次不应答,等待一定时间(默认是75 s)后重新发送探测报文;重新探测一定次数(默认是 9 次)后,依然无应答,判断为离线,断开连接。