C++(3)TCP
·
三、TCP/IP 网络编程
1. OSI 七层模型与 TCP/IP 四层模型对应关系
表格
| OSI 七层模型 | TCP/IP 四层模型 | 核心功能 | 典型协议 |
|---|---|---|---|
| 应用层 | 应用层 | 文件传输、邮件、终端交互 | HTTP、FTP、SMTP、DNS、Telnet |
| 表示层 | 应用层 | 数据格式化、加密、解码 | 无独立协议,融入应用层 |
| 会话层 | 应用层 | 建立 / 断开会话、会话管理 | 无独立协议,融入应用层 |
| 传输层 | 传输层 | 端到端数据传输、流量控制 | TCP、UDP |
| 网络层 | 网络层 | 路由选择、IP 寻址、数据包转发 | IP、ICMP、RIP、OSPF |
| 数据链路层 | 链路层 | 帧传输、MAC 寻址、错误检测 | ARP、RARP、PPP、SLIP |
| 物理层 | 链路层 | 二进制数据的物理介质传输 | 以太网、串口、光纤 |
2. TCP Socket 通信流程
- 服务器:
socket()→bind()→listen()→accept()→recv()/send()→close() - 客户端:
socket()→connect()→send()/recv()→close()
3. TCP 三次握手(建立连接)

流程
- 客户端(CLOSED)→ 服务器(LISTEN):发送
SYN=1,seq=a,客户端进入 SYN_SENT 状态; - 服务器 → 客户端:发送
SYN=1,ACK=1,seq=b,ack=a+1,服务器进入 SYN_RCVD 状态; - 客户端 → 服务器:发送
ACK=1,seq=a+1,ack=b+1,双方进入 ESTABLISHED 状态,开始数据传输。
listen () 的 backlog 参数
listen(fd, backlog)的第二个参数,不同阶段含义不同:
- 早期:半连接队列(SYN_RCVD)的长度,防止 SYN 泛洪;
- 中期:半连接队列 + 全连接队列(ESTABLISHED 未被 accept)的总长度;
- 现在:仅指全连接队列的长度(主流操作系统)。

4. TCP 的可靠性保障机制

TCP 是面向连接、可靠的字节流协议,通过以下机制保证可靠性:
- 三次握手建立连接:确保双方收发能力正常;
- 序列号与确认应答(ACK):为每个字节分配序列号,接收方收到数据后返回 ACK,发送方仅收到 ACK 才认为数据成功传输;
- 超时重传:发送方启动定时器,超时未收到 ACK 则重传数据,超时时间根据网络状况动态调整;
- 滑动窗口:实现流量控制(控制发送速率,避免接收方缓冲区溢出)和高效传输(允许未收到 ACK 时连续发送多个数据包);
- 拥塞控制:避免网络拥塞,分为慢启动、拥塞避免、快速重传、快速恢复四个阶段;
- 数据校验:TCP 头部包含校验和,接收方验证数据完整性,校验失败则要求重传。
注意:TCP 仍可能数据丢失的原因
- TCP 仅保证数据到达对端操作系统内核缓冲区,不保证被应用层读取;
- 网络长期拥塞时,重传数据包反复丢失,超过最大重传次数后,TCP 连接被强制关闭,未确认数据被丢弃。
5. TCP 最大连接数限制

TCP 连接由四元组(源 IP、源端口、目标 IP、目标端口) 唯一标识,限制因素:
- 端口号:共 65536 个,0-1023 为知名端口,普通应用无法使用,实际可用端口数有限;
- 文件描述符:Linux 对系统 / 用户 / 进程级的文件描述符数量有限制,每个 TCP 连接对应一个文件描述符;
- 线程:若每个连接对应一个线程,连接数过多会导致线程上下文切换开销过大(C10K 问题),可通过 IO 多路复用解决;
- 内存:每个 TCP 连接的缓冲区、控制结构均占用内存,内存不足会限制连接数。
6. TCP vs UDP

