#Linux #TCP 粘包 #HTTP
前言
TCP 是面向字节流的可靠传输协议,字节流没有数据包边界,由此引出开发高频踩坑点:TCP 粘包。而 HTTP 是构建在 TCP 之上最常用的应用层协议,裸 Socket 手写 HTTP 客户端必须理解报文格式。
一、TCP 粘包
1.1 什么是粘包
TCP 是面向字节流协议 ,不存在数据包边界。发送的数据会存放在内核 Socket 缓冲区。 发送端发送速度快、接收端处理慢,多个独立数据包在内核缓冲区缓存合并;应用层调用recv()读取时,一次读到多份原始数据包,数据包粘连在一起,无法区分原始报文边界,该现象就是TCP 粘包。
⚠️ UDP 不会发生粘包,UDP 面向数据报,一次
recvfrom读取一个完整数据报。
1.2 粘包产生两种场景
- 发送方造成:内核 Nagle 算法将多个小数据包合并,一次性发送出去。
- 接收方造成:接收缓冲区积攒多份发送过来的数据,应用程序一次 recv 读出全部。
粘包不是 bug,不是 TCP 协议故障 ,是字节流特性带来的应用层问题,必须在应用层设计协议解决。
1.3 TCP 粘包三种工程解决方案
方案 1:固定长度收发
每次收发固定大小的数据,例如直接收发结构体。发送send(sockfd, &data, sizeof(struct data),0);,接收固定sizeof(struct data)字节。
缺点:数据不足时需要填充占位;跨平台注意大小端、结构体对齐问题。
方案 2:特殊分隔符分割报文
数据包末尾添加专属分隔符(如\n、0x7E)。接收方循环读数据,在缓冲区查找分隔符,切割拆分出完整报文。
缺点:业务数据内部不能出现分隔符字符,否则解析错乱。
方案 3:自定义帧协议(工业项目最常用)
自定义帧格式:帧头 + 数据长度 + 消息体 + 校验 + 帧尾。 接收逻辑:
- 先读取帧头;
- 读取消息长度字段;
- 根据长度读取对应字节数的消息体;
- 校验校验和,完成一帧解析。
核心思路:提前告知接收方本次数据包到底多大,不受 TCP 字节流粘连影响。
二、HTTP 协议
HTTP 属于应用层协议,底层基于 TCP。
- HTTP 默认端口:80(明文)
- HTTPS 默认端口:443(TCP+TLS 加密,裸 socket 不能直接发送明文 HTTP)
2.1 HTTP 工作流程
- 客户端与服务器建立 TCP 三次握手连接
- 客户端向服务端发送HTTP 请求报文
- 服务端处理请求,返回HTTP 响应报文
- 根据
Connection字段决定断开连接还是保持长连接Connection: close:短连接,请求完成立刻断开 TCPConnection: keep‑alive:长连接,连接复用,一段时间内保持连接可以继续传输数据
2.2 HTTP 请求报文格式
文本格式,由 4 部分组成:请求行、请求头、空行\r\n\r\n、请求体。
GET / HTTP/1.1\r\n
Host: www.baidu.com\r\n
User‑Agent: Mozilla/5.0 xxx\r\n
Accept: */*\r\n
Connection: close\r\n
\r\n
- 请求行:
方法 资源路径 HTTP版本,GET 后面必须有空格,写错服务器返回 400。 - 常用方法:
GET查询资源、POST提交数据、HEAD只获取响应头、PUT修改、DELETE删除。 - 空行
\r\n\r\n代表请求头结束,如果缺少会导致服务器一直等待剩余数据。
2.3 HTTP 响应报文格式
响应行、响应头、空行、响应体 (html/json 等数据)
HTTP/1.1 200 OK\r\n
Content‑Type: text/html;charset=utf‑8\r\n
Server: openresty\r\n
\r\n
<!DOCTYPE html><html>......
2.4 HTTP 状态码
三位数字,分为 5 大类:
- 1xx 信息提示:请求已接收,继续处理
- 2xx 请求成功 :
200 OK正常访问成功 - 3xx 重定向:资源位置跳转
- 4xx 客户端错误
400 Bad Request请求语法错误(GET 缺少空格经常出现)404 Not Found找不到访问资源
- 5xx 服务端错误:服务器内部异常
重要踩坑:访问 HTTPS (443),不能直接裸 TCP socket 发送明文 HTTP 报文;443 端口需要完成 TLS 握手,需要 openssl 库。
小结
- TCP 粘包根源:TCP 是字节流,无报文边界;属于应用层问题,TCP 本身没有错误。三种解决方式:定长、分隔符、带长度自定义帧协议。UDP 无粘包。
- HTTP 基于 TCP;80 明文、443 加密;报文严格遵循
\r\n换行,空行分割头部与数据;状态码定位请求故障;443 端口不能直接裸 TCP 发送 http 明文。