一、TCP 粘包问题(续上一节)
1. 粘包本质
核心原因 | 具体表现 |
---|---|
TCP 是字节流协议,无天然消息边界 | 发送端:多个小消息被合并成一个 TCP 段(Nagle 算法优化) |
内核缓冲区(发送 / 接收)暂存数据 | 接收端:多个 TCP 段数据被合并,一次性交给应用层 |
2. 应用层解决思路
解决方案 | 核心逻辑 | 适用场景 | 优缺点 |
---|---|---|---|
特殊字符边界 | 每个消息末尾加固定标记(如\r\n ),接收端按标记拆分 |
文本类数据(如 HTTP 协议) | 优点:实现简单;缺点:消息含标记时易误拆分 |
定长消息 | 约定固定消息长度(如 100 字节),不足补空,接收端按固定长度读取 | 数据长度固定场景(如传感器数据) | 优点:逻辑简单;缺点:长度不固定时浪费带宽 |
自定义结构体协议 | 设计 “消息头 + 消息体”,头中含消息体长度(如type 字段标识长度 / 类型) |
复杂数据(文件、二进制数据) | 优点:灵活无歧义;缺点:需两端协议兼容 |
二、TCP 编程(C/S 模型)
1. 核心 API 对比
API 类型 | 函数原型 | 关键差异 | 适用场景 |
---|---|---|---|
标准 IO 函数 | read(int fd, void *buf, size_t len) write(int fd, const void *buf, size_t len) |
无额外标志,仅支持阻塞 | 通用 IO 操作(含 socket) |
Socket 专用函数 | recv(int sockfd, void *buf, size_t len, int flags) send(int sockfd, const void *buf, size_t len, int flags) |
支持MSG_DONTWAIT (非阻塞) |
需控制读写模式的 socket 场景 |
2. 客户端与服务器流程对比
角色 | 核心步骤 | 关键说明 |
---|---|---|
TCP 客户端 | 1. socket() :创建 TCP socket2. (可选) bind() :绑定客户端地址(通常系统自动分配)3. connect() :连接服务器(必须成功后通信)4. send()/recv() :数据交互(需处理粘包)5. close() :关闭 socket |
依赖connect() 建立连接,无连接则无法通信 |
TCP 服务器 | 1. socket() :创建 TCP socket2. bind() :绑定固定端口(客户端需知道该端口)3. listen() :开启监听(设置监听队列长度)4. accept() :接收客户端连接(返回新通信 socket)5. send()/recv() :通过新 socket 交互6. close() :关闭通信 socket 和监听 socket |
listenfd 仅用于监听,connfd 用于实际通信 |
3. 常见场景实现
场景 | 核心技术 | 注意事项 |
---|---|---|
点对点聊天 | 多线程 / 多进程: - 线程 1:读键盘→ send() - 线程 2: recv() →打印 |
线程需pthread_join() /pthread_detach() 回收资源 |
文件传输 | 自定义结构体协议: - 步骤 1:发文件名( type=-1 )- 步骤 2:循环发文件数据( type=数据长度 )- 步骤 3:发结束标志( type=0 ) |
需处理文件读写错误(如read() 返回 0 表示文件结束) |
三、UDP 编程核心(C/S 模型)
1. UDP 协议核心特点
特点 | 具体说明 | 影响 |
---|---|---|
无连接 | 无需connect() ,直接通过sendto() 指定目标地址 |
通信灵活,但需每次发送时携带目标地址 |
不可靠 | 不保证数据送达、不保证顺序、不重传 | 适合实时场景(如视频),不适合需可靠传输的场景(如文件) |
数据报 | 每个sendto() 对应一个完整消息,recvfrom() 一次读一个 |
无粘包问题,无需额外处理消息边界 |
2. 核心 API(sendto/recvfrom)
函数 | 原型 | 关键参数说明 |
---|---|---|
sendto |
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) |
dest_addr :目标地址(客户端→服务器需填服务器地址) |
recvfrom |
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) |
src_addr :保存发送方地址;addrlen 需先初始化(值结果参数) |
3. 客户端与服务器流程对比
角色 | 核心步骤 | 关键说明 |
---|---|---|
UDP 客户端 | 1. socket() :创建 UDP socket2. (可选) bind() :绑定客户端地址3. sendto() :指定服务器地址发送数据4. recvfrom() :接收服务器回发5. close() :关闭 socket |
无需连接,直接发送;需提前知道服务器 IP 和端口 |
UDP 服务器 | 1. socket() :创建 UDP socket2. bind() :绑定固定端口(必须,客户端需定位)3. recvfrom() :接收客户端数据(获取客户端地址)4. sendto() :通过客户端地址回发数据5. close() :关闭 socket |
依赖bind() 固定端口,否则客户端无法找到服务器 |
四、TCP 与 UDP 核心差异对比
对比维度 | TCP | UDP |
---|---|---|
连接方式 | 面向连接(需connect ) |
无连接(直接sendto ) |
数据格式 | 字节流(易粘包,需应用层处理边界) | 数据报(无粘包,天然消息边界) |
可靠性 | 可靠(确认、重传、有序) | 不可靠(无确认、无重传) |
效率 | 低(连接建立、确认机制耗时) | 高(无额外开销,实时性好) |
编程重点 | 处理粘包、连接管理、资源回收 | 处理地址(sendto /recvfrom )、丢包应对 |
典型应用 | 文件传输、聊天、HTTP/HTTPS |