linux学习笔记(30)网络编程——TCP协议详解
TCP握手挥手
TCP 协议(传输控制协议)为应用层提供可靠的、面向连接的和基于流的服务。TCP 协
议使用超时重传、确认应答等方式来确保数据包被正确的发送至目的端,因此 TCP 服务是
可靠的。使用 TCP 协议通信的双方必须先建立 TCP 连接,并在内核中为该连接维持一些必
要的数据结构,比如连接状态,读写缓冲区等。当通信结束时,双方必须关闭连接以释放这
些内核数据。TCP 服务是基于流的,基于流的数据没有边界(长度)限制,它源源不断地从
通信地一端流入另一端。发送端可以逐个字节地向数据流中写入数据,接收端可以逐个字节
地将它们读出

三次握手全过程
第一次握手:客户端 → 服务器
客户端:"你好!能听到我吗?" (SYN)
第二次握手:服务器 → 客户端
服务器:"能听到!你也能听到我吗?" (SYN + ACK)
第三次握手:客户端 → 服务器
=== TCP三次握手模拟 ===
第一次握手:
客户端 → 服务器: SYN (seq=1000)
第二次握手:
服务器 → 客户端: SYN + ACK (seq=2000, ack=1001)
第三次握手:
客户端 → 服务器: ACK (seq=1001, ack=2001)
✅ 连接建立成功!可以开始数据传输了
结合Socket代码理解
服务器端(接电话的人):
// 这些函数调用对应三次握手:
int server_fd = socket(AF_INET, SOCK_STREAM, 0); // 准备电话
bind(server_fd, ...); // 公布电话号码
listen(server_fd, 5); // 打开电话铃声// 到这里,服务器在等待第一次握手(SYN)int client_fd = accept(server_fd, NULL, NULL);
// accept() 内部完成了:
// 1. 收到SYN(第一次握手)
// 2. 发送SYN+ACK(第二次握手)
// 3. 收到ACK(第三次握手)
// 4. 返回新的socket用于通信
客户端(打电话的人):
// 这些函数调用对应三次握手:
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 准备电话// connect() 内部完成了三次握手:
connect(sockfd, ...);
// 1. 发送SYN(第一次握手)
// 2. 收到SYN+ACK(第二次握手)
// 3. 发送ACK(第三次握手)
// 4. 连接建立成功
为什么需要三次握手?
两次握手的问题:
情况:网络延迟导致旧的连接请求突然到达客户端 服务器|--旧SYN-->| (延迟的包突然到达)| | 服务器以为新连接,回复SYN+ACK| | 但客户端早已忘记这个连接!| | → 服务器浪费资源等待永远不会来的数据
三次握手解决:
客户端 服务器|--旧SYN-->| (延迟的包)|<-SYN+ACK-| 服务器回复| | 但客户端不会回复ACK(因为不记得这个连接)| | → 服务器超时后关闭,不浪费资源
如果tcp第三次握手没收到ACK会怎么样?


