当服务器端程序重启之后,总是碰到“Address in use”的报错信息,服务器程序不能很快地重启。
启动服务器,用ctrl+c关闭,然后再重启,发现重启会报错“bind failed:Address already in ues”
这个错误是因为主动关闭连接的一方在TIME_WAIT停留一段时间,默认时间为2MSL
重用套接字选项
解决TIME_WAIT 的新旧连接化身冲突(Address already in ues)的问题。
- 优化新连接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 套接字选项,允许启动绑定在一个端口,即使之前存在一个和该端口一样的连接。
最佳解决方法
服务器端程序,都应该设置 SO_REUSEADDR 套接字选项,以便服务端程序可以在极短时间内复用同一个端口启动。
TCP 的机制绝对不允许在相同的地址和端口上绑定不同的服务器,即使我们设置 SO_REUSEADDR 套接字选项,也不可能在 ANY 通配符地址下和端口 9527 上重复启动两个服务器实例。
SO_REUSEADDR 套接字和tcp_tw_reuse 的内核配置选项区别
- tcp_tw_reuse 是内核选项,主要用在连接的发起方。TIME_WAIT 状态的连接创建时间超过 1 秒后,新的连接才可以被复用,注意,这里是连接的发起方;
- SO_REUSEADDR 是用户态的选项,告诉操作系统内核,如果端口已被占用,但是 TCP 连接状态位于 TIME_WAIT ,可以重用端口。如果端口忙,而 TCP 处于其他状态,重用端口时依旧得到“Address already in use”的错误信息。注意,这里一般都是连接的服务方。
关于tcp_tw_reuse和SO_REUSEADDR的区别,可以概括为:tcp_tw_reuse是为了缩短time_wait的时间,避免出现大量的time_wait链接而占用系统资源,解决的是accept后的问题;SO_REUSEADDR是为了解决time_wait状态带来的端口占用问题,以及支持同一个port对应多个ip,解决的是bind时的问题。
总结
在所有 TCP 服务器程序中,调用 bind 之前请设置 SO_REUSEADDR 套接字选项。这不会产生危害,相反,它会帮助我们在很快时间内重启服务端程序。