一、问题描述
在服务器端和客户端已经建立连接的状态下,向服务器端控制台输入CTRL+C,即强制关闭服务器端。"
这主要模拟了服务器端向客户端发送FIN消息的情景。但如果以这种方式终止程序,那服务器端重新运行时将产生问题。如果用同一端口号重新运行服务器端,将输出"bind()error"消息,并且无法再次运行。但在这种情况下,再过大约3分钟即可重新运行服务器端。
但如果是客户端发送FIN消息,就不会产生什么问题。
二、产生问题原因:Time-wait状态
1、什么是TIME-WAIT状态
在TCP协议传输过程中,发送断开连接请求的一方在发送完最后一次ACK包后,会进入一个TIME-WAIT状态(等待时间是两倍的数据包在收发双方之间传输的最大传输时间),这个状态主要是为了确保本次TCP传输中所有的数据包都已失效。
如果没有这个TIME-WAIT状态,申请断开连接的一方发送完ACK后直接进入关闭状态,如果申请断开的一方最后一次发送的ACK包没有到达对方,那么接收方认为对方没有收到自己发送的FIN包,会重新发送FIN包。
如果申请断开连接的一方此时已经由关闭状态再次申请了建立连接请求,但此时又收到了上一次连接时来自接收方的FIN包,那么会立刻断开连接,这就会发生错误。
因此申请断开连接的一方必须存在一个TIME-WAIT状态,确保这一次TCP连接中的所有数据包都已失效。
2、TIME-WAIT状态导致的问题
time-wait时这个IP和端口号就被占用了,不能分配,所以结束服务器进程,再次重新启动去bind这个IP和端口时会发生错误,IP端口已经被占用,无法bind,产生bind() error。
time-wait状态最长甚至会达到两个小时。
但是服务器需要马上进行重启,不可能等待time-wait时间再去重新bind
相同的,客户端如果主动发起断开连接请求,也会存在time-wait状态。
但是客户端是connect自动分配客户端的IP(分情况分配IP,外网就使用自己的以太网或者无线网)和端口,但是可分配的端口是无限多的,并且客户端不需要固定bind固定的IP和端口,所以不会产生错误。
三、解决办法:SO_REUSEADDR
解决方案就是在套接字的可选项中更改SO_REUSEADDR的状态。适当调整该参数,可将Time-wait状态下的套接字端口号重新分配给新的套接字。SO_REUSEADDR的默认值为0(假),这就意味着无法分配Time-wait状态下的套接字端口号。因此需要将这个值改成1(真)。
代码如下
optlen=sizeof(option);
option=TRUE;
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);