一、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 |