当前位置: 首页 > news >正文

10个Tcp三次握手四次挥手题目整理

TCP三次握手与四次挥手:核心知识点整理

这篇文章整理了TCP连接管理相关的核心问题,包括自己的理解、踩过的坑,以及在C++ Reactor项目中的实践经验。
在这里插入图片描述

目录

  • 前言
  • Q1: TCP三次握手的完整过程
  • Q2: 为什么需要三次握手?两次不行吗?
  • Q3: 第三次握手可以携带数据吗?
  • Q4: 什么是SYN攻击?如何防御?
  • Q5: 什么是半连接队列和全连接队列?
  • Q6: TCP四次挥手的完整过程
  • Q7: TIME_WAIT状态的作用
  • Q8: 大量CLOSE_WAIT说明什么问题?
  • Q9: close() vs shutdown()
  • Q10: 在项目中如何处理连接关闭
  • 学习总结

前言

这段时间在看王道的计算机网络课程,发现TCP连接管理这部分概念特别多:三次握手、四次挥手、TIME_WAIT、CLOSE_WAIT…光记住流程还不够,更重要的是理解为什么要这样设计。

这篇文章整理了我认为最重要的10个问题,每个问题我都会写下自己的理解,以及在实现Reactor项目时遇到的实际问题。


Q1: TCP三次握手的完整过程

我的理解

TCP建立连接需要三次握手,完整流程如下:

第一次握手:

客户端发送:SYN=1, seq=x
客户端状态:CLOSED → SYN_SENT

客户端向服务器发起连接请求。

第二次握手:

服务器发送:SYN=1, ACK=1, seq=y, ack=x+1
服务器状态:LISTEN → SYN_RCVD

服务器确认收到请求,同时也向客户端发起连接请求。

第三次握手:

客户端发送:ACK=1, ack=y+1
客户端:SYN_SENT → ESTABLISHED
服务器:SYN_RCVD → ESTABLISHED

客户端确认,连接建立完成。

状态变化图:

客户端                           服务器
CLOSED                          LISTEN|                               ||--- SYN, seq=x -------------->||                               |
SYN_SENT                      SYN_RCVD|                               ||<-- SYN+ACK, seq=y, ack=x+1 --||                               ||--- ACK, ack=y+1 ------------->||                               |
ESTABLISHED                   ESTABLISHED

我原来的疑问

Q:为什么ACK号是x+1,而不是x?

一开始我也搞不清楚,后来理解了:

  • seq表示"我这次发送的数据从序号x开始"
  • ack表示"我期望你下次从序号x+1开始发"
  • 所以确认号永远是"对方seq + 1"

Q:客户端和服务器的状态为什么不同?

因为两者的角色不同:

  • 客户端是主动发起方,状态是:CLOSED → SYN_SENT → ESTABLISHED
  • 服务器是被动接受方,状态是:LISTEN → SYN_RCVD → ESTABLISHED

Q2: 为什么需要三次握手?两次不行吗?

我的理解

看完王道视频后,我总结了两个关键原因:

原因1:确认双方的收发能力

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

如果只有两次握手,服务器无法确认"客户端能收"。

原因2:防止历史连接

这个例子帮我理解了:

场景:客户端发送的旧SYN因为网络延迟,在连接关闭后才到达服务器两次握手的问题:旧SYN → 服务器:建立连接服务器:分配资源,等待数据客户端:不知情,不会发数据结果:服务器资源浪费三次握手的解决:旧SYN → 服务器:发送SYN+ACK客户端:收到后发现不是自己期望的连接,发送RST服务器:释放资源结果:避免了错误连接

项目中的体现

在实现Reactor项目时,我用accept()从全连接队列中取出已经完成三次握手的连接:

int connfd = accept(listenfd, NULL, NULL);
// 此时TCP已经完成了三次握手
// connfd已经是ESTABLISHED状态

这个过程对应用层是透明的,内核自动完成了三次握手。


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

我的理解

可以!

前两次握手不能携带数据,因为连接还没完全建立,如果允许携带数据,攻击者可以发送大量带数据的SYN,造成SYN Flood攻击。

但第三次握手时,客户端已经进入ESTABLISHED状态,连接已经建立,所以可以携带数据。

实际应用

HTTP/1.1的请求就是在第三次握手中发送的:

第一次:客户端 → 服务器:SYN
第二次:服务器 → 客户端:SYN+ACK
第三次:客户端 → 服务器:ACK + HTTP请求(GET /index.html)

这样可以节省一个RTT,提高效率。


Q4: 什么是SYN攻击?如何防御?

我的理解

SYN攻击原理:

攻击者发送大量SYN请求(伪造IP地址),但不回复ACK。服务器会为每个SYN分配资源并等待,直到超时(默认63秒)。大量半连接会占满服务器资源,导致无法处理正常请求。

防御方法:SYN Cookie

