TIME_WAIT
TIME_WAIT是什么?
TIME_WAIT = "礼貌的等待时间"
就像挂电话后不会立即离开,而是等一会儿确保对方真的挂断了。
1. 什么时候会出现TIME_WAIT?
#include <stdio.h>
#include <unistd.h>
void simulate_time_wait() {
printf("=== TIME_WAIT 状态模拟 ===\n\n");
printf("四次挥手过程:\n");
printf("1. 客户端: 我要关闭了 (FIN)\n");
printf("2. 服务器: 好的 (ACK)\n");
printf("3. 服务器: 我也要关闭了 (FIN)\n");
printf("4. 客户端: 好的 (ACK)\n\n");
printf("🔁 现在进入 TIME_WAIT 状态...\n");
printf("客户端: 我发送了最后一个ACK,但要等待2MSL时间\n");
printf(" 确保服务器收到了我的ACK\n");
printf(" 如果服务器没收到,会重发FIN,我还能回复ACK\n\n");
printf("⏰ 等待 2MSL (通常是 60秒)...\n");
sleep(3); // 模拟等待
printf("✅ TIME_WAIT 结束,连接完全关闭\n");
}
int main() {
simulate_time_wait();
return 0;
}
2. 实际代码中的TIME_WAIT
客户端代码(主动关闭方):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
printf("🎪 客户端启动(主动关闭方)\n");
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
printf("✅ 连接服务器成功\n");
// 发送一些数据
write(sockfd, "Hello", 5);
// 主动关闭连接(客户端会成为TIME_WAIT方)
printf("🔄 客户端主动关闭连接...\n");
close(sockfd);
printf("📞 调用close()完成,进入TIME_WAIT状态\n");
printf("💤 进程退出,但TCP连接还在TIME_WAIT状态\n");
return 0;
}
3. 为什么需要TIME_WAIT?
原因1:确保最后一个ACK到达
没有TIME_WAIT的情况:
客户端 → 服务器: ACK [丢失!]
客户端立即关闭
服务器没收到ACK,重发FIN
但客户端已经关闭,无法回复ACK
→ 服务器一直等待,资源泄露
原因2:让旧连接的数据包消失
bash
网络中有延迟的旧数据包:
旧连接的数据包在新连接建立后才到达
可能混淆新连接的数据
TIME_WAIT等待2MSL,确保所有旧数据包都从网络中消失
4. TIME_WAIT的持续时间
2MSL = 2 × Maximum Segment Lifetime
bash
#include <stdio.h>
int main() {
printf("=== TIME_WAIT 持续时间 ===\n\n");
printf("MSL (Maximum Segment Lifetime):\n");
printf("- 数据包在网络中的最大生存时间\n");
printf("- 通常是 30秒 或 60秒\n");
printf("- 不同操作系统可能不同\n\n");
printf("TIME_WAIT = 2 × MSL\n");
printf("原因:\n");
printf("1. 等待最后一个ACK到达对端 (1MSL)\n");
printf("2. 如果对端没收到,等待FIN重传 (1MSL)\n");
printf("3. 总共等待: 2MSL\n\n");
printf("常见系统的TIME_WAIT时间:\n");
printf("Linux: 60秒 (2 × 30秒)\n");
printf("Windows: 240秒 (4分钟)\n");
printf("BSD: 60秒\n");
return 0;
}
客户端知道服务器收到数据的几种方式 :
- 应用层ACK:服务器明确回复"我收到了"
- 服务器主动关闭:服务器的close()是最终确认
- 业务响应:HTTP 200、RPC结果等业务层面的确认
- 重传机制:超时未确认则重传,直到收到确认为止
TIME_WAIT本身不是确认机制,它只是防止旧包干扰新连接。真正的确认需要在应用层或传输层以上实现。
5. 如何查看TIME_WAIT状态?
使用 netstat 命令:
bash
# 查看所有TCP连接状态
netstat -an | grep tcp
# 或者用 ss 命令(更推荐)
ss -tan
输出示例:
bash
State Recv-Q Send-Q Local Address:Port Peer Address:Port
TIME-WAIT 0 0 127.0.0.1:8080 127.0.0.1:54321
ESTABLISHED 0 0 127.0.0.1:8080 127.0.0.1:54322
6. TIME_WAIT的问题和解决方案
问题:端口耗尽
bash
// 如果服务器频繁重启,可能出现:
// 无法绑定端口,因为还在TIME_WAIT状态
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
// 设置SO_REUSEADDR选项,解决TIME_WAIT问题
int reuse = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
bind(server_fd, ...); // 现在可以立即重用端口
完整示例:
bash
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
printf("🚀 服务器(解决TIME_WAIT问题)\n");
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
// 关键:设置SO_REUSEADDR
int reuse = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
perror("setsockopt failed");
exit(1);
}
printf("✅ 设置SO_REUSEADDR成功,可以立即重用端口\n");
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
bind(server_fd, (struct sockaddr*)&address, sizeof(address));
listen(server_fd, 5);
printf("📡 服务器启动成功,即使有TIME_WAIT也能绑定端口\n");
close(server_fd);
return 0;
}
TIME_WAIT只在主动关闭连接的一方出现
|-------|------------|
| 角色 | 状态 |
| 主动关闭方 | TIME_WAIT |
| 被动关闭方 | 直接进入CLOSED |
TIME_WAIT的作用:
- ✅ 确保TCP可靠关闭
- ✅ 防止旧连接数据干扰新连接
- ✅ 让网络中的残余数据包消失(保证让迟来的tcp报文段有足够时间被识别并丢弃)
解决方法:
- 服务器端设置 SO_REUSEADDR
- 让客户端主动关闭(这样TIME_WAIT在客户端)
