Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3426816
  • 博文数量: 198
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 7246
  • 用 户 组: 普通用户
  • 注册时间: 2013-01-23 18:56
个人简介

将晦涩难懂的技术讲的通俗易懂

文章分类

全部博文(198)

文章存档

2023年(9)

2022年(4)

2021年(12)

2020年(8)

2019年(18)

2018年(19)

2017年(9)

2016年(26)

2015年(18)

2014年(54)

2013年(20)

分类: LINUX

2014-06-03 22:46:22

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

问题模型:

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

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

1.建立连接,正常

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

2.server1主动关闭后的状态

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

3.server1主动关闭时序

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

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

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

4.server2终止后连接状态

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

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

5.关闭server2时序

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

下面开始我们真正的问题,我们重新启动server1,并用server2连接server1,结果如图6所示。此时,server2server1发起链接,调用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)中通过调用socketbindlisten重新启动时,由于它试图捆绑一个现有连接(即正由早先派生的那个子进程处理着的连接)上的端口,从而bind调用会失败。但如果该服务器在socketbind中间调用设置了SO_REUSEADDR选项,那么bind将成功。                                         ——以上摘自UNP V1

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

阅读(7036) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~