深入理解TCP连接管理:三次握手与四次挥手

在这里插入图片描述

目录


一、前言

准备校招的时候,计网是绕不过去的一关。我先看了王道考研的视频课程,对TCP三次握手有了基本概念:客户端发SYN,服务器回SYN+ACK,客户端再发ACK。看起来很简单,但深入思考后发现有很多细节没搞懂:

  • 为什么必须是三次握手?两次不行吗?
  • 第三次握手丢失了怎么办?
  • 什么是SYN攻击?如何防御?
  • 四次挥手的TIME_WAIT状态是干什么的?

后来我写了个Reactor网络服务器项目,实际遇到了不少问题。比如服务器重启后端口被占用,报错"Address already in use";又比如发现服务器有很多CLOSE_WAIT状态的连接,导致文件描述符耗尽。这些问题逼着我去深入理解TCP连接管理的每个细节。

今天把我的学习过程分享出来,希望对你有帮助。

本文包含:

  • TCP三次握手的完整流程(状态变化、为什么是3次)
  • SYN攻击与SYN Cookie防御机制
  • TCP四次挥手的完整流程(TIME_WAIT、CLOSE_WAIT详解)
  • 实际问题排查(端口占用、连接泄漏)
  • Reactor项目中的连接管理实战

二、TCP三次握手深度解析

2.1 基本流程

先看一张完整的三次握手时序图:

客户端                                           服务器
CLOSED                                          CLOSED
  |                                                |
  |  socket() + connect()                   socket() + bind() + listen()
  |                                                |
  |                                             LISTEN
  |                                                |
  |-------------- SYN (seq=x) ------------------>|
  |                                                |
SYN_SENT                                       SYN_RCVD
  |                                                |
  |<------- SYN+ACK (seq=y, ack=x+1) ------------|
  |                                                |
  |-------------- ACK (ack=y+1) ---------------->|
  |                                                |
ESTABLISHED                                   ESTABLISHED
  |                                                |
  |<------------- 数据传输 ---------------------->|

第一次握手: 客户端发送SYN包

  • 发送:SYN=1, seq=x(初始序列号,随机)
  • 状态变化:CLOSED → SYN_SENT
  • 含义:客户端请求建立连接

第二次握手: 服务器回复SYN+ACK包

  • 发送:SYN=1, ACK=1, seq=y, ack=x+1
  • 状态变化:LISTEN → SYN_RCVD
  • 含义:服务器同意连接,并确认收到客户端的SYN

第三次握手: 客户端回复ACK包

  • 发送:ACK=1, ack=y+1
  • 状态变化:SYN_SENT → ESTABLISHED
  • 含义:客户端确认收到服务器的SYN

服务器收到ACK后,状态变为ESTABLISHED,连接建立完成。

2.2 为什么是三次握手?

这是面试必考题。很多人只知道"确认双方的收发能力",但面试官一追问细节就答不上来了。我们从两次握手的问题说起。

两次握手的问题:

假设只有两次握手:

  1. 客户端发送SYN
  2. 服务器回复SYN+ACK,连接建立

看起来没问题,但考虑这个场景:

时刻T1:客户端发送SYN1(因网络延迟,滞留在网络中)
时刻T2:客户端超时,重发SYN2
时刻T3:服务器收到SYN2,建立连接,数据传输完毕,连接关闭
时刻T4:延迟的SYN1到达服务器

问题来了:
- 两次握手下,服务器收到SYN1后立即建立连接
- 但客户端已经关闭了,不会再发数据
- 服务器白白分配了资源(TCB控制块、端口),浪费!

三次握手如何解决:

时刻T4:延迟的SYN1到达服务器
服务器:收到SYN1,回复SYN+ACK(第二次握手)
客户端:收到SYN+ACK,但这不是我发的连接请求!
        不回复ACK(或发送RST)
服务器:超时未收到ACK(第三次握手)
        放弃这次连接,不分配资源

本质原因: 三次握手让客户端有机会确认"这是我发起的连接请求",避免因延迟的旧连接请求导致服务器资源浪费。

面试标准答案:

