本篇文章于 750 天前发表,某些内容可能已经过时,请注意甄别。
运行环境: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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| int main() { int pid = getpid(); printf("server pid:%d\n",pid); int sock_server = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP); int opt = 0; socklen_t optlen = sizeof(opt); setsockopt(sock_server, SOL_SOCKET, SO_REUSEADDR, &opt, optlen); struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(12345); server_addr.sin_family = AF_INET; if(-1 == bind(sock_server, (struct sockaddr*)&server_addr, sizeof(server_addr))){ printf("server failed to bind\n"); }; listen(sock_server, 20); char buf[100] = {0}; int sock_clnt; if(-1 != (sock_clnt = accept(sock_server,NULL,NULL))){ ssize_t size = read(sock_clnt, buf, sizeof(buf)); printf("server received %ld bytes\n",size); size = write(sock_clnt, buf, sizeof(buf)); printf("server sent %ld bytes\n=====================",size); } close(sock_server); close(sock_clnt); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| int main() { int pid = getpid(); printf("client pid:%d\n",pid); int sock_clnt = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(12345); server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); connect(sock_clnt, (struct sockaddr*)&server_addr, sizeof(server_addr)); char in_buf[100]; char out_buf[100]; for(int i=0;i<99;i++) out_buf[i]='f'; ssize_t size = write(sock_clnt, out_buf, sizeof(out_buf)); printf("client sent %ld bytes\n",size); size = read(sock_clnt, in_buf, sizeof(in_buf)); printf("client received %ld bytes\n",size); sleep(3); close(sock_clnt); }
|
直接编译上述代码,先运行 server 端,再运行 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 即可。结果如下:

server

client

本端为 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#先说结论