HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 网络架构师学习手册

    • 网络架构师学习教程
    • 基础篇

      • 第1章 网络模型与数据流转
      • 第2章 以太网与二层通信
      • 第3章 IP路由与三层转发
      • 第4章 TCP与可靠传输
      • 第5章 应用层协议
    • Linux网络栈

      • 第6章 数据包接收路径
      • 第7章 多核网络优化
      • 第8章 Netfilter与防火墙
      • 第9章 流量控制与QoS
    • 虚拟网络

      • 第10章 Network Namespace基础
      • 第11章 Bridge与互联
      • 第12章 VXLAN与Overlay网络
      • 第13章 OVS与SDN
    • Kubernetes网络

      • 第14章 CNI模型与实现
      • 第15章 kube-proxy与Service实现
      • 第16章 CoreDNS与服务发现
      • 第17章 NetworkPolicy与安全隔离
      • 第18章 Calico网络深度解析
      • 第19章 Cilium与eBPF网络
    • 网络架构

      • 第20章 网络设备与拓扑设计
      • 第21章 网络容量规划与计算
      • 第22章 负载均衡架构设计
      • 第23章 高可用网络架构
      • 第24章 网络安全架构
    • 性能调优

      • 第25章 系统级网络调优
      • 第26章 故障排查方法论
      • 第27章 生产环境案例分析
    • 前沿技术

      • 第28章 eBPF深度实践
      • 第29章 ServiceMesh与边车代理
      • 第30章 网络技术趋势与未来展望
    • 附录

      • 附录A:命令速查手册
      • 附录B:排错决策树
      • 附录C:学习资源
      • 附录D:技能图谱

第4章 TCP与可靠传输

学习目标

  • 理解TCP三次握手和四次挥手过程
  • 掌握滑动窗口和流量控制机制
  • 了解拥塞控制算法(Cubic、BBR)
  • 能够排查TCP连接和性能问题

🔬 原理

TCP三次握手

连接建立过程:

客户端                    服务器
SYN seq=x        →
                 ←        SYN-ACK seq=y ack=x+1
ACK ack=y+1      →

详细过程:

  1. 客户端:发送SYN包,序列号=x,状态=SYN_SENT
  2. 服务器:发送SYN-ACK包,序列号=y,确认号=x+1,状态=SYN_RCVD
  3. 客户端:发送ACK包,确认号=y+1,状态=ESTABLISHED
  4. 服务器:收到ACK,状态=ESTABLISHED

状态转换:

CLOSED → SYN_SENT → ESTABLISHED
CLOSED → LISTEN → SYN_RCVD → ESTABLISHED

TCP四次挥手

连接关闭过程:

客户端                    服务器
FIN seq=x        →
                 ←        ACK ack=x+1
                 ←        FIN seq=y
ACK ack=y+1      →

详细过程:

  1. 主动关闭方:发送FIN包,状态=FIN_WAIT_1
  2. 被动关闭方:发送ACK包,状态=CLOSE_WAIT
  3. 被动关闭方:发送FIN包,状态=LAST_ACK
  4. 主动关闭方:发送ACK包,状态=TIME_WAIT

状态转换:

ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED

滑动窗口机制

发送窗口:

  • 已发送未确认:等待ACK的数据
  • 可发送未发送:窗口内可发送的数据
  • 窗口大小:由接收方通告

接收窗口:

  • 已接收:已确认的数据
  • 可接收:窗口内可接收的数据
  • 窗口大小:接收缓冲区剩余空间

流量控制:

  • 接收方通过窗口大小控制发送方
  • 窗口为0时暂停发送
  • 窗口更新时恢复发送

拥塞控制算法

Cubic算法:

  1. 慢启动:指数增长(1→2→4→8...)
  2. 拥塞避免:线性增长
  3. 快速重传:3个重复ACK触发
  4. 快速恢复:减半窗口后继续

BBR算法:

  • 基于带宽和RTT的主动探测
  • 不依赖丢包信号
  • 适合高带宽延迟网络

关键参数:

# 查看拥塞控制算法
sysctl net.ipv4.tcp_congestion_control

# 查看TCP参数
sysctl -a | grep tcp

️ 实现

Linux TCP实现

TCP连接结构:

struct tcp_sock {
    struct inet_connection_sock inet_conn;
    
    /* TCP状态 */
    u32 rcv_nxt;        // 下一个期望接收的序列号
    u32 snd_nxt;        // 下一个发送的序列号
    u32 snd_una;        // 最早未确认的序列号
    
    /* 窗口管理 */
    u32 rcv_wnd;        // 接收窗口
    u32 snd_wnd;        // 发送窗口
    u32 snd_cwnd;       // 拥塞窗口
    
    /* 重传机制 */
    u32 rto;            // 重传超时时间
    u32 rtt;            // 往返时间
    u32 rttvar;         // RTT方差
    