三次握手的目的是确认双方的收发能力:

  • 第一次握手:服务器确认"客户端能发"
  • 第二次握手:客户端确认"服务器能收、能发",客户端确认"自己能收"
  • 第三次握手:服务器确认"客户端能收"

此外,三次握手还能防止因历史连接请求(延迟的SYN包)导致服务器资源浪费。

2.3 第三次握手可以携带数据吗?

可以!这是个冷门但有深度的考点。

前两次握手不能携带数据:

  • 原因:防止SYN泛洪攻击
  • 如果SYN包可以携带大量数据,攻击者可以伪造大量SYN包,消耗服务器带宽和资源

第三次握手可以携带数据:

  • 此时客户端已经是ESTABLISHED状态
  • 携带数据相当于第一个数据包,不会被用于攻击

TCP Fast Open(TFO):
现代优化,允许在SYN包中携带数据(需要Cookie验证)。

2.4 三次握手失败的场景

场景1:SYN丢失

客户端:发送SYN → 超时未收到SYN+ACK → 重传SYN
重传次数:tcp_syn_retries(默认6次)
重传间隔:1s, 2s, 4s, 8s, 16s, 32s(指数退避)
总超时时间:约127秒

场景2:SYN+ACK丢失

服务器:发送SYN+ACK → 超时未收到ACK → 重传SYN+ACK
重传次数:tcp_synack_retries(默认5次)
客户端:超时重传SYN

场景3:ACK丢失

服务器:未收到ACK,停留在SYN_RCVD状态 → 超时重传SYN+ACK
客户端:已经ESTABLISHED,发送数据
服务器:收到数据包,隐式确认ACK → 进入ESTABLISHED

注意场景3的处理很巧妙:客户端发送的数据包本身就带着ACK,服务器可以通过数据包进入ESTABLISHED状态。

2.5 SYN攻击与防御

SYN攻击原理:

攻击者大量伪造源IP,发送SYN包:

攻击者 → 服务器:SYN (源IP=1.1.1.1)
攻击者 → 服务器:SYN (源IP=2.2.2.2)
攻击者 → 服务器:SYN (源IP=3.3.3.3)
...发送数万个SYN包

服务器:
  1. 为每个SYN分配资源(TCB控制块)
  2. 加入半连接队列
  3. 等待ACK(永远不会来,因为IP是伪造的)
  4. 半连接队列满了 → 拒绝正常连接请求

传统防御方式:

  • 增大半连接队列(治标不治本)
  • 缩短SYN_RCVD超时时间
  • SYN Proxy(代理检查)

SYN Cookie防御机制:

核心思想:不分配资源,把状态信息编码到序列号中。

服务器收到SYN:
  1. 不分配TCB控制块
  2. 不加入半连接队列
  3. 计算Cookie:
     cookie = hash(源IP, 源端口, 目的IP, 目的端口, 时间戳, 密钥)
  4. 发送SYN+ACK,seq=cookie

客户端(真实):
  收到SYN+ACK,回复ACK (ack=cookie+1)

服务器收到ACK:
  1. 提取cookie = ack - 1
  2. 重新计算:expected_cookie = hash(...)
  3. 验证:cookie == expected_cookie?
  4. 验证通过:分配资源,建立连接

攻击者(伪造IP):
  不会回复ACK → 服务器不分配资源

Linux中启用SYN Cookie:

# 查看状态
cat /proc/sys/net/ipv4/tcp_syncookies

# 启用(1=启用,0=禁用)
echo 1 > /proc/sys/net/ipv4/tcp_syncookies

在我的Reactor项目中,这是默认开启的系统防护。


三、TCP四次挥手深度解析

3.1 基本流程

客户端(主动关闭方)                        服务器(被动关闭方)
ESTABLISHED                                ESTABLISHED
  |                                            |
  | close()                                    |
  |                                            |
  |------------- FIN (seq=u) ---------------->|
  |                                            |
FIN_WAIT_1                                 CLOSE_WAIT
  |                                            |
  |<------------ ACK (ack=u+1) ---------------|
  |                                            |
FIN_WAIT_2                                 CLOSE_WAIT
  |                                            |
  |                                      close() |
  |                                            |
  |<------------ FIN (seq=v) -----------------|
  |                                            |
