相信不少刚学网络编程的人都会遇上这个问题,在测试自己程序的时候,把原先的服务端程序关了之后,重启一下,发现就出现了”Address already in use“。
下面就来分析一下原因和解决办法:
一、原因
首先来复习一下TIME_WAIT,当连接的一方主动关闭连接,在接收到对端的 FIN 报文之后,主动关闭连接的一方会在 TIME_WAIT 这个状态里停留一段时间,这个时间大约为 2MSL(Windows一般为2min,linux为30s)。如下图所示:
服务端发起的关闭连接操作,就会引起注意一个TCP连接处于TIME_WAIT的现象。也正是这个 TIME_WAIT 的连接,使得服务器重启时,地址和端口不变的话,就会返回“Address already in use ”的错误。
二、解决方法
当然,最容易想到的方法,就是换一个地址或者端口号。不过在现代 Linux 操作系统下,即使使用了相同的地址和端口号,也不会有什么大的问题,原因是现代 Linux 操作系统对此进行了一些优化。
- 第一种优化是新连接 SYN 告知的初始序列号,一定比 TIME_WAIT 老连接的末序列号大,这样通过序列号就可以区别出新老连接。
- 第二种优化是开启了 tcp_timestamps,使得新连接的时间戳比老连接的时间戳大,这样通过时间戳也可以区别出新老连接。
在这样的优化之下,一个 TIME_WAIT 的 TCP 连接可以忽略掉旧连接,重新被新的连接所使用。
这就是重用套接字选项,通过给套接字配置可重用属性,告诉操作系统内核,这样的 TCP 连接完全可以复用 TIME_WAIT 状态的连接。代码如下:
int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
SO_REUSEADDR 套接字选项,允许启动绑定在一个端口,即使之前存在一个和该端口一样的连接。
前面的例子已经表明,在默认情况下,服务器端历经创建 socket、bind 和 listen 重启时,如果试图绑定到一个现有连接上的端口,bind 操作会失败,但是如果我们在创建 socket 和 bind 之间,使用上面的代码片段设置 SO_REUSEADDR 套接字选项,情况就会不同。
这里需要注意的是,SO_REUSEADDR 是用户态的选项,SO_REUSEADDR 选项用来告诉操作系统内核,如果端口已被占用,但是 TCP 连接状态位于 TIME_WAIT ,可以重用端口。如果端口忙,而 TCP 处于其他状态,重用端口时依旧得到“Address already in use”的错误信息。
三、总结
在所有 TCP 服务器程序中,调用 bind 之前请设置 SO_REUSEADDR 套接字选项。这不会产生危害,相反,它会帮助我们在很快时间内重启服务端程序,而这一点恰恰是很多场景所需要的。
问题一:关于tcp_tw_reuse和SO_REUSEADDR的区别
tcp_tw_reuse是为了缩短time_wait的时间,避免出现大量的time_wait链接而占用系统资源,解决的是accept后的问题;SO_REUSEADDR是为了解决time_wait状态带来的端口占用问题,以及支持同一个port对应多个ip,解决的是bind时的问题。
|