TIME_WAIT与SO_REUSEADDR
运行环境:Ubuntu 16.0.4
-
无论是否开启 SO_REUSEADDR ,客户端主动断开连接后,无需等待 TIME_WAIT 即可重连,因为客户端每次 bind 的端口都不一样。由此可见, TIME_WAIT 是对端口而言 。
不管是服务器还是客户端,只要是主动断开连接的,都会有 TIME_WAIT 。
-
如不开启 SO_REUSEADDR,服务器端主动断开连接,则必须等待 TIME_WAIT 后才可重新 bind 该端口,原因见下文。
-
注意,必须是要在 accept 或 connect 成功返回后(即连接成功后)断连或终止程序,才会有 TIME_WAIT ;仅仅 bind 但未连接成功,终止程序后是不会 TIME_WAIT 的 。
-
注意,如果 bind 指定端口不成功,则会自动 bind 其他任意端口;
-
SO_REUSEADDR
生效的前提条件是开启时间戳,即令/proc/sys/net/ipv4/tcp_timestamps
为 1(默认也为1)。实操发现即使关闭时间戳,服务器也能立刻重新绑定端口,原因尚不明确。
1 | //server |
1 | //client |
直接编译上述代码,先运行 server 端,再运行 client 端(注意,最好运行可执行文件,不要直接在编译器中运行),结果如下:
由于客户端 close 前调用了 sleep,所以可以判断一定是 server 主动断开连接,如下图:
localhost:12345
是本端地址信息,localhost:53188
是对端地址信息,后面的状态是描述本端状态的,因此可知 server 主动发送 FIN 并断开连接后,进入了 TIME_WAIT 状态。此时我们马上重启服务器,则输出以下内容:
在 TIME_WAIT 内重启服务器,则报错绑定失败。注意,绑定指定端口失败后,会随机绑定其他端口,如下:
第三行的 46937
便是服务器端随机绑定的端口。不过这已经失去意义,因为服务器是通过知名端口来被客户端认识的,客户端根本不认识这些随机端口,所以 server 会一直处于监听状态,不会有 client 来连接。因此,对于服务端,如果 bind 失败,应该直接终止程序或等待 TIME_WAIT 后重新 bind。
下面我们让 client 主动关闭连接——加上 server 的 sleep ,去掉 client 的 sleep 即可。结果如下:
本端为 localhost:53200
,可见确实是 client 发起的 FIN 。因为 client 无需 bind 特定端口,即 client 每次运行绑定的端口都不同,所以不用担心会因为 TIME_WAIT 而连接失败。
接下来我们使用 SO_REUSEADDR 选项来修改 TIME_WAIT 的限制——将 server 代码第 7 行的 opt 赋值为 1 即可,这样就可以无视 TIME_WAIT 直接复用端口。下面是连续两次运行 server 和 client 的结果:
你看,即使服务器处于处于 TIME_WAIT 状态,还是可以直接绑定端口并进行通信。
注意,即使将 SO_REUSEADDR 置 1,TIME_WAIT 状态也依旧存在,只是可以无视该状态直接 bind 而已。
待补充
https://xiaolincoding.com/network/3_tcp/tcp_tw_reuse_close.html#为什么要设计-time-wait-状态
https://xiaolincoding.com/network/3_tcp/time_wait_recv_syn.html#先说结论