TIME_WAIT                                  LAST_ACK
  |                                            |
  |------------- ACK (ack=v+1) -------------->|
  |                                            |
TIME_WAIT                                    CLOSED
  |                                            
  | 等待2MSL                                   
  |                                            
CLOSED                                         

第一次挥手: 客户端发送FIN

  • 发送:FIN=1, seq=u
  • 状态:ESTABLISHED → FIN_WAIT_1
  • 含义:客户端没有数据要发送了

第二次挥手: 服务器回复ACK

  • 发送:ACK=1, ack=u+1
  • 状态:ESTABLISHED → CLOSE_WAIT
  • 含义:服务器确认收到FIN

第三次挥手: 服务器发送FIN

  • 发送:FIN=1, seq=v
  • 状态:CLOSE_WAIT → LAST_ACK
  • 含义:服务器也没有数据要发送了

第四次挥手: 客户端回复ACK

  • 发送:ACK=1, ack=v+1
  • 状态:FIN_WAIT_2 → TIME_WAIT
  • 含义:客户端确认收到服务器的FIN

3.2 为什么是四次挥手?

这个问题的标准答案是"因为TCP是全双工的",但这个答案太抽象了。我们用实际场景说明。

为什么不能合并成三次?

第二次和第三次挥手能不能合并?答案是:有时可以,有时不行。

场景1:服务器没有数据要发送

客户端:FIN (我不发了)
服务器:ACK + FIN (我确认,我也不发了) ← 合并!
客户端:ACK (确认)

这种情况下,确实是三次挥手

场景2:服务器还有数据要发送

客户端:FIN (我不发了,但我还能收)
服务器:ACK (我确认,但我还有数据要发)
        继续发送剩余数据...
        数据发完了
        FIN (我也不发了)
客户端:ACK (确认)

这种情况下,必须是四次挥手

本质原因: TCP的关闭是独立的。客户端不发了,不代表服务器也不发了。第二次挥手只是确认"我知道你不发了",第三次挥手才是"我也不发了"。

面试标准答案:

建立连接需要三次握手,因为只需要同步双方的初始序列号。

关闭连接需要四次挥手,因为TCP是全双工的,双方都需要单独关闭发送方向:

  • 第一次:客户端关闭发送
  • 第二次:服务器确认
  • 第三次:服务器关闭发送
  • 第四次:客户端确认

第二次和第三次不能合并,因为服务器可能还有数据要发送。

3.3 TIME_WAIT状态详解

TIME_WAIT是最容易被问到的状态。在我的Reactor项目中,服务器重启时经常遇到"Address already in use"错误,就是因为TIME_WAIT导致的。

为什么需要TIME_WAIT?

主要有两个原因:

原因1:保证最后的ACK能到达

客户端发送最后的ACK后立即关闭:

客户端:ACK (ack=v+1) → CLOSED(有问题)
        |
        | ACK丢失
        ↓
服务器:超时未收到ACK → 重传FIN
        |
        ↓
客户端:已经CLOSED,收到FIN → 回复RST(端口不存在)
服务器:收到RST → 异常终止(有问题)

TIME_WAIT等待2MSL(Maximum Segment Lifetime),确保:

  • 如果ACK丢失,服务器会重传FIN
  • 客户端在TIME_WAIT期间能收到重传的FIN,重新发送ACK

原因2:防止旧连接的数据包干扰新连接

场景:客户端关闭连接后,立即用相同的四元组(源IP、源端口、目的IP、目的端口)建立新连接

旧连接:客户端:8888 → 服务器:80
        发送数据,seq=100
        关闭连接
        
新连接:客户端:8888 → 服务器:80(相同四元组)
        
旧连接的延迟数据包:seq=100 → 到达服务器
服务器:误以为是新连接的数据(问题出现)

TIME_WAIT等待2MSL,确保网络中所有旧连接的数据包都消失了(MSL是IP数据包在网络中的最大生存时间)。

2MSL的原因:

  • 1个MSL:客户端的ACK到达服务器的最大时间
  • 1个MSL:服务器重传的FIN到达客户端的最大时间
  • 2MSL:一个来回的最大时间

