linux学习笔记(31)网络编程——TCP time_wait机制

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;
}

客户端知道服务器收到数据的几种方式

  1. 应用层ACK:服务器明确回复"我收到了"
  2. 服务器主动关闭:服务器的close()是最终确认
  3. 业务响应:HTTP 200、RPC结果等业务层面的确认
  4. 重传机制:超时未确认则重传,直到收到确认为止
    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的作用:

  1. ✅ 确保TCP可靠关闭
  2. ✅ 防止旧连接数据干扰新连接
  3. ✅ 让网络中的残余数据包消失(保证让迟来的tcp报文段有足够时间被识别并丢弃)
    解决方法:
  • 服务器端设置 SO_REUSEADDR
  • 让客户端主动关闭(这样TIME_WAIT在客户端)
相关推荐
Raymond运维3 小时前
MySQL源码编译安装
linux·数据库·mysql
Yupureki3 小时前
从零开始的C++学习生活 7:vector的入门使用
c语言·c++·学习·visual studio
dessler3 小时前
Elasticsearch(ES)分片(Shard)和 副本分片(Replica Shard)
linux·运维·elasticsearch
知北游天3 小时前
Linux网络:使用TCP实现网络通信(服务端)
linux·网络·tcp/ip
i学长的猫3 小时前
Ruby小白学习路线
开发语言·学习·ruby
送秋三十五4 小时前
Docker 构建教程:学习上下文、架构和性能优化技术
学习·docker·架构
Dovis(誓平步青云)4 小时前
《探秘 Linux 进程控制:驾驭系统运行的核心之力》
linux·运维·服务器
思成不止于此4 小时前
软考中级软件设计师备考指南(四):I/O 技术、安全与可靠性 —— 综合应用篇
网络·笔记·学习·信息安全·总线系统·i/o 技术·可靠性计算
聪明的笨猪猪4 小时前
Java Redis “核心应用” 面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试