TIME_WAIT状态引起的服务端重启失败问题
2015-05-06 10:55
477 查看
问题模型:
server1为服务端,在本地的9999端口监听,server2相对server1是客户端,server2启动后首先向server1发起连接,然后再8888端口监听。程序代码不在列出。
先后启动server1、server2,然后查看当前连接,如图1所示。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/8d0d9d0daddcd0084f46f1038f2e77fc.png)
图1.建立连接,正常
之后强制结束server1(ctrl+c),再次查看当前连接状态,如图2所示。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/a5308dbd73c78138508b0eb98cf90ae3.png)
图2.server1主动关闭后的状态
我们发现连接并没有消失(close),这是什么原因呢?我们用时序图分析一下,如图3.
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/276cc84b1c3a9f49960f68ac5ec21887.png)
图3.server1主动关闭时序
从时序图中可以看出server1终止伴随着FIN分节的发送,server2收到FIN导致进入CLOSE_WAIT状态,同时server2发送FIN分节的ACK,server1收到ACK后进入FIN_WAIT2状态。
注意:虽然进程server1已经终止,但是这条server2到server1的连接并没有终止,因为还没有完成TCP的最后“四次挥手”。
接着强制终止server2(ctrl+c),再次查看端口状态,如图4所示。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/940800d415e8e86e02f430315035129d.png)
图4.server2终止后连接状态
终止server2,导致server向server1发送FIN。我们发现这个连接进入了TIME_WAIT状态。
我们再将终止server2后的时序图画出,如图5。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/56f43f8db2c58336b27507c3e2ef7556.png)
图5.关闭server2时序
关于什么是TIME_WAIT状态,以及为什么要有TIME_WAIT状态,网上可以很容易查到,这里不再赘述。但要说明的,从图4从可以看出,TIME_WAIT状态是在server1端出现的,也就是整个连接的主动关闭端。
下面开始我们真正的问题,我们重新启动server1,并用server2连接server1,结果如图6所示。此时,server2向server1发起链接,调用connect会失败。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/386e84c5d7664dc4a538620c991b3d4b.png)
图6.server1重启后,server2再连接
我们发现连接失败,而从出错的信息(connection refused)可以推断应该是服务端(server1)没有在相应端口(9999)监听。等一段时间过后,在查看端口状态,如图7所示,我们发现TIME_WAIT状态消失了。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/1aa38a662fd8fd00a64bfee161653937.png)
图7.TIME_WAIT消失
server1并没有在9999端口监听,而我们的server1确的的确确的在运行如图8所示。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/e4dec7004b9c97dc879ab3af2bbdaa18.png)
图8
这是什么原因?其实看似server1在运行,其实bind时已经出错,只是程序没有将出错信息打印出来。在server1添加如下代码:
n=bind(listenfd, (sockaddr *) &serveraddr, sizeof(serveraddr));
if(n!=0)
{
perror("bind error:");
}
再次启动server1(此时server1之前的连接仍处于TIME_WAIT状态),如图9所示。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/a4b597b7cc82ac3c5b379a86689e38de.png)
图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所示,重启成功。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/99ab2316722a7d1281570b061f59b4ef.jpg)
图.10
l 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选项,以便可以快速重启。
server1为服务端,在本地的9999端口监听,server2相对server1是客户端,server2启动后首先向server1发起连接,然后再8888端口监听。程序代码不在列出。
先后启动server1、server2,然后查看当前连接,如图1所示。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/8d0d9d0daddcd0084f46f1038f2e77fc.png)
图1.建立连接,正常
之后强制结束server1(ctrl+c),再次查看当前连接状态,如图2所示。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/a5308dbd73c78138508b0eb98cf90ae3.png)
图2.server1主动关闭后的状态
我们发现连接并没有消失(close),这是什么原因呢?我们用时序图分析一下,如图3.
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/276cc84b1c3a9f49960f68ac5ec21887.png)
图3.server1主动关闭时序
从时序图中可以看出server1终止伴随着FIN分节的发送,server2收到FIN导致进入CLOSE_WAIT状态,同时server2发送FIN分节的ACK,server1收到ACK后进入FIN_WAIT2状态。
注意:虽然进程server1已经终止,但是这条server2到server1的连接并没有终止,因为还没有完成TCP的最后“四次挥手”。
接着强制终止server2(ctrl+c),再次查看端口状态,如图4所示。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/940800d415e8e86e02f430315035129d.png)
图4.server2终止后连接状态
终止server2,导致server向server1发送FIN。我们发现这个连接进入了TIME_WAIT状态。
我们再将终止server2后的时序图画出,如图5。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/56f43f8db2c58336b27507c3e2ef7556.png)
图5.关闭server2时序
关于什么是TIME_WAIT状态,以及为什么要有TIME_WAIT状态,网上可以很容易查到,这里不再赘述。但要说明的,从图4从可以看出,TIME_WAIT状态是在server1端出现的,也就是整个连接的主动关闭端。
下面开始我们真正的问题,我们重新启动server1,并用server2连接server1,结果如图6所示。此时,server2向server1发起链接,调用connect会失败。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/386e84c5d7664dc4a538620c991b3d4b.png)
图6.server1重启后,server2再连接
我们发现连接失败,而从出错的信息(connection refused)可以推断应该是服务端(server1)没有在相应端口(9999)监听。等一段时间过后,在查看端口状态,如图7所示,我们发现TIME_WAIT状态消失了。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/1aa38a662fd8fd00a64bfee161653937.png)
图7.TIME_WAIT消失
server1并没有在9999端口监听,而我们的server1确的的确确的在运行如图8所示。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/e4dec7004b9c97dc879ab3af2bbdaa18.png)
图8
这是什么原因?其实看似server1在运行,其实bind时已经出错,只是程序没有将出错信息打印出来。在server1添加如下代码:
n=bind(listenfd, (sockaddr *) &serveraddr, sizeof(serveraddr));
if(n!=0)
{
perror("bind error:");
}
再次启动server1(此时server1之前的连接仍处于TIME_WAIT状态),如图9所示。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/a4b597b7cc82ac3c5b379a86689e38de.png)
图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所示,重启成功。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/10/99ab2316722a7d1281570b061f59b4ef.jpg)
图.10
l 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选项,以便可以快速重启。
相关文章推荐
- TIME_WAIT状态引起的服务端重启失败问题
- TIME_WAIT状态引起的服务端重启失败问题
- [转]关于流量升高导致TIME_WAIT增加,MySQL连接大量失败的问题
- TCP握手协议-原理和案例(服务端大量CLOSE_WAIT,TIME_WAIT问题)参考总结
- http服务器中的TIME_WAIT状态过的的问题
- socket Server 解决TIME_WAIT状态等待问题
- netstat下time_wait数过多引起的问题
- 解决windows的socket长时间保留在time_wait状态问题
- 关于流量升高导致TIME_WAIT增加,MySQL连接大量失败的问题
- netstat下time_wait数过多引起的问题
- LINUX下解决netstat查看TIME_WAIT状态过多问题(转)
- LINUX下解决netstat查看TIME_WAIT状态过多问题
- LINUX下解决time_wait连接过多和同一IP连接过多的问题 及 TCP/IP TIME_WAIT状态原理
- TIME_WAIT状态的连接过多导致系统端口资源耗尽问题(2)
- 关于流量升高导致TIME_WAIT增加,MySQL连接大量失败的问题
- 关于tcp中time_wait状态的4个问题
- 服务端端口状态和客户端端口状态解释-LISTENING-ESTABLISHED-TIME_WAIT
- socket:close_wait状态和time_wait状态问题
- TIME-WAIT状态和reuse问题
- 关于tcp中time_wait状态的4个问题