Linux中,MSL=30秒(不可配置),所以TIME_WAIT=60秒。

TIME_WAIT过多的问题:

在高并发场景下(比如压力测试),客户端会产生大量TIME_WAIT状态的连接:

  • 占用端口(每个连接需要一个端口)
  • 客户端端口范围有限(32768-60999,约2.8万个)
  • 端口耗尽 → 无法建立新连接

解决方法:

# 允许TIME_WAIT状态的socket被新连接复用
net.ipv4.tcp_tw_reuse = 1

# 快速回收TIME_WAIT连接(不推荐,可能导致数据混乱)
net.ipv4.tcp_tw_recycle = 0  # Linux 4.12后已废弃

3.4 CLOSE_WAIT状态详解

CLOSE_WAIT是服务器端容易出问题的状态。我在Reactor项目中遇到过CLOSE_WAIT堆积,导致文件描述符耗尽的问题。

CLOSE_WAIT产生的原因:

1. 客户端:发送FIN,主动关闭连接
2. 服务器:内核自动回复ACK,进入CLOSE_WAIT状态
3. 服务器:应用层应该调用close(),关闭连接
4. 如果应用层不调用close(),连接一直停留在CLOSE_WAIT(问题出现)

常见原因:

  • 应用层bug,忘记关闭连接
  • 应用层阻塞,无法及时处理连接关闭
  • 应用层死循环或死锁

危害:

  • 连接泄漏,占用文件描述符
  • 文件描述符耗尽 → 无法accept新连接
  • 服务器挂掉

排查方法:

# 查看CLOSE_WAIT连接数
netstat -an | grep CLOSE_WAIT | wc -l

# 查看哪个进程占用
lsof -i | grep CLOSE_WAIT

# 查看文件描述符使用情况
cat /proc/<pid>/fd | wc -l
ulimit -n  # 查看限制

在Reactor项目中的修复:

// 错误的写法
void handle_read_event(int connfd) {
    char buf[4096];
    int n = recv(connfd, buf, sizeof(buf), 0);
    
    if (n > 0) {
        process_data(buf, n);
    } else if (n == 0) {
        // 对方关闭连接
        epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, NULL);
        // 忘记close(connfd) → CLOSE_WAIT堆积
    }
}

// 正确的写法
void handle_read_event(int connfd) {
    char buf[4096];
    int n = recv(connfd, buf, sizeof(buf), 0);
    
    if (n > 0) {
        process_data(buf, n);
    } else if (n == 0) {
        // 对方关闭连接
        epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, NULL);
        close(connfd);  // 必须close
        
        // 最好记录日志
        LOG_INFO("Connection closed by peer, fd=%d", connfd);
    }
}

3.5 close() vs shutdown()

这是个常考但容易混淆的知识点。

close():

  • 关闭读和写两个方向
  • 引用计数减1,如果为0则真正关闭
  • 发送FIN,开始四次挥手
int connfd = accept(...);
// 使用connfd...
close(connfd);  // 完全关闭

shutdown():

  • 可以单独关闭读或写方向
  • 不管引用计数,立即关闭指定方向
  • shutdown(fd, SHUT_WR) 发送FIN,但还能接收数据
int connfd = accept(...);

// 半关闭:关闭写方向,但还能读
shutdown(connfd, SHUT_WR);  
// 发送FIN给对方
// 但还能recv()接收对方的数据

// 等对方也关闭后,再close
close(connfd);

使用场景:

  • close():大部分情况使用
  • shutdown():需要优雅关闭时使用(比如HTTP服务器发送完响应后,关闭写方向,但还要等客户端关闭)

四、Reactor项目实战

下面是我在Reactor项目中处理TCP连接管理的实际代码。

4.1 创建监听socket

int create_listen_socket(int port) {
    // 1. 创建socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0) {
        perror("socket");
        return -1;
    }
    
    // 2. 设置SO_REUSEADDR,避免TIME_WAIT导致的端口占用
    int opt = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, 
                   &opt, sizeof(opt)) < 0) {
        perror("setsockopt SO_REUSEADDR");
        close(listenfd);
        return -1;
    }
    
    // 3. bind
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);
    
    if (bind(listenfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("bind");
        close(listenfd);
        return -1;
    }
    
    // 4. listen
    // backlog=128:全连接队列的最大长度
    if (listen(listenfd, 128) < 0) {
        perror("listen");
        close(listenfd);
        return -1;
    }
    
    printf("Server listening on port %d\n", port);
    return listenfd;
}