标准
"如果三次握手中的第三次ACK丢失,服务器会启动超时重传机制,多次重传SYN+ACK包。如果客户端很快发送数据,数据包中的ACK信息可以弥补丢失的第三次ACK,连接仍能建立。如果客户端不立即发送数据,服务器在多次重传失败后会重置连接。"
加分回答:
"这体现了TCP的 robustness 原则:单个包丢失不会立即导致连接失败,通过重传和数据包捎带确认机制提供了容错能力。实际中由于客户端通常很快发送数据,这种情况很少导致连接真正失败。"
序列号的作用
序列号 = 给数据包编号
- 确保数据按顺序到达
- 检测丢失的数据包
- 防止旧的重复数据包
在三次握手中,双方交换初始序列号:
- 客户端:我從1000开始编号
- 服务器:我從2000开始编号
四次挥手全过程
第一次挥手:客户端 → 服务器
客户端:"我说完了" (FIN)
第二次挥手:服务器 → 客户端
服务器:"好的,我知道你说完了" (ACK) // 但服务器可能还有话要说...
第三次挥手:服务器 → 客户端
服务器:"我也说完了" (FIN)
第四次挥手:客户端 → 服务器
客户端:"好的,我知道你说完了" (ACK)
结合Socket代码理解
客户端主动关闭连接:
// 客户端代码中:
close(sockfd); // 这一行触发了四次挥手!// 实际上发生了:
// 1. 发送FIN ("我要关闭连接了")
// 2. 接收ACK ("我知道你要关了")
// 3. 接收FIN ("我也要关了")
// 4. 发送ACK ("我知道你也要关了")
// 5. 真正关闭连接
服务器端:
// 服务器代码中: close(client_fd);
// 也会触发四次挥手
// 但通常客户端先发起关闭
为什么需要四次挥手?三次不行吗?
关键原因:TCP连接是全双工的
全双工 = 双方可以同时发送数据
就像打电话:
- 你说完了,不代表对方也说完了
- 对方可能还有话要说
- 所以要分别确认双方都说完了
如果只有三次挥手会怎样?
你: "我说完了,挂了啊" (FIN)
朋友: "好的" (ACK)
// 你直接挂断... // 但朋友还有重要的话没说完!😠
四次挥手确保:
你: "我说完了" (FIN)
朋友: "好的" (ACK)
朋友: "我也说完了" (FIN)
你: "好的,拜拜" (ACK)
// 双方都把话说完了,安心挂断 👍
四次挥手也可以优化成三次(当服务器没有数据要发送时
客户端 → 服务器:FIN (我说完了)
服务器 → 客户端:ACK + FIN (我知道你要挂了 + 我也说完了)← 合并了!
客户端 → 服务器:ACK (我知道你也要挂了)
为什么可以合并?
关键点:TCP的延迟确认机制
- 当服务器收到FIN时,如果它正好也要关闭,或者没有数据要发送了
- 它可以把ACK和FIN放在同一个包里发送
- 这样就节省了一个网络往返
技术上的触发条件
// 当客户端调用 close() 时:
close(sockfd); // 发送FIN// 如果服务器:
// 1. 没有数据要发送
// 2. 接收缓冲区是空的
// 3. 正好也准备关闭连接
// 那么服务器的TCP栈可能会把ACK和FIN合并发送
我们只能控制close的调用时机,但依然无法确保是否三次挥手,
这要取决于tcp那时是否自动优化,准确的三次or四次不能被人为控制
缓冲区
send是把信息发送到缓冲区了
recv才是接收到缓冲区的信息

发送跟接收不是一一对应的,是各按各的命令每次发送和接收数量
缓冲区的关键特性
阻塞 vs 非阻塞:
// 阻塞模式(默认)
char buffer[100];
int bytes = read(sockfd, buffer, sizeof(buffer));
// 如果接收缓冲区空,程序会停在这里等待数据// 非阻塞模式
fcntl(sockfd, F_SETFL, O_NONBLOCK);
int bytes = read(sockfd, buffer, sizeof(buffer));
// 如果接收缓冲区空,立即返回-1,不会等待
缓冲区大小问题:
#include <stdio.h>
#include <string.h>int main() {printf("=== 缓冲区大小问题 ===\n\n");char small_buffer[10]; // 太小的缓冲区char* big_message = "Hello World! This is too long!";printf("问题: 数据太大,缓冲区太小\n");printf("数据: '%s' (%zu字节)\n", big_message, strlen(big_message));printf("缓冲区大小: 10字节\n\n");// 危险!可能缓冲区溢出// strcpy(small_buffer, big_message); // 不要这样做!// 安全做法:检查长度if (strlen(big_message) < sizeof(small_buffer)) {strcpy(small_buffer, big_message);} else {printf("安全处理: 数据太大,只拷贝部分内容\n");strncpy(small_buffer, big_message, sizeof(small_buffer) - 1);small_buffer[sizeof(small_buffer) - 1] = '\0';}printf("结果: %s\n", small_buffer);return 0;
}
缓冲区滑动窗口机制