最有效的防御方法是SYN Cookie:

  • 不分配资源,将连接信息编码到seq中
  • seq = hash(客户端IP, 端口, 服务器IP, 端口, 密钥)
  • 收到ACK后验证Cookie,再分配资源

这样攻击者发再多SYN也不会占用服务器资源。

在Linux中启用:

echo 1 > /proc/sys/net/ipv4/tcp_syncookies

项目中的考虑

在部署Reactor服务器时,我查了一下系统配置:

cat /proc/sys/net/ipv4/tcp_syncookies
# 输出:1(已启用)

现代Linux系统默认都开启了SYN Cookie,所以不用太担心SYN攻击。


Q5: 什么是半连接队列和全连接队列?

我的理解

服务器维护两个队列:

半连接队列(SYN队列):

  • 存储处于SYN_RCVD状态的连接
  • 收到SYN后,发送SYN+ACK,加入半连接队列
  • 队列长度:tcp_max_syn_backlog

全连接队列(Accept队列):

  • 存储处于ESTABLISHED状态的连接
  • 收到第三次握手ACK后,从半连接队列移到全连接队列
  • accept()从这里取出连接
  • 队列长度:min(somaxconn, backlog)

项目中遇到的问题

一开始我的Reactor服务器在高并发时会丢连接,后来发现是全连接队列满了。

原因分析:

// 我的代码
listen(listenfd, 5);  // backlog太小了!

解决方法:

listen(listenfd, 128);  // 增大backlog

同时检查系统参数:

cat /proc/sys/net/core/somaxconn
# 输出:128

现在高并发下也不会丢连接了。


Q6: TCP四次挥手的完整过程

我的理解

TCP关闭连接需要四次挥手:

第一次挥手:

客户端:调用close(),发送FIN=1
状态:ESTABLISHED → FIN_WAIT_1

第二次挥手:

服务器:收到FIN,发送ACK
服务器:ESTABLISHED → CLOSE_WAIT
客户端:FIN_WAIT_1 → FIN_WAIT_2

第三次挥手:

服务器:发送完数据后,调用close(),发送FIN
状态:CLOSE_WAIT → LAST_ACK

第四次挥手:

客户端:收到FIN,发送ACK
客户端:FIN_WAIT_2 → TIME_WAIT(等待2MSL)→ CLOSED
服务器:LAST_ACK → CLOSED

我原来的疑问

Q:为什么关闭需要四次,而建立只需要三次?

因为TCP是全双工的:

  • 建立连接时,双方都没有数据要发送,可以把SYN和ACK合并
  • 关闭连接时,一方想关闭,但另一方可能还有数据要发送,不能立刻关闭
  • 所以ACK和FIN通常不能合并,需要四次

Q7: TIME_WAIT状态的作用

我的理解

TIME_WAIT持续2MSL(Linux默认60秒),有两个作用:

作用1:确保最后的ACK能到达对方

如果最后的ACK丢失了:

客户端                      服务器|-------- ACK ------------->|  X(丢失)|                           |
TIME_WAIT                   LAST_ACK|                           |(超时,重发FIN)|<------- FIN --------------||-------- ACK ------------->|(重新发送)

如果客户端立刻关闭,就无法收到重发的FIN,服务器就无法正常关闭。

作用2:防止旧连接的数据干扰新连接

关闭后,网络中可能还有延迟的数据包。TIME_WAIT等待2MSL,确保所有旧数据包都消失了,才允许建立相同四元组的新连接。

项目中遇到的问题

我的Reactor服务器关闭后,立刻重启会报错:

bind error: Address already in use

原因: 服务器主动关闭连接,进入TIME_WAIT,端口被占用60秒。

解决方法: 设置SO_REUSEADDR

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

这样即使处于TIME_WAIT状态,也可以重新bind。


Q8: 大量CLOSE_WAIT说明什么问题?

我的理解

CLOSE_WAIT状态表示:对方已经关闭连接(发送了FIN),但我方还没调用close()

如果出现大量CLOSE_WAIT,说明程序有BUG:收到对方的FIN后,忘记调用close()

项目中踩过的坑

一开始我的代码:

// 错误代码
int n = recv(connfd, buf, sizeof(buf), 0);
if (n == 0) {// 对方关闭,进入CLOSE_WAIT// 但忘记调用close()// 一直停留在CLOSE_WAIT状态
}

正确做法:

if (n == 0) {// 检测到对方关闭,立刻关闭连接epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, NULL);close(connfd);  // 必须调用!LOG_INFO("Connection closed by peer, fd=%d", connfd);
}

检测方法:

netstat -an | grep CLOSE_WAIT | wc -l

现在我的服务器不会有CLOSE_WAIT堆积了。


Q9: close() vs shutdown()

我的理解

在学习过程中,我发现了两个关闭连接的函数:

close():

  • 彻底关闭连接,发送FIN
  • 引用计数-1,为0时才真正关闭
  • 无法控制只关闭读或写