关键点:

  • SO_REUSEADDR:允许处于TIME_WAIT状态的地址被重新bind,解决服务器重启问题
  • backlog=128:全连接队列大小,影响并发连接数

4.2 接受新连接

void handle_accept_event(int listenfd, int epollfd) {
    struct sockaddr_in client_addr;
    socklen_t addr_len = sizeof(client_addr);
    
    // accept获取新连接
    int connfd = accept(listenfd, 
                        (struct sockaddr*)&client_addr, 
                        &addr_len);
    if (connfd < 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            // 没有新连接了,正常
            return;
        }
        perror("accept");
        return;
    }
    
    // 打印客户端信息
    char ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &client_addr.sin_addr, ip, sizeof(ip));
    printf("New connection from %s:%d, fd=%d\n", 
           ip, ntohs(client_addr.sin_port), connfd);
    
    // 设置非阻塞
    set_nonblocking(connfd);
    
    // 加入epoll,监听读事件
    struct epoll_event ev;
    ev.events = EPOLLIN | EPOLLET;  // 边缘触发
    ev.data.fd = connfd;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &ev) < 0) {
        perror("epoll_ctl ADD");
        close(connfd);
        return;
    }
}

4.3 处理读事件(检测对方关闭)

void handle_read_event(int connfd, int epollfd) {
    char buf[4096];
    
    while (1) {
        int n = recv(connfd, buf, sizeof(buf), 0);
        
        if (n > 0) {
            // 正常接收数据
            buf[n] = '\0';
            printf("Received %d bytes: %s\n", n, buf);
            
            // 回声服务器:原样返回
            send(connfd, buf, n, 0);
            
        } else if (n == 0) {
            // 对方关闭连接(收到FIN)
            printf("Connection closed by peer, fd=%d\n", connfd);
            
            // 从epoll删除
            epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, NULL);
            
            // 关闭连接(触发四次挥手)
            close(connfd);
            break;
            
        } else {
            // n < 0
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                // 非阻塞socket,数据读完了
                break;
            } else {
                // 真实错误
                perror("recv");
                epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, NULL);
                close(connfd);
                break;
            }
        }
    }
}

关键点:

  • recv()返回0:对方发送了FIN,连接关闭
  • 必须调用close(connfd):否则停留在CLOSE_WAIT状态
  • 边缘触发模式:必须循环读取直到EAGAIN

4.4 主动关闭连接

void close_connection(int connfd, int epollfd) {
    // 1. 从epoll删除
    epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, NULL);
    
    // 2. 关闭连接(触发四次挥手)
    close(connfd);
    
    // 注意:close后,connfd进入FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT
    // TIME_WAIT会持续60秒(2MSL)
    
    printf("Connection closed, fd=%d\n", connfd);
}

4.5 优雅关闭

对于HTTP服务器,更好的做法是优雅关闭:

void graceful_close(int connfd, int epollfd) {
    // 1. 发送完响应后,关闭写方向
    shutdown(connfd, SHUT_WR);
    // 此时发送FIN给客户端
    
    // 2. 继续接收数据,直到对方关闭
    // (在handle_read_event中处理recv返回0的情况)
    
    // 3. 收到对方的FIN后,调用close
    // close(connfd);
}

4.6 常见问题排查

问题1:Address already in use

# 原因:TIME_WAIT状态占用端口
# 解决:设置SO_REUSEADDR

int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

问题2:大量CLOSE_WAIT

# 原因:应用层没有close
# 解决:检查代码,确保recv返回0时调用close

if (n == 0) {
    close(connfd);  // 必须
}

问题3:连接超时

# 原因:防火墙丢弃SYN包,或服务器半连接队列满
# 解决:
# 1. 增大半连接队列
net.ipv4.tcp_max_syn_backlog = 8192

# 2. 启用SYN Cookie
net.ipv4.tcp_syncookies = 1

