Linux c++ 2.2.1 tcp的实现
客户端和服务器之间通过tcp协议来通信。需要使用很多POSIX api
客户端:
socket() bind() conect send recv close
服务器:
socket bind listen accept recv send close
使用到的的io多路复用是epoll。对应的接口是epoll create、epoll ctl、epoll wait。fcntl
一、建立连接
使用到的有 socket bind listen。
1.socket:
内部由两部分组成。分配int型的fd(套接字描述符)。tcp控制块。
它的原型定义在 <sys/socket.h> 头文件中
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
(1) domain —— 协议族/地址族
指定套接字使用的通信域(地址格式)。常见的取值有:
| 常量 | 含义 |
|---|---|
AF_INET |
IPv4 网络通信,地址结构为 struct sockaddr_in |
AF_INET6 |
IPv6 网络通信,地址结构为 struct sockaddr_in6 |
总之就是设置协议族。
(2) type —— 套接字类型
指定通信的语义/服务类型。常见取值:
| 常量 | 含义 |
|---|---|
SOCK_STREAM |
流式套接字,提供可靠的、面向连接的字节流服务。底层通常使用 TCP。保证数据顺序、无丢失、无重复。 |
SOCK_DGRAM |
数据报套接字,提供无连接的、不可靠的消息服务。底层通常使用 UDP。数据可能乱序、丢失、重复,但保留了消息边界。 |
就是设置用哪个协议进行通信。代码中使用的是tcp。
(3)protocol —— 协议号
指定具体的传输协议,大多数情况下设为 0,表示让系统自动选择合适的默认协议
设置完之后,会返回一个int型的fd。也就是套接字描述符。socket() 本身只是创建一个通信端点,此时它还未绑定地址、未连接
总结
| 维度 | 内容 |
|---|---|
| 主要作用 | 创建一个套接字对象,返回文件描述符,用来后续接收客户端的链接。 |
| 三个参数 | domain(协议族)、type(服务类型)、protocol(具体协议,多数为0) |
| 核心产出 | 一个通信端点,可以是 TCP、UDP、原始包、本地通信等不同形态。 |
| 后续配合 | bind、listen、accept、connect、send/recv 等系统调用。 |
2.bind:
将套接字与一个本地地址(IP + 端口或文件路径)绑定,使得系统知道该套接字从哪个地址接收数据。将socketfd绑定在本地上的端口。记事把服务员绑定在饭店门口
函数原型:
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
把sockfd绑定在 本地addr上。
3.listen:
就是让sockfd正式上岗。
函数原型:
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数:
-
sockfd:由socket()返回,并已经通过bind()绑定了本地地址的套接字描述符。指定让哪一个sockfd开始工作 -
backlog:定义已完成连接队列的最大长度。
返回值:
-
成功:返回
0。 -
失败:返回
-1,并设置errno。
1. sockfd —— 待转换的套接字
-
这个套接字必须是
SOCK_STREAM或SOCK_SEQPACKET类型。 -
在调用
listen()之前,通常已经调用了bind()。
2. backlog —— 连接队列大小
backlog 定义了套接字等待连接队列的最大长度。对于 TCP 而言,系统通常维护两个队列:
-
未完成连接队列(SYN_RCVD 状态)半连接状态:客户端发送 SYN,服务端收到后等待客户端 ACK,这个队列长度由系统全局限制决定,
backlog对它影响较小。 -
已完成连接队列(ESTABLISHED 状态):三次握手已完成,等待
accept()取走的连接。backlog主要限制这个队列的大小。此时内核里面已经建立好连接了。accpet只是把链接拿走而已。并不是accept接收了连接。
当已完成连接队列满时,新的连接请求的处理行为依赖于系统:Linux 会忽略新的 SYN,客户端会重试;某些系统可能直接拒绝连接(发送 RST)。
listen() 与 accept() 的关系
-
listen()是准备工作:告诉内核开始接受连接,但并不阻塞。 -
accept()是实际从已完成队列中取出一个连接(若队列为空则阻塞,除非设置为非阻塞)。
可以把 listen() 理解为“启动电话交换机的接线员”,accept() 是“接听一个来电”。listen是真正干活的。把链接处理完,accpet只是拿走而已。
4.tcp三次握手,调用那些函数?
一、客户端(主动打开连接)
客户端调用 connect() 函数,该函数会:
-
立即向服务端发送 SYN 报文(第一次握手)。
-
等待服务端回复 SYN+ACK(第二次握手)。
-
收到后,内核自动回复 ACK(第三次握手)。
-
当三次握手完成,
connect()返回(成功返回 0,失败返回 -1)。
二、服务端(被动打开连接)
服务端在握手前必须先准备好监听套接字,调用顺序为:
-
socket()– 创建套接字。 -
bind()– 绑定地址和端口。 -
listen()– 将套接字转为被动监听状态,并分配连接队列。
上述三个函数完成之后,内核开始能够处理发往该端口的 SYN 请求。
当客户端 SYN 到达时,内核自动进行以下操作(不涉及用户函数调用)这是tcp自动完成的,不涉及到各种函数:
-
回复 SYN+ACK(第二次握手)。
-
将连接放入“未完成连接队列”(SYN_RCVD)。
当收到客户端的 ACK(第三次握手)后,连接转入“已完成连接队列”(ESTABLISHED),并等待应用程序取走。
服务端通过调用 accept() 函数从已完成队列中取出一个已经完成三次握手的连接:
-
如果队列为空,
accept()会阻塞(同步模式)或返回-1(非阻塞模式)。 -
accept()返回一个新的已连接套接字描述符,用于与该客户端通信。
图标流程如下:
客户端 服务端
------ ------
socket() socket()
bind()
listen() --> 进入监听状态
connect() -------- SYN (1) ---------> (内核接收并处理)
(内核发SYN) <-------- SYN+ACK (2) ------- 内核自动发SYN+ACK
(内核收ACK) -------- ACK (3) ---------> 连接进入已完成队列
connect() 返回 accept() <-- 从队列取出连接,返回
( accept返回后即可通信)
总结
| 角色 | 核心函数 | 作用 |
|---|---|---|
| 客户端 | connect() |
触发三次握手的开始(发送 SYN 并等待完成)。 |
| 服务端 | listen() |
使内核准备好接收连接请求(不参与握手报文,是前置条件)。 |
| 服务端 | accept() |
取出已经完成三次握手的连接(被动等待,不发送握手包)。 |
三次握手中实际发送报文的动作完全由内核自动完成,应用程序只需调用上述函数来同步或等待握手的完成。
tcp生命周期从什么时候开始?
TCP 连接的生命周期从第一次握手的 SYN 发出开始
第三次握手后,如何从半连接队列查找匹配的节点?
在 TCP 三次握手中,第三次握手是客户端发送的 ACK 报文。服务端收到这个 ACK 后,根据报文的 五元组(源 IP、源端口、目的 IP、目的端口、协议)从 半连接队列(syn queue) 中查找对应的半连接节点(request_sock),然后将其移入全连接队列(accept queue),等待应用程序调用 accept() 取出。
如何避免syn泛红?
启用SYN Cookies。不存储半连接状态,而是将连接信息编码成Cookie随SYN/ACK发回,只有当客户端回复正确的ACK且通过Cookie验证后,才为其分配资源。此机制确保了即便半连接队列满了,仍能为合法请求提供服务。也就是半连接队列里都是真正的请求连接。
减轻连接压力。限制一个监听(listen())端口上已完成握手但尚未被应用程序accept()的连接数。适当增加此值有助于应对突发的高并发请求。也就是限制全连接队列的大小。
二、tcp数据传输
传输过程由内核自动完成。主要使用的有拥塞控制,超时重传,延迟确认,滑动窗口,慢启动。
三、tcp断开连接
断开连接时,tcp有好多种状态。
| 状态 | 含义 |
|---|---|
| ESTABLISHED | 正常数据传输状态 |
| FIN_WAIT_1 | 主动关闭方,发完 FIN,等待 ACK |
| FIN_WAIT_2 | 主动关闭方,收到 FIN 的 ACK,等待对方 FIN |
| CLOSE_WAIT | 被动关闭方,收到对方 FIN,回复 ACK |
| LAST_ACK | 被动关闭方,自己发 FIN,等待对方最后 ACK |
| TIME_WAIT | 主动关闭方,收到对方 FIN,回复最后 ACK,等待 2MSL |
| CLOSED | 完全断开 |
场景一:正常单方关闭(最常用)如果a发起断开,b接收:
-
第一次挥手A 业务结束,调用
close(),向 B 发送FIN报文,A 进入FIN_WAIT_1。含义:A 不会再发数据给 B。 -
第二次挥手B 收到
FIN,立即回复ACK确认,B 进入CLOSE_WAIT。含义:B 知道 A 不发数据了,但 B 还可以继续给 A 发剩余数据(半关闭状态)。A 收到这个 ACK → 进入FIN_WAIT_2。 -
第三次挥手B 所有数据发送完毕,业务调用
close(),向 A 发FIN,B 进入LAST_ACK。含义:B 也不会再发数据给 A。 -
第四次挥手A 收到 B 的
FIN,回复ACK,A 进入TIME_WAIT;B 收到最后这个 ACK → 直接进入CLOSED。A 等待 2MSL 确保报文全部消散,再进入CLOSED。
核心:两次单向关闭 + 两次 ACK 确认 = 四次挥手
场景二:双方同时调用 close ()(同时断开)
逐步拆解状态变化
-
初始A、B:
ESTABLISHED -
双方同时发 FIN
- A 发 FIN → A:
FIN_WAIT_1 - B 发 FIN → B:
FIN_WAIT_1
- 双方各自收到对方的 FIN
- A(FIN_WAIT_1)收到 B 的 FIN:立即应答 ACK,A 从
FIN_WAIT_1→CLOSING - B(FIN_WAIT_1)收到 A 的 FIN:立即应答 ACK,B 从
FIN_WAIT_1→CLOSING
- 双方收到自己那条 FIN 的 ACK
- A 在
CLOSING收到 B 发来的、确认 A-FIN 的 ACK→ A:CLOSING→TIME_WAIT - B 在
CLOSING收到 A 发来的、确认 B-FIN 的 ACK→ B:CLOSING→TIME_WAIT
- 收尾两端都维持
TIME_WAIT2MSL,确保网络中残留报文消失、最后 ACK 可靠送达,之后双双进入CLOSED。
4. 核心特点
- 全程没有:
FIN_WAIT_2、CLOSE_WAIT、LAST_ACK - 报文依旧是 4 个:
FIN、FIN、ACK、ACK,本质还是四次挥手 - CLOSING 状态专属场景:只有「双方同时主动断连」才会出现
- 两端都是主动关闭方,都需要走
TIME_WAIT
场景三:A 先发 FIN,A 处于 FIN_WAIT1,对方 FIN 先到、对方 ACK 后到
步骤 1:A 主动发 FIN
- A 调用 close,关闭上行写入,发 FIN
- A 状态:
ESTABLISHED➝FIN_WAIT_1 - 此时 A 的预期:等待 B 回复「ACK」确认这个 FIN
步骤 2:B 的 FIN 率先抵达 A(异常乱序点)
此时 A 还停留在 FIN_WAIT_1,还没收到 B 的 ACK,反而收到了 B 发来的 FIN。
TCP 标准行为:
当主机处于 FIN_WAIT_1 状态,收到对端 FIN:
- 立即发送 ACK 确认对方的 FIN
- 状态直接切换为 CLOSING
步骤 3:A 进入 CLOSING 状态
CLOSING含义:我发过 FIN、对方也发了 FIN,双方都要关闭,只差收到我自己 FIN 的 ACK- 此时连接属于「双向同时关闭中」的中间态
步骤 4:迟到的 ACK 终于到达 A
这个 ACK 是:B 用来确认 A 最开始发的那条 FIN 的应答。A 现在处于 CLOSING,收到这个迟到 ACK:
- 无冲突、不丢弃、不报错
- 满足了「收到自身 FIN 的 ACK」条件
- A 状态:
CLOSING➝TIME_WAIT
步骤 5:最终收尾
- A:
TIME_WAIT保持 2MSL,防止最后报文丢失、旧报文残留 - B:收到 A 的两次 ACK 后,正常从
LAST_ACK进入CLOSED
4. 该场景核心规则
FIN_WAIT_1收到 对端 FIN → 切CLOSINGCLOSING收到 自己 FIN 的 ACK → 切TIME_WAIT- 报文乱序(FIN 比 ACK 先到)是 TCP 设计内兼容的,不会崩连接、不会死锁
- 区别:
- 正常单关闭:
FIN_WAIT1 → FIN_WAIT2 → TIME_WAIT - 本场景:
FIN_WAIT1 → CLOSING → TIME_WAIT
- 正常单关闭:
总之就是:
A 在 FIN_WAIT1 没等到 ACK、先等到对方 FIN,立刻回 ACK 切 CLOSING;后续迟到的 ACK 到达,再从 CLOSING 转入 TIME_WAIT,正常关闭。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)