三、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 三次握手(建立连接)

流程
  1. 客户端(CLOSED)→ 服务器(LISTEN):发送SYN=1,seq=a,客户端进入 SYN_SENT 状态;
  2. 服务器 → 客户端:发送SYN=1,ACK=1,seq=b,ack=a+1,服务器进入 SYN_RCVD 状态;
  3. 客户端 → 服务器:发送ACK=1,seq=a+1,ack=b+1,双方进入 ESTABLISHED 状态,开始数据传输。
listen () 的 backlog 参数

listen(fd, backlog)的第二个参数,不同阶段含义不同:

  1. 早期:半连接队列(SYN_RCVD)的长度,防止 SYN 泛洪;
  2. 中期:半连接队列 + 全连接队列(ESTABLISHED 未被 accept)的总长度;
  3. 现在:仅指全连接队列的长度(主流操作系统)。

4. TCP 的可靠性保障机制

TCP 是面向连接、可靠的字节流协议,通过以下机制保证可靠性:

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

5. TCP 最大连接数限制

TCP 连接由四元组(源 IP、源端口、目标 IP、目标端口) 唯一标识,限制因素:

  1. 端口号:共 65536 个,0-1023 为知名端口,普通应用无法使用,实际可用端口数有限;
  2. 文件描述符:Linux 对系统 / 用户 / 进程级的文件描述符数量有限制,每个 TCP 连接对应一个文件描述符;
  3. 线程:若每个连接对应一个线程,连接数过多会导致线程上下文切换开销过大(C10K 问题),可通过 IO 多路复用解决;
  4. 内存:每个 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()调用可能读取到多个消息的拼接数据,导致接收方无法区分消息边界

解决方案

核心:明确消息边界,三种常用方式:

  1. 固定长度分包:每个消息固定长度,不足补 0;
  2. 消息头 + 消息体:消息头包含消息体长度,接收方先读长度,再读对应字节数的消息体;
  3. 特殊字符作为分隔符:如\r\n,接收方按分隔符拆分消息(注意:需处理分隔符出现在消息体中的情况)。

8. TCP 四次挥手(关闭连接)

流程
  1. 主动关闭方 → 被动关闭方:发送FIN=1,seq=x,主动方进入 FIN_WAIT1 状态;
  2. 被动关闭方 → 主动关闭方:发送ACK=1,ack=x+1,被动方进入 CLOSE_WAIT 状态,主动方进入 FIN_WAIT2 状态;
  3. 被动关闭方 → 主动关闭方:发送FIN=1,seq=y,被动方进入 LAST_ACK 状态;
  4. 主动关闭方 → 被动关闭方:发送ACK=1,ack=y+1,主动方进入 TIME_WAIT 状态,被动方收到 ACK 后进入 CLOSED 状态;
  5. 主动方在 TIME_WAIT 状态等待2MSL后,进入 CLOSED 状态。
关键状态说明
  1. CLOSE_WAIT:被动关闭方收到 FIN 后,未及时发送自己的 FIN,若大量出现,说明程序未调用close()关闭 socket(如死循环、忘记释放);
  2. TIME_WAIT:主动关闭方最终的状态,等待 2MSL 的原因:
    • 确保最后一个 ACK 被对方收到,避免对方重传 FIN;
    • 让网络中迟来的 TCP 报文段自然过期,避免影响新连接。
握手三次、挥手四次的原因
  • 三次握手:服务器的SYN(建立连接)和ACK(确认客户端 SYN)可合并为一个报文发送;
  • 四次挥手:服务器收到 FIN 后,可能还有未发送完的数据,需先发送ACK确认关闭,待数据发送完成后再发送FIN,因此ACKFIN无法合并,需分两次发送。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