# 3. 检查防火墙
iptables -L -n

五、面试高频题总结

必背知识点(10个)

  1. 三次握手的完整流程和状态变化

    • SYN_SENT、SYN_RCVD、ESTABLISHED
  2. 为什么是三次握手,不是两次或四次

    • 防止历史连接请求导致资源浪费
    • 确认双方收发能力
  3. SYN攻击原理和SYN Cookie防御

    • 半连接队列满,拒绝服务
    • 不分配资源,状态编码到seq
  4. 四次挥手的完整流程和状态变化

    • FIN_WAIT_1、FIN_WAIT_2、TIME_WAIT、CLOSE_WAIT
  5. TIME_WAIT的作用和2MSL的原因

    • 保证最后的ACK到达
    • 防止旧连接数据包干扰新连接
  6. CLOSE_WAIT产生的原因和解决方法

    • 应用层没有close
    • 必须在recv返回0时调用close
  7. SO_REUSEADDR的作用

    • 允许TIME_WAIT状态的地址被复用
    • 解决服务器重启问题
  8. close() vs shutdown()的区别

    • close关闭读写,shutdown可单独关闭
    • shutdown用于优雅关闭
  9. 半连接队列和全连接队列

    • 半连接:SYN_RCVD状态的连接
    • 全连接:ESTABLISHED状态,等待accept
  10. 如何在项目中处理连接管理

    • socket、bind、listen、accept
    • epoll监听,recv检测关闭,及时close

面试标准答案模板

问:为什么TCP需要三次握手?

TCP需要三次握手有两个原因:

  1. 确认双方的收发能力:

    • 第一次握手:服务器确认客户端能发
    • 第二次握手:客户端确认服务器能收、能发,客户端确认自己能收
    • 第三次握手:服务器确认客户端能收
  2. 防止历史连接请求导致资源浪费:

    • 如果只有两次握手,客户端发送的延迟SYN包到达服务器时,服务器会立即建立连接
    • 但客户端已经关闭,不会发送数据,服务器白白分配了资源
    • 三次握手让客户端有机会确认"这是我发起的连接",拒绝旧连接请求

在我的Reactor项目中,我理解了listen和accept的作用:listen创建半连接队列,三次握手完成后连接移入全连接队列,accept从全连接队列取出连接。

问:TIME_WAIT状态的作用是什么?

TIME_WAIT状态有两个作用:

  1. 保证最后的ACK能到达:

    • 如果最后的ACK丢失,服务器会重传FIN
    • 客户端在TIME_WAIT期间能收到重传的FIN,重新发送ACK
    • 等待2MSL,确保服务器能收到ACK(一个来回的时间)
  2. 防止旧连接的数据包干扰新连接:

    • 如果立即关闭,新连接可能使用相同的四元组
    • 旧连接的延迟数据包可能被新连接误接收
    • 等待2MSL,确保旧连接的数据包在网络中消失

Linux中,MSL=30秒,所以TIME_WAIT=60秒。在我的项目中,通过设置SO_REUSEADDR,解决了服务器重启时的端口占用问题。


六、总结

TCP连接管理是计网的核心知识点,也是面试必考题。学习过程中,我的体会是:

理论学习:

  1. 看视频课程(王道考研),建立基本概念
  2. 画时序图和状态机图,理解状态变化
  3. 思考"为什么":为什么是三次?为什么TIME_WAIT?

实践验证:

  1. 写Reactor项目,实际处理连接管理
  2. 遇到问题(端口占用、CLOSE_WAIT),查资料深入理解
  3. 抓包分析(tcpdump/Wireshark),观察真实的三次握手和四次挥手

面试准备:

  1. 整理标准答案模板,背诵核心知识点
  2. 结合项目讲解,展示实践经验
  3. 准备追问:SYN攻击、TIME_WAIT、CLOSE_WAIT

下一篇文章,我们深入聊TCP的可靠性机制:滑动窗口、超时重传、拥塞控制。这部分更难,但也更有深度。


参考资料

  • 王道考研《计算机网络》视频课程
  • RFC 793:TCP协议规范
  • Linux内核源码:net/ipv4/tcp_input.c

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