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

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. 速度匹配
应用程序:快速产生数据 网络:慢速发送数据 缓冲区:中间调节,避免卡顿
  1. 流量控制
发送太快 → 接收方缓冲区满 → 停止发送 发送太慢 → 接收方等待数据 → 提高发送速度
  1. 数据组装
网络数据可能分多次到达: 数据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 网络上实现了可靠的字节流传输。
http://www.dtcms.com/a/478546.html

相关文章:

  • ICT 数字测试原理 21 - -VCL中的板级预处理
  • 学校要求做网站做网站要源代码
  • 项目缺乏成功衡量标准会导致什么问题
  • 2025年的12大技术栈
  • 越南国家建设部网站企业站手机网站
  • Qt6.7.2下,qml中Window组件全屏加载WebEngineView实现圆角
  • Struts2_S2-045漏洞复现:原理详解+环境搭建+渗透实践(CVE-2017-5638)
  • 【慕伏白】Android Studio 无线调试配置
  • 厦门方易网站制作有限公司做网站对象存储
  • 【Docker】零基础上手:原理+Ubuntu/Windows GUI 安装 + 镜像源 / 目录优化
  • 网站的引导页怎么做的手机虚拟空间
  • 大连网站开发公司力推选仟亿科技有源码如何搭建网站
  • 【Java虚拟机(JVM)全面解析】从原理到面试实战、JVM故障处理、类加载、内存区域、垃圾回收
  • 高并发面试
  • 模板网站 建设 方法西安网站建设中心
  • 《早期经验:语言智能体学习的中间道路》Agent Learning via Early Experience论文深度解读
  • QT6中Commd Link Button,Dialog Button Box,Tool Button 功能与应用
  • asp做网站安全性wordpress 文章 接口
  • 关系型数据库RDBMS与非关系型数据库NoSQL区别
  • 网站建设发布wordpress主题带会员中心
  • 单元测试 vs Main方法调试:何时使用哪种方式?
  • 03--CSS基础(2)
  • Wireshark笔记-从抓包的角度分析几种客户端不能正常获取IP地址的场景
  • 企业 网站 推广wordpress文章状态
  • typescript中infer常见用法
  • 科技赋能塞上农业:宁夏从黄土地到绿硅谷的蝶变
  • 第13讲:深入理解指针(3)——数组与指针的“深度绑定”
  • 基于MATLAB的匈牙利算法实现任务分配
  • Type-C 接口充电兼容设计(针对 5V1A 需求)
  • Anaconda 学习手册记录