TIME_WAIT状态引起的服务端重启失败问题

问题模型:

server1为服务端,在本地的9999端口监听,server2相对server1是客户端,server2启动后首先向server1发起连接,然后再8888端口监听。程序代码不在列出。

先后启动server1、server2,然后查看当前连接,如图1所示。

1.建立连接,正常

之后强制结束server1ctrl+c),再次查看当前连接状态,如图2所示。

2.server1主动关闭后的状态

我们发现连接并没有消失(close),这是什么原因呢?我们用时序图分析一下,如图3.

3.server1主动关闭时序

从时序图中可以看出server1终止伴随着FIN分节的发送,server2收到FIN导致进入CLOSE_WAIT状态,同时server2发送FIN分节的ACK,server1收到ACK后进入FIN_WAIT2状态。

注意:虽然进程server1已经终止,但是这条server2到server1的连接并没有终止,因为还没有完成TCP的最后“四次挥手”。

接着强制终止server2ctrl+c,再次查看端口状态,如图4所示。

4.server2终止后连接状态

终止server2,导致server向server1发送FIN。我们发现这个连接进入了TIME_WAIT状态。

我们再将终止server2后的时序图画出,如图5。

5.关闭server2时序

关于什么是TIME_WAIT状态,以及为什么要有TIME_WAIT状态,网上可以很容易查到,这里不再赘述。但要说明的,从图4从可以看出,TIME_WAIT状态是在server1端出现的,也就是整个连接的主动关闭端。

下面开始我们真正的问题,我们重新启动server1,并用server2连接server1,结果如图6所示。此时,server2向server1发起链接,调用connect会失败。

6.server1重启后,server2再连接

我们发现连接失败,而从出错的信息(connection refused)可以推断应该是服务端(server1)没有在相应端口(9999)监听。等一段时间过后,在查看端口状态,如图7所示,我们发现TIME_WAIT状态消失了。

7.TIME_WAIT消失

server1并没有在9999端口监听,而我们的server1确的的确确的在运行如图8所示。

8

这是什么原因?其实看似server1在运行,其实bind时已经出错,只是程序没有将出错信息打印出来。在server1添加如下代码:

n=bind(listenfd, (sockaddr *) &serveraddr, sizeof(serveraddr));

if(n!=0)

{

perror("bind error:");

}

再次启动server1(此时server1之前的连接仍处于TIME_WAIT状态),如图9所示

9

造成这个错误的原因是之前的连接还并没有消失(处于TIME_WAIT),而server1又试图bind一个现有连接(处于TIME_WAIT的连接)上的端口(9999),所以bind失败。Server2当然也就不能连接成功了。那么如何使服务端(server1)主动关闭后可以立即重启呢?

解决方法:在bind设置SO_REUSEADDR套接字选项。

const int on=1;

setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); 

之后再次重复上述重启操作,结果如图10所示,重启成功。

.10

SO_REUSEADDR选项

SO_REUSEADDR选项的用途有多中,我们只讨论这里使用到的功能。先来看看UNP V1对这种情况的描述。

SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将该端口用作它们的本地的连接仍存在。这个条件通常是这样碰到的:

(1) 启动一个监听服务器;

(2) 连接请求到达,派生一个子进程来处理这个客户;

(3) 监听服务器终止,但子进程继续为现有连接上的客户提供服务;

(4) 重启监听服务器。

默认情况下,当监听服务器在步骤(4)中通过调用socket、bind和listen重新启动时,由于它试图捆绑一个现有连接(即正由早先派生的那个子进程处理着的连接)上的端口,从而bind调用会失败。但如果该服务器在socket和bind中间调用设置了SO_REUSEADDR选项,那么bind将成功。                                         ——以上摘自UNP V1

下面对比我们这里遇到的情况,server1主动关闭后进入TIME_WAIT状态,此时对server1来说原有连接没有彻底终止,当重启server1时,就试图bind一个现有的连接,所以造成bind失败。所以一般TCP服务端都要设置SO_REUSEADDR选项,以便可以快速重启。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值