shutdown():

  • 可以只关闭读端或写端
  • 立即关闭,不管引用计数
  • SHUT_WR:关闭写端,发送FIN,但还能读
  • SHUT_RD:关闭读端,不发送FIN
  • SHUT_RDWR:关闭读写,相当于close()

项目中的应用

有时候需要只关闭写端,但还能继续读取数据:

void graceful_shutdown(int connfd) {// 1. 关闭写端,发送FINshutdown(connfd, SHUT_WR);// 2. 继续读取对方可能发送的数据char buf[1024];while (true) {int n = recv(connfd, buf, sizeof(buf), 0);if (n == 0) {// 对方也关闭了break;}// 处理数据...}// 3. 彻底关闭close(connfd);
}

Q10: 在项目中如何处理连接关闭

我的实现

在Reactor项目中,我需要处理两种情况:

情况1:检测对方关闭(被动关闭)

if (events[i].events & EPOLLIN) {int n = recv(connfd, buf, sizeof(buf), 0);if (n == 0) {// 对方关闭连接(收到FIN)epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, NULL);close(connfd);LOG_INFO("Connection closed by peer");}else if (n < 0) {// 错误处理if (errno != EAGAIN && errno != EWOULDBLOCK) {epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, NULL);close(connfd);}}else {// 处理数据}
}

情况2:主动关闭连接

// HTTP短连接:处理完请求后主动关闭
void handle_http_request(int connfd) {// 接收请求// 处理请求// 发送响应// 主动关闭close(connfd);  // 发送FIN,进入FIN_WAIT_1
}

注意事项:

  1. 检测到recv() == 0,立刻close()(避免CLOSE_WAIT)
  2. 所有错误路径都要close()(避免fd泄漏)
  3. 主动关闭会进入TIME_WAIT(60秒)
  4. 设置SO_REUSEADDR,允许服务器重启

学习总结

这段时间系统复习TCP连接管理,收获很大:

理论层面:

  • 理解了三次握手和四次挥手的完整流程
  • 搞清楚了各种状态的含义和转换条件
  • 知道了TIME_WAIT、CLOSE_WAIT的作用

实践层面:

  • 在项目中正确处理连接关闭
  • 解决了Address already in use的问题
  • 避免了CLOSE_WAIT堆积

遇到的坑:

  1. 忘记调用close()导致CLOSE_WAIT堆积
  2. listen()的backlog太小导致丢连接
  3. 没设置SO_REUSEADDR导致无法快速重启

下一步学习:

  • TCP可靠传输机制(序列号、ACK、重传)
  • 滑动窗口和拥塞控制
  • HTTP协议和缓存机制

参考资料

  • 王道计算机网络视频课程

这篇文章记录了我对TCP连接管理的理解和项目实践经验。如果有错误或不准确的地方,欢迎指正!

http://www.dtcms.com/a/577990.html

相关文章:

  • tcp_connect_v4接口
  • SELinux 文件上下文管理详解:从基础到实战
  • 10个TCP可靠性与拥塞控制题目整理
  • 天津建设网站培训房地产怎么做网站推广
  • 20251106在荣品RD-RK3588开发板的Android13系统下配置单5GHz的softAP模式以提高网速
  • 有没有做长图的网站如何制作网站教程视频
  • Photoshop - Photoshop 工具栏(23)单列选框工具
  • 计算机图形中的法线矩阵:深入理解与应用
  • MySQL入门练习50题
  • SSM公办小学网络报名系统f3d3p(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 可以做投票的网站深圳网站建设制作设计企业
  • 在飞牛nas中docker使用nas挂载的硬盘问题(docker开机后自动重启)
  • 告别文件混乱!Adobe Bridge 2026 全媒体可视化管理,让设计流程更顺畅
  • 记录kubelet错误:Could not open resolv conf file
  • MATLAB基于Theil不等系数的IOWHA算子组合预测模型
  • 河南旅游集团 网站建设计算机是学什么内容的
  • 社交网站页面设计长春火车站是哪个站
  • 算法题(Python)数组篇 | 4.长度最小的子数组
  • 噬菌体展示技术:基因型 - 表型统一的分子筛选与研发利器
  • 江西网站开发公司电话宁夏水利建设工程网站
  • Ngram Overlap Example Selector in langchain
  • 温州网站改版哪家好郑州建设网站推广公司
  • 长沙网站推广 下拉通推广网站调用时间
  • [手写系列]Go手写db — — 第七版(实现Disk存储引擎、Docker化支持)
  • win11系统 Android Studio AVD 模拟器创建【记录】
  • 架构论文《论UP(统一过程)在开发中的设计和应用》
  • 中文域名网站好不好优化公司网站建设整体架构
  • 【LeetCode】102. 二叉树的层序遍历
  • 梅州市做试块网站wordpress mo
  • wps excel中把特定几列除以某一列,然后以百分比显示