| 特性 | TCP | UDP |
|---|---|---|
| 连接性 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠(确认、重传、校验) | 不可靠(无确认、无重传) |
| 数据传输 | 面向字节流(无消息边界) | 面向报文(有消息边界,一个 UDP 包对应一个完整消息) |
| 传输方式 | 仅单播 | 单播、多播、广播 |
| 流量控制 | 支持(滑动窗口) | 不支持 |
| 拥塞控制 | 支持 | 不支持 |
| 头部开销 | 20-60 字节 | 8 字节(固定) |
| 适用场景 | 要求可靠的场景(HTTP、FTP、数据库) | 要求高效、低延迟的场景(直播、游戏、DNS) |
7. TCP 粘包问题
在 TCP 面向字节流的传输中,粘包是指多个数据包被粘连在一起,形成一个连续的字节流,导 致接收方难以准确地分辨出每个数据包的边界,进而无法正确地解析和处理数据。
举个实际的例子来说明。
发送方准备发送 「Hi.」和「I am Xiaolin」这两个消息。
在发送端,当我们调用 send 函数完成数据“发送”以后,数据并没有被真正从网络上发送出去, 只是从应用程序拷贝到了操作系统内核协议栈中。
至于什么时候真正被发送,取决于发送窗口、拥塞窗口以及当前发送缓冲区的大小等条件。也就 是说,我们不能认为每次 send 调用发送的数据,都会作为一个整体完整地消息被发送出去。
至于什么时候真正被发送,取决于发送窗口、拥塞窗口以及当前发送缓冲区的大小等条件。也就 是说,我们不能认为每次 send 调用发送的数据,都会作为一个整体完整地消息被发送出去。

原因
TCP 是面向字节流的协议,发送方的多个send()调用可能被合并为一个 TCP 报文发送,接收方的多个recv()调用可能读取到多个消息的拼接数据,导致接收方无法区分消息边界。
解决方案
核心:明确消息边界,三种常用方式:
- 固定长度分包:每个消息固定长度,不足补 0;
- 消息头 + 消息体:消息头包含消息体长度,接收方先读长度,再读对应字节数的消息体;
- 特殊字符作为分隔符:如
\r\n,接收方按分隔符拆分消息(注意:需处理分隔符出现在消息体中的情况)。
8. TCP 四次挥手(关闭连接)

流程
- 主动关闭方 → 被动关闭方:发送
FIN=1,seq=x,主动方进入 FIN_WAIT1 状态; - 被动关闭方 → 主动关闭方:发送
ACK=1,ack=x+1,被动方进入 CLOSE_WAIT 状态,主动方进入 FIN_WAIT2 状态; - 被动关闭方 → 主动关闭方:发送
FIN=1,seq=y,被动方进入 LAST_ACK 状态; - 主动关闭方 → 被动关闭方:发送
ACK=1,ack=y+1,主动方进入 TIME_WAIT 状态,被动方收到 ACK 后进入 CLOSED 状态; - 主动方在 TIME_WAIT 状态等待2MSL后,进入 CLOSED 状态。
关键状态说明
- CLOSE_WAIT:被动关闭方收到 FIN 后,未及时发送自己的 FIN,若大量出现,说明程序未调用
close()关闭 socket(如死循环、忘记释放); - TIME_WAIT:主动关闭方最终的状态,等待 2MSL 的原因:
- 确保最后一个 ACK 被对方收到,避免对方重传 FIN;
- 让网络中迟来的 TCP 报文段自然过期,避免影响新连接。
握手三次、挥手四次的原因
- 三次握手:服务器的
SYN(建立连接)和ACK(确认客户端 SYN)可合并为一个报文发送; - 四次挥手:服务器收到 FIN 后,可能还有未发送完的数据,需先发送
ACK确认关闭,待数据发送完成后再发送FIN,因此ACK和FIN无法合并,需分两次发送。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)