关于TCP
本文参考JavaGuide:有了 HTTP 协议,为什么还要有 RPC ?
总结
问题:TCP 是流式协议,没有消息边界
现象:会出现"粘包"(多个消息粘在一起)和"拆包"(一个消息被拆开)
解决方案:消息头 + 消息体
- 消息头:固定格式,包含消息体长度等信息
- 消息体:实际要传输的数据
工作流程:
发送方:计算长度 → 添加头部 → 发送
接收方:读取头部 → 解析长度 → 读取对应长度的数据
更细节知识参考:为什么要组装发送的数据
基于上方文章补充:当接收端收到的消息长度小于 Content-Length 时,说明还有些消息没收到。那接收端会一直等,直到拿够了消息或超时。
优点:
- 明确的消息边界
- 可以传输任意长度的数据
- 支持复杂协议扩展
- 纯裸 TCP 是能收发数据,但它是个无边界的数据流,上层需要定义消息格式用于界定 消息边界。于是就有了各种协议,HTTP 和各类 RPC 协议就是在 TCP 之上定义的应用层协议。这就是为什么几乎所有基于 TCP 的协议(HTTP、WebSocket、Redis 协议等)都需要定义自己的消息格式来界定消息边界的原因。
- TCP 是传输层的协议 ,而基于 TCP 造出来的 HTTP 和各类 RPC 协议,它们都只是定义了不同消息格式的 应用层协议 而已。
TCP的特点
TCP的三个特点:面向连接、可靠、基于字节流。
基于字节流
字节流可以理解为一个双向的通道里流淌的二进制数据,也就是 01 串 。纯裸 TCP 收发的这些 01 串之间是 没有任何边界 的,你根本不知道到哪个地方才算一条完整消息。
这便出现了所谓的粘包问题。
由于纯裸 TCP 是不能直接拿来用的,所以在这个基础上加入一些 自定义的规则 ,用于区分消息边界 。即消息头和消息体。
消息头和消息体
于是我们会把每条要发送的数据都包装一下,比如加入消息头 ,消息头里写清楚一个完整的包长度是多少,根据这个长度可以继续接收数据,截取出来后它们就是我们真正要传输的消息体 。
而这里头提到的 消息头 ,还可以放各种东西,比如消息体是否被压缩过和消息体格式之类的,只要上下游都约定好了,互相都认就可以了,这就是所谓的 协议。
每个使用 TCP 的项目都可能会定义一套类似这样的协议解析标准,他们可能 有区别,但原理都类似。
于是基于 TCP,就衍生了非常多的协议,比如 HTTP 和 RPC。
TCP传输的内容
基于 TCP 传输的消息,说到底,无非都是 消息头 Header 和消息体 Body。
Header 是用于标记一些特殊信息,其中最重要的是 消息体长度(Content-Length)。
Body 则是放我们真正需要传输的内容,而这些内容只能是二进制 01 串,毕竟计算机只认识这玩意。所以 TCP 传字符串和数字都问题不大,因为字符串可以转成编码再变成 01 串,而数字本身也能直接转为二进制。但结构体呢,我们得想个办法将它也转为二进制 01 串,这样的方案现在也有很多现成的,比如 JSON,Protocol Buffers (Protobuf) 。
这个将结构体转为二进制数组的过程就叫 序列化 ,反过来将二进制数组复原成结构体的过程叫 反序列化。

对于主流的 HTTP1.1,虽然它现在叫超文本协议,支持音频视频,但 HTTP 设计 初是用于做网页文本展示的,所以它传的内容以字符串为主。Header 和 Body 都是如此。在 Body 这块,它使用 JSON 来 序列化 结构体数据。
截图如下。

小疑问:
1、为什么Post请求有Content-Length,而Get请求没有?
- GET 请求通常用于获取数据
- 参数通过 URL 查询字符串传递(
?id=123) - 没有请求体(Request Body)
- 既然没有请求体,自然不需要
Content-Length
2、什么时候需要Content-Length?
场景1:有请求体的请求
POST /api/users HTTP/1.1
Content-Type: application/json
Content-Length: 56 # ← 必须的
{"username": "test", "password": "123456", "email": "test@example.com"}场景2:分块传输(Transfer-Encoding: chunked)
POST /api/upload HTTP/1.1
Transfer-Encoding: chunked # ← 分块传输
Content-Type: multipart/form-data
7\r\n
Mozilla\r\n
9\r\n
Developer\r\n
7\r\n
Network\r\n
0\r\n
\r\n
# 分块传输时,不需要 Content-Length场景3:空的请求体也需要
POST /api/ping HTTP/1.1
Content-Length: 0 # ← 明确表示请求体长度为0
# 空行下面是请求体(没有内容)