    /* 队列管理 */
    struct sk_buff_head write_queue;  // 发送队列
    struct sk_buff_head out_of_order_queue; // 乱序队列
};

三次握手实现:

// 客户端发送SYN
int tcp_connect(struct sock *sk) {
    struct tcp_sock *tp = tcp_sk(sk);
    
    // 初始化序列号
    tp->snd_nxt = tp->snd_una = tp->write_seq;
    
    // 发送SYN包
    tcp_send_syn(sk);
    
    // 设置重传定时器
    tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
    
    return 0;
}

// 服务器处理SYN
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb) {
    struct tcp_sock *tp = tcp_sk(sk);
    
    // 分配新的socket
    struct sock *newsk = tcp_create_openreq_child(sk, req);
    
    // 发送SYN-ACK
    tcp_send_synack(newsk);
    
    return 0;
}

拥塞控制实现

Cubic算法:

static void bictcp_cong_avoid(struct sock *sk, u32 ack, u32 acked) {
    struct tcp_sock *tp = tcp_sk(sk);
    struct bictcp *ca = inet_csk_ca(sk);
    
    if (!tcp_is_cwnd_limited(sk))
        return;
    
    if (tp->snd_cwnd <= tp->snd_ssthresh) {
        // 慢启动阶段
        tp->snd_cwnd = min(tp->snd_cwnd + acked, tp->snd_cwnd_clamp);
    } else {
        // 拥塞避免阶段
        u32 bic_target = cubic_target_cwnd(tp);
        u32 cubic_target = cubic_target_cwnd(tp);
        
        tp->snd_cwnd = min(tp->snd_cwnd + 1, cubic_target);
    }
}

🛠️ 命令

TCP连接管理

# 查看TCP连接状态
ss -tanp

# 查看TCP统计信息
ss -s

# 查看TCP详细信息
ss -ti

# 查看监听端口
ss -tuln

TCP参数调优

# 查看TCP参数
sysctl -a | grep tcp

# 重要参数
net.core.somaxconn = 128                 # 全连接队列大小
net.ipv4.tcp_max_syn_backlog = 512       # 半连接队列大小
net.ipv4.tcp_tw_reuse = 1                # TIME_WAIT复用
net.ipv4.tcp_congestion_control = cubic  # 拥塞控制算法

网络性能测试

# 带宽测试
iperf3 -s  # 服务端
iperf3 -c server_ip  # 客户端

# 延迟测试
ping -c 10 target_ip

# 连接测试
nc -zv target_ip port

代码

简单TCP服务器

// tcp_server.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <signal.h>

int server_socket;

void signal_handler(int sig) {
    printf("\nShutting down server...\n");
    close(server_socket);
    exit(0);
}

int main() {
    // 注册信号处理
    signal(SIGINT, signal_handler);
    
    // 创建socket
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        perror("socket");
        return 1;
    }
    
    // 设置SO_REUSEADDR
    int reuse = 1;
    if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, 
                   &reuse, sizeof(reuse)) < 0) {
        perror("setsockopt");
        close(server_socket);
        return 1;
    }
    
    // 绑定地址
    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_port = htons(8080),
        .sin_addr.s_addr = INADDR_ANY
    };
    
    if (bind(server_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("bind");
        close(server_socket);
        return 1;
    }
    
    // 监听
    if (listen(server_socket, 128) < 0) {
        perror("listen");
        close(server_socket);
        return 1;
    }
    
    printf("Server listening on port 8080\n");
    
    // 接受连接
    while (1) {
        struct sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
        
        int client_socket = accept(server_socket, 
                                  (struct sockaddr*)&client_addr, 
                                  &client_len);
        if (client_socket < 0) {
            perror("accept");
            continue;
        }
        
        printf("Client connected: %s:%d\n", 
               inet_ntoa(client_addr.sin_addr), 
               ntohs(client_addr.sin_port));
        
        // 处理客户端请求
        char buffer[4096];
        int bytes_received;
        
        while ((bytes_received = read(client_socket, buffer, sizeof(buffer))) > 0) {
            // 回显数据
            write(client_socket, buffer, bytes_received);
        }
        
        close(client_socket);
        printf("Client disconnected\n");
    }
    
    close(server_socket);
    return 0;
}

TCP客户端