缓冲区的重要性
为什么需要缓冲区?
- 速度匹配:
应用程序:快速产生数据 网络:慢速发送数据 缓冲区:中间调节,避免卡顿
- 流量控制:
发送太快 → 接收方缓冲区满 → 停止发送 发送太慢 → 接收方等待数据 → 提高发送速度
- 数据组装:
网络数据可能分多次到达: 数据1: "Hello " 数据2: "World!" 缓冲区: "Hello World!" ← 组装成完整消息
常见缓冲区问题
问题1:缓冲区太小
char buf[10]; read(sockfd, buf, 100); // 危险!可能溢出
问题2:不检查返回值
char buf[100]; read(sockfd, buf, sizeof(buf)); // 不知道读了多少数据
printf("收到: %s\n", buf); // 如果只收到部分数据,可能乱码
问题3:粘包问题
// 客户端快速发送两条消息:
write(sockfd, "Hello", 5);
write(sockfd, "World", 5);
// 服务器可能一次收到:"HelloWorld" ← 粘在一起了!
最佳实践
// 1. 总是检查返回值
int bytes = read(sockfd, buffer, sizeof(buffer) - 1);
if (bytes > 0) {buffer[bytes] = '\0'; // 添加字符串结束符printf("收到: %s\n", buffer);
}// 2. 使用足够大的缓冲区
#define BUFFER_SIZE 4096
char buffer[BUFFER_SIZE];3. 处理粘包问题(添加消息边界)方法1: 固定长度方法2: 使用分隔符方法3: 添加消息头(长度信息)
netstat -natp 用来查看网络连接状态 命令(也可看接收和发送缓冲区的数据状态
在握手 / 挥手阶段,ACK 号用来确认对方的连接请求;
在数据传输阶段,ACK 号用来确认已收到的数据字节。
总结tcp为什么可靠
1. 面向连接的三次握手 🔄
技术机制:
- 建立连接前必须完成"三次握手"
- 确保双方都准备好接收数据
- 交换初始序列号,为后续数据传输做准备
生活例子: 📞
就像重要会议前的确认:
你: "会议10点开始,准备好了吗?" (SYN)
同事:"准备好了!你也OK吗?" (SYN + ACK)
你: "我也OK,开始吧!" (ACK)
✅ 确保双方都就绪才开始重要事务
2. 序号与确认机制 🔢
技术机制:
- 每个字节都有唯一序号
- 接收方发送ACK确认已收到的数据
- 确认号表示期望接收的下一字节序号
- 可累计确认多个数据包
生活例子: 📦
就像重要会议前的确认:
你: "会议10点开始,准备好了吗?" (SYN)
同事:"准备好了!你也OK吗?" (SYN + ACK)
你: "我也OK,开始吧!" (ACK)
✅ 确保双方都就绪才开始重要事务
3. 智能重传机制 ⚡(此处ack非挥手握手的ack)
技术机制:
- 超时重传:发送方未按时收到ACK则重发
- 快速重传:收到3个重复ACK立即重传
- 为什么是 3 个?
- 1~2 个重复 ACK 可能只是网络乱序造成的,不一定是丢包
- 连续 3 个重复 ACK 被认为是高概率丢包的信号(RFC 规定的经验值)
- 这样可以在超时前快速恢复,减少等待时间
- 选择性重传:只重传丢失的部分而非全部
生活例子: 🚚
就像智能快递重发:
正常:发货 → 收到签收 → 继续发
超时:发货(等3天没签收) → 重新发货
快速:连续3个"没收到#005" → 立即重发#005
选择:只重发丢失的#005,不重发#006-010
✅ 多种方式确保重要物品必达
4. 流量控制(滑动窗口) 🪟
技术机制:
- 接收方通过窗口大小告知发送方可发送的数据量
- 防止发送方速度超过接收方处理能力
- 窗口大小随接收方缓冲区状态动态调整
生活例子: 🏠
就像仓库管理:
仓库:"现在有100平米空位" (窗口大小=100)
供应商:"好的,发100平米的货"
仓库:"卖出去50平米,现在有150平米空位" (窗口=150)
供应商:"增加发货到150平米"
✅ 按仓库容量动态调整发货量
5. 拥塞控制 🚦
技术机制:
- 慢启动:连接初期指数增加发送量
- 拥塞避免:接近网络容量时线性增长
- 快速恢复:发生丢包时快速调整发送速率
生活例子: 🛣️
就像文件校对:
发送前:计算校验码"ABC123"
传输中:数据可能被干扰
接收后:重新计算校验码"ABC124" → 错误!
要求重发,直到收到"ABC123" ✓
✅ 确保信息准确无误
6. 数据完整性保障 ✅
技术机制:
- 每个报文段包含校验和
- 检测数据在传输中是否被损坏
- 损坏的数据包会被丢弃并触发重传
生活例子: 📝
就像文件校对:
发送前:计算校验码"ABC123"
传输中:数据可能被干扰
接收后:重新计算校验码"ABC124" → 错误!
要求重发,直到收到"ABC123" ✓
✅ 确保信息准确无误
7. 其他辅助机制 🛠️
技术机制:
- 按序交付:接收方重新排序乱序到达的数据包
- 丢弃重复:自动识别并丢弃重复数据
- TCP保活:长时间无通信时检测连接状态
生活例子: 🧩
就像拼图游戏:
按序:收到乱序拼图块"1,3,2,5,4" → 排成"1,2,3,4,5"
去重:发现两个"拼图块3" → 丢掉多余的
保活:长时间不动 → 问"还在玩吗?"
✅ 保证最终结果的完整性和正确性
简单理解
TCP 通过 "序号 + 确认 + 重传 + 流量控制 + 拥塞控制" 的组合,在不可靠的 IP 网络上实现了可靠的字节流传输。