// tcp_client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("Usage: %s <server_ip> <port>\n", argv[0]);
        return 1;
    }
    
    // 创建socket
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket");
        return 1;
    }
    
    // 连接服务器
    struct sockaddr_in server_addr = {
        .sin_family = AF_INET,
        .sin_port = htons(atoi(argv[2])),
        .sin_addr.s_addr = inet_addr(argv[1])
    };
    
    if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("connect");
        close(sock);
        return 1;
    }
    
    printf("Connected to %s:%s\n", argv[1], argv[2]);
    
    // 发送数据
    char message[] = "Hello, TCP Server!";
    if (send(sock, message, strlen(message), 0) < 0) {
        perror("send");
        close(sock);
        return 1;
    }
    
    // 接收响应
    char buffer[1024];
    int bytes_received = recv(sock, buffer, sizeof(buffer), 0);
    if (bytes_received < 0) {
        perror("recv");
        close(sock);
        return 1;
    }
    
    buffer[bytes_received] = '\0';
    printf("Server response: %s\n", buffer);
    
    close(sock);
    return 0;
}

编译运行:

gcc tcp_server.c -o tcp_server
gcc tcp_client.c -o tcp_client

# 运行服务器
./tcp_server

# 运行客户端(另一个终端)
./tcp_client 127.0.0.1 8080

🧪 实验

实验1:观察TCP三次握手

目标:通过抓包观察TCP连接建立过程

步骤:

# 1. 启动抓包(一个终端)
sudo tcpdump -i lo -nn 'port 8080' -S

# 2. 启动服务器(另一个终端)
./tcp_server

# 3. 连接客户端(第三个终端)
./tcp_client 127.0.0.1 8080

# 4. 观察抓包输出
# 应该看到:
# SYN seq=x
# SYN-ACK seq=y ack=x+1
# ACK ack=y+1

预期结果:

  • 理解三次握手过程
  • 观察序列号和确认号
  • 掌握TCP状态转换

实验2:TCP窗口机制观察

目标:理解滑动窗口和流量控制

步骤:

# 1. 查看TCP窗口参数
cat /proc/sys/net/ipv4/tcp_rmem
cat /proc/sys/net/ipv4/tcp_wmem

# 2. 使用iperf3测试
iperf3 -s  # 服务端
iperf3 -c localhost -t 30  # 客户端

# 3. 观察窗口变化
ss -ti | grep ESTABLISHED

# 4. 调整窗口大小
sudo sysctl -w net.core.rmem_max=134217728
sudo sysctl -w net.core.wmem_max=134217728

预期结果:

  • 理解窗口大小对性能的影响
  • 观察流量控制机制
  • 掌握TCP参数调优

实验3:拥塞控制算法对比

目标:对比不同拥塞控制算法的性能

步骤:

# 1. 测试Cubic算法
sudo sysctl -w net.ipv4.tcp_congestion_control=cubic
iperf3 -s &
iperf3 -c localhost -t 30

# 2. 测试BBR算法
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
iperf3 -s &
iperf3 -c localhost -t 30

# 3. 模拟网络延迟
sudo tc qdisc add dev lo root netem delay 50ms
iperf3 -c localhost -t 30

# 4. 清理
sudo tc qdisc del dev lo root

预期结果:

  • 理解不同算法的特点
  • 观察延迟对性能的影响
  • 掌握拥塞控制调优

实验4:TCP连接状态分析

目标:分析TCP连接的各种状态

步骤:

# 1. 查看当前连接状态
ss -tanp

# 2. 创建大量连接
for i in {1..100}; do
    ./tcp_client 127.0.0.1 8080 &
done

# 3. 观察连接状态
ss -tanp | grep -E "(SYN_SENT|ESTABLISHED|TIME_WAIT)"

# 4. 查看TCP统计
cat /proc/net/snmp | grep -i tcp

预期结果:

  • 理解各种TCP状态
  • 观察连接建立和关闭过程
  • 掌握连接管理技巧

排错

常见问题排查

问题1:连接被拒绝

# 检查服务是否监听
ss -tuln | grep 8080

# 检查防火墙
sudo iptables -L -n | grep 8080

# 检查服务状态
systemctl status service_name

问题2:大量TIME_WAIT连接

# 查看TIME_WAIT数量
ss -tan | grep TIME_WAIT | wc -l

# 启用TIME_WAIT复用
sudo sysctl -w net.ipv4.tcp_tw_reuse=1

# 调整TIME_WAIT超时
sudo sysctl -w net.ipv4.tcp_fin_timeout=30

问题3:频繁重传

# 查看重传统计
ss -ti | grep -i retrans

# 检查网络质量
ping -c 10 target_ip

# 调整重传参数
sudo sysctl -w net.ipv4.tcp_retries2=8

排错清单

  • [ ] 检查服务监听状态(ss -tuln)
  • [ ] 验证防火墙规则(iptables、ufw)
  • [ ] 确认网络连通性(ping、telnet)
  • [ ] 查看TCP统计信息(/proc/net/snmp)
  • [ ] 检查连接队列(somaxconn、backlog)
  • [ ] 分析重传情况(ss -ti)
  • [ ] 查看系统日志(dmesg、/var/log/syslog)
Prev
第3章 IP路由与三层转发
Next
第5章 应用层协议