WebSocket 和 Socket 的区别,90% 的开发者都答不完整

后端面试里有一道经典题:"WebSocket 和 Socket 有什么区别?"
这道题的通过率低得反常。不是因为它冷门,恰恰因为它太常见:前端天天 new WebSocket(),后端张口闭口 Socket 编程,两个词朝夕相处,真被问到区别,多数人只能挤出一句"一个是网页用的,一个是底层的"。
更麻烦的是,网上流传着几个似是而非的说法:
- "WebSocket 就是浏览器版的 Socket"
- "Socket 是一种协议"
- "WebSocket 走的是 HTTP"
这三句话,一句都不准确。这篇文章从这几个误解出发,把两者的技术原理、性能差距、选型逻辑讲清楚,再往深处走一步——看看它们背后牵出的浏览器安全模型,以及腾讯 TARS 这类跨平台架构是怎么把两者缝合到一起的。
一、先纠正一个误解:Socket 根本不是协议
先说结论:Socket 是操作系统提供的编程接口,不是协议。
1983 年,加州大学伯克利分校在 4.2BSD 版本的 Unix 里集成了 TCP/IP 协议栈,同时留出一套系统调用,让普通程序也能用上网络——这套接口叫 Berkeley Socket API。从那时起,"Socket"这个词指的就不是某个协议,而是操作系统暴露给程序员、用来操纵协议栈的把手。
它的样子大致是这样的:
int fd = socket(AF_INET, SOCK_STREAM, 0); // 我要一个 TCP Socket
int fd = socket(AF_INET, SOCK_DGRAM, 0); // 我要一个 UDP Socket
第二个参数决定你拿到的是什么:
- SOCK_STREAM → TCP Socket:可靠字节流,建立连接前先三次握手;
- SOCK_DGRAM → UDP Socket:无连接,没有握手,只管发不管确认;
- SOCK_RAW → Raw Socket:绕过传输层直达网络层,能直接读写内核通常不处理的 IP 包。网络抓包工具(tcpdump、Wireshark 的底层)和全新自定义协议的实现,靠的都是它。
放到协议栈上看,三者各居其位:
┌──────────────────────────────┐
│ 应用层 │ WebSocket(消息帧)、HTTP……
├──────────────────────────────┤
│ 传输层 │ TCP / UDP ← Socket 接口在这层供你调用
├──────────────────────────────┤
│ 网络层 │ IP ← Raw Socket 直达这层
├──────────────────────────────┤
│ 链路层 / 物理层 │
└──────────────────────────────┘
这里藏着第二个常见误解:把 Socket 等同于 TCP 或 UDP 中的某一个。都不对。Socket 是那把扳手,TCP 和 UDP 是你用扳手去拧的螺丝。 传入不同参数,同一套接口给你不同的传输特性。
可以用一个比喻收束这一节:Socket 是地基和毛坯房,WebSocket 是在地基上按特定标准装修好的精装房。这个比喻还藏着下一节的引子——WebSocket 这个"精装房"是怎么盖起来的?它的出生方式相当取巧:借 HTTP 的壳。
二、WebSocket:借 HTTP 的壳出生,出生后就不认 HTTP 了
很多人以为"WebSocket 使用 HTTP 协议通信"。不准确。它的生命周期分成界限分明的两个阶段。
握手阶段:借用 HTTP。 客户端先发一个"长得很像普通网页请求"的 HTTP 请求:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
关键在 Upgrade: websocket 这一行——客户端在请求把协议升级。服务器如果支持,返回:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
数据传输阶段:彻底脱离 HTTP。 101 状态码一返回,这条 TCP 连接上跑的就不再是 HTTP,而是 WebSocket 自己的帧格式。HTTP 的使命到此结束——它只是个"启动器"。
为什么要绕这么一道?这是 WebSocket 设计上最精妙的一笔。全世界的防火墙、反向代理、企业网关,几乎都认 HTTP(80/443 端口)。一个新协议想穿过现成的互联网基础设施,最好的办法就是出生时伪装成 HTTP,握手成功后再"变身"。基础设施只需放行这一次升级请求,之后的数据帧对它们来说是普通的 TCP 流量。
这就是 WebSocket 能成为 Web 标准的关键,而无数更高效的自定义协议死在了防火墙面前。
握手之后,WebSocket 的数据以帧为单位传输:
- 每条消息可以是文本或二进制,类型在帧头里标明;
- 帧头只有 2–14 字节,极其轻量;
- 客户端发往服务器的数据强制做掩码处理,防的是中间代理的缓存投毒攻击;
- 协议自带消息边界——你
send("Hello"),对方onmessage收到的就是一条完整的"Hello"。
最后这一点看着不起眼,恰恰是 TCP Socket 最让人头疼的地方。
三、TCP Socket:一根水管,和绕不开的"粘包"
TCP Socket 给你的是一根水管:这头倒进去什么字节,那头就接住什么字节。但水是连成一片的,没有"一杯一杯"的概念。
经典问题立刻出现:
socket.write('Hello');
socket.write('World');
对端一次 data 事件收到的,很可能是 "HelloWorld",也可能是 "Hel" 加上下一轮的 "loworld"。这就是流传已久的"粘包"问题。
严格地说,这个词有点冤枉 TCP——TCP 从没承诺过消息边界,它是字节流协议,"包粘住了"是应用层的一厢情愿。但麻烦是真实的:用 TCP Socket 通信,你必须自己设计应用层协议。常见三种方案:
- 定长消息:每条消息固定 N 字节,不足就填充。实现最简单,也最浪费;
- 特殊分隔符:用
\n或某个魔法字节分隔,文本协议常用,要处理内容转义; - 长度前缀:用固定字节标明消息长度(比如前 4 字节存长度,后面跟 payload)。最通用的方案,绝大多数私有二进制协议和 RPC 框架都走这条路。
协议设计好之后,TCP Socket 就给你完全的控制权:超时、缓冲区大小、TCP_NODELAY(关闭 Nagle 算法——内核默认会把小包攒一波再发,省带宽但增加延迟)、keepalive 参数……全是你的。自由,但每一层都要自己负责。
两边都见过了,把所有区别摆上桌。
四、一张表看清两者的边界
| 维度 | TCP Socket | WebSocket |
|---|---|---|
| 本质 | 传输层编程接口 | 应用层协议 |
| 位置 | 操作系统层 | 跑在 TCP 之上 |
| 握手 | 三次握手,只认 IP + 端口 | 三次握手 + HTTP 协议升级 |
| 数据单位 | 字节流,自己处理边界 | 消息帧,天然有边界 |
| 运行环境 | 任意进程,系统级权限 | 浏览器沙箱 / 任意语言实现 |
| 可选传输 | TCP / UDP / Raw | 只能 TCP |
| 基础设施亲和度 | 无法被通用 HTTP 设施管理 | 轻松穿透防火墙和反向代理 |
表里有几个维度值得展开。
能力边界:沙箱特供 vs 系统工具箱。 WebSocket 在浏览器里是"特供品":它是浏览器内置的实时双向通信 API,被严格限制——只能发文本或二进制消息,碰不到原始 TCP 包,更碰不到 UDP;连接目标、内网地址访问都受浏览器管控。它的使命是在不破坏 Web 安全模型的前提下,给网页实时通信能力(为什么管这么严,第八节展开)。TCP Socket 则是系统级工具箱:收发任意字节流,调整一切参数。游戏服务器、数据库中间件这类后端基础设施,只能靠它建。
编程模型:消息 vs 字节。 WebSocket 面向消息,协议替你处理了粘包;TCP Socket 面向字节流,边界自己画。前者换来开发效率,后者换来性能和控制力。
表格里的能力差异是"质"的差别。还有一个"量"的问题常被追问:性能到底差多少?
五、性能差距是真实的,但多数场景无关紧要
开销从哪来?对比一条消息的处理路径:
- TCP Socket:收到字节流 → 按自定义规则拼包、解析 → 拿到业务数据;
- WebSocket:收到字节流 → 解析帧头 → 取出 payload → 解掩码 → 拿到业务数据。
每多出的一步都在消耗 CPU 和内存。一份典型的基准数据(局域网理想环境,纯协议层测试):
| 指标 | 原生 TCP Socket | WebSocket | 差距 |
|---|---|---|---|
| 吞吐量 | ~15,000 MB/s | ~1,200 MB/s | 约 12 倍 |
| 小消息延迟 | 2–5 微秒 | 50–100 微秒 | 约 20–50 倍 |
| 连接建立 | ~0.05 毫秒 | 1–5 毫秒(含 HTTP 握手) | 约 20–100 倍 |
数字看着悬殊,但有两个注脚:
- 这是把协议层单独拎出来测的极限数据。真实业务里,一次请求耗时的大头在网络 I/O、数据库、业务逻辑——协议层这几十微秒会被稀释到几乎看不见;
- 差距的绝对值是微秒级的,而网络本身的往返延迟就在几十到几百毫秒。对用户来说,50 微秒和 5 微秒没有任何可感知的差别。
结论一句话:只有当瓶颈真的卡在协议本身时——高频交易、实时对战、亿级并发下的转发层——才值得为那点性能提升去承担原生 TCP 的开发复杂度。 给一个网页聊天室纠结 WebSocket 的帧头开销,相当于纠结快递盒上胶带的重量。
数据看完了,落到具体场景怎么选?
六、选型实践:环境决定选择
用 Node.js 举例最直观,因为两种它都原生支持。
面向浏览器 → WebSocket(ws 库 / Socket.IO)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (data) => {
ws.send(`echo: ${data}`); // 发出去就是一条完整的消息,没有粘包
});
});
浏览器只认 WebSocket 这个协议。用它,握手和消息边界自动解决。Socket.IO 在这之上补了自动重连、房间广播、消息确认等工程能力——顺带提醒一句,很多人把 Socket.IO 简称 "Socket",但它和本文说的 Socket 不是一个东西,它内部封装的就是 WebSocket。
服务端之间 / 原生 App → TCP Socket(net 模块)
const net = require('net');
const server = net.createServer((socket) => {
let buf = Buffer.alloc(0);
socket.on('data', (chunk) => {
buf = Buffer.concat([buf, chunk]); // 字节流:先把碎片攒起来
while (buf.length >= 4) { // 约定协议:4 字节长度 + payload
const len = buf.readUInt32BE(0);
if (buf.length < 4 + len) break; // 包没收全,等下一个 data 事件
const payload = buf.subarray(4, 4 + len);
buf = buf.subarray(4 + len);
const reply = Buffer.from(`echo: ${payload}`);
const head = Buffer.alloc(4);
head.writeUInt32BE(reply.length, 0); // 回包也要自己封长度头
socket.write(Buffer.concat([head, reply]));
}
});
});
server.listen(8080);
注意中间那段 while 循环——这就是手动处理粘包、拆包的现场。两端都是自己可控的服务时,直接上 TCP:没有 HTTP 握手的额外开销,还可以定制极致紧凑的私有协议。
一个常见的例外:不少原生 App 为了省事(复用基建、多端统一),也直接集成 WebSocket 客户端连服务端。性能要求不苛刻时这完全合理;但追求极致性能或纯二进制私有协议的场景,首选仍是原生 TCP Socket。
"性能不苛刻时完全合理"——那性能真正苛刻的巨头,是怎么做的?
七、巨头怎么选:QQ 和微信为什么不用 WebSocket
先说事实:QQ、微信这类巨型 IM 的核心通信协议,都是基于 TCP Socket 的自定义私有协议,而不是 WebSocket。
原因不只是性能,更关键的是三个工程考量。
第一,弱网优化——这是决定性因素。 WebSocket 基于 TCP,而 TCP 有队头阻塞:一个包丢了,后面的包都得停下等重传。地铁里、电梯里、地下车库里的用户体验,就毁在这上面。微信的私有协议在 TCP 上层做了专门优化——比如在应用层多发冗余包,不必死等重传,让消息在极差网络下也能尽快送达。标准 WebSocket 给不了这种自由度。
第二,流量与电量控制。 微信的协议是二进制格式,紧凑到一个字节能表示多种状态;WebSocket 每条消息至少多出 2–14 字节帧头。在十亿级用户的规模下,省下的每个字节都是巨额带宽成本。更关键的是电量:私有协议可以自定义心跳策略,精准按需唤醒手机;标准 WebSocket 的心跳要应用层自己封装,相对低效且死板。
第三,多路复用。 QQ 能在同一个 TCP 连接里同时传输文字、图片、视频和心跳,这种高效复用是自研协议的基础能力。而标准场景下一个 WebSocket 连接通常只承载一个逻辑通道,多任务并发就得开多条连接,资源开销更大。
但这里有个关键反转:QQ 和微信用私有协议的前提,是它们的核心阵地是原生 App,不是浏览器。 原生客户端想用什么协议就用什么协议,自然选控制力最强的路。反过来,如果你的聊天应用跑在浏览器里,哪怕规模再大,首选依然是 WebSocket——因为你没得选,或者说,没必要为这点性能脱离整个 Web 生态。
"没得选"三个字值得深究:浏览器为什么不给网页 TCP Socket?这背后是 Web 安全模型的基石。
八、浏览器的锁:为什么网页碰不到 TCP Socket
严格来说,浏览器里的 JavaScript 无法建立任何原生 TCP 或 UDP 连接。不是技术做不到,是 W3C 和浏览器厂商刻意锁死的。
试想这道门要是开着,你随手点开一个恶意网页,它背后的脚本就能:
- 扫描你的内网:直接连
192.168.1.1:3306(MySQL)、:22(SSH)、:8080(内网 Jenkins)……这些设备没有公网防护,你的浏览器瞬间变成黑客进入内网的跳板; - 盗用你的身份:企业内网里,浏览器天然握着各种服务的登录态和网络凭证。恶意脚本可以连邮件服务器的 25 端口用你的身份发钓鱼邮件,连内网短信网关拦截你的验证码,甚至操纵打印机、摄像头这类物联网设备;
- 跨协议攻击:HTTP 有同源策略和严格校验,但 Redis、FTP 这些非 HTTP 协议没有防御。攻击者可以向它们发送精心构造的数据,利用协议间的格式歧义实现数据泄露或文件植入;
- 把你变成肉鸡:恶意脚本建立 TCP 连接收发外部指令,你的浏览器就是一台匿名僵尸机,被用来 DDoS、刷流量、中转非法内容;
- DNS 重绑定:更进阶的玩法——攻击者控制域名的解析记录,先指向恶意服务器(给你加载脚本),再改指向你的内网 IP。同源策略认域名不认 IP,恶意脚本就此获得"以内网身份"持续访问内网服务的权限,从一次性攻击升级为持久渗透。
所以结论很清晰:不是 TCP Socket 本身不安全,而是"任意网页代码 + 系统级网络权限"这个组合是灾难。 沙箱剥夺网页建立任意连接的能力,不是限制开发者,而是保护用户。
顺着这个逻辑还能得到一个更深的洞察:WebSocket 的安全性来自浏览器这层"外壳",而不是协议本身。 WebSocket 协议只定义数据怎么打包;真正把危险挡在门外的,是浏览器这个强制执行的中间人。它把网页的网络能力收束到几条可控通道里,每一条都被严格约束。
门锁死了,窗户开着。浏览器到底留了几扇窗?
九、沙箱里的三扇窗
浏览器里能用的实时、双向通信方案,就这三个:
| 方案 | 底层 | 定位 |
|---|---|---|
| WebSocket | TCP | 可靠的双向消息:聊天、金融行情、游戏指令 |
| WebRTC | UDP(DataChannel:SCTP over DTLS over UDP) | 低延迟双向流:音视频、P2P 数据传输 |
| fetch / SSE | HTTP | 请求-响应 / 服务端到浏览器的单向推送 |
其中 WebRTC 的 DataChannel 值得多说两句。它是浏览器里最接近"真 Socket"的存在:支持可靠或不可靠两种传输模式(类似 TCP / UDP 的语义可选),延迟远低于 WebSocket,还能在浏览器之间建立点对点连接。代价是架构复杂得多——信令服务器、STUN/TURN、ICE 协商,为了低延迟要养一整套基础设施。
如果你的需求是"连上后端的 TCP 私有协议",这条路在浏览器端是彻底堵死的。唯一的办法是协议转换:浏览器用 WebSocket 连到网关,网关再转成 TCP 连到后端。
而这个"网关",正是大型公司跨平台架构的核心部件。
十、跨平台架构的终极解法:协议转换网关
设想一个现实的难题:你的后端服务用的是 TCP 私有二进制协议(性能最优),现在产品要上 H5 页面(跑在 App 里的 Hybrid 页面),而浏览器只能用 WebSocket。怎么办?
腾讯 TARS(原 TAF)这类统一服务框架给出的答案是:在中间加一层协议转换网关。
H5 页面 ──WebSocket──┐
├──> 统一接入网关 ──TCP 私有协议──> 后端业务服务
原生 App ──TCP───────┘ │
└──> 注册中心(查实例、负载均衡)
网关同时监听两种端口:对 H5 用 WebSocket 协议,对原生 App 用原生 TCP 协议。一次 H5 请求的完整旅程是这样的:
- H5 里的 JS SDK 把业务数据打包成二进制,通过 WebSocket 发出;
- 网关收到 WebSocket 帧,解出里面的二进制负载——注意,网关不解析业务内容;
- 网关作为 TCP 客户端,把负载原样转发给后端业务服务;
- 后端处理完返回响应,网关再把它封装成 WebSocket 帧,发回给 H5。
整个流程里最妙的一点:对后端服务来说,请求来自 WebSocket 还是原生 TCP,它根本感知不到。
网关怎么知道该转发给哪个服务?靠两级路由,这套设计和 HTTP 世界的寻址惊人地同构:
- 第一级:Servant 名,类似域名。 格式是"应用名.服务名.对象名"(比如
order.UserServiceObj)。网关解析出这个名字,向注册中心查询:这个服务有哪些实例在线?IP 和端口分别是什么?再用负载均衡算法挑一个最空闲的; - 第二级:Func 名,类似 API 路径。 私有协议的包头里有专门字段写着要调用的方法,比如
getUserInfo,后端收到后交给对应的函数执行。
配套地,客户端两侧各有一个 SDK:原生 App SDK 直接把业务对象编码成二进制走 TCP;H5 的 JS SDK 暴露一模一样的 send(data) 接口,内部走 WebSocket。两个 SDK 对上层暴露的 API 和二进制编码格式完全一致,只有底层传输不同——业务代码完全无感。
这套架构的收益有三个:
- 安全隔离:后端服务不用直接暴露在公网;
- 架构统一:一套私有协议,服务所有客户端;
- 极致复用:鉴权、限流、日志全部在网关层统一完成,后端服务只关注业务。
同样的模式你在别处也见过:HTTP 世界叫 Nginx 反向代理 + 注册中心,gRPC 世界叫 gRPC Gateway。名字不同,骨架一致。
写在最后
回到开头那道面试题。
WebSocket 和 Socket 的区别,一句话的版本是:Socket 是操作系统递给你的网络工具箱,WebSocket 是跑在 TCP 之上、借 HTTP 握手出生的应用层协议。 一个是接口,一个是协议;一个在系统层,一个在应用层。
但真正理解这个区别,要看到它背后的三层逻辑:
- 环境决定选择:在浏览器里,WebSocket 几乎是唯一解;在系统级开发里,TCP Socket 才是主场;
- 规模决定深度:绝大多数应用,WebSocket 的性能绰绰有余;只有做到微信那个体量、要面对极端弱网那种场景,才值得为私有协议投入工程成本;
- 安全决定边界:浏览器锁死 TCP Socket 不是刁难开发者,而是保护用户。WebSocket 的安全来自沙箱外壳,而非协议本身。
一字之差,隔着整个协议栈。下次再有人把两者混为一谈,你可以把这台"从毛坯房到精装房"的对比,讲给他听。后端面试里有一道经典题:"WebSocket 和 Socket 有什么区别?"
这道题的通过率低得反常。不是因为它冷门,恰恰因为它太常见:前端天天 new WebSocket(),后端张口闭口 Socket 编程,两个词朝夕相处,真被问到区别,多数人只能挤出一句"一个是网页用的,一个是底层的"。
更麻烦的是,网上流传着几个似是而非的说法:
- "WebSocket 就是浏览器版的 Socket"
- "Socket 是一种协议"
- "WebSocket 走的是 HTTP"
这三句话,一句都不准确。这篇文章从这几个误解出发,把两者的技术原理、性能差距、选型逻辑讲清楚,再往深处走一步——看看它们背后牵出的浏览器安全模型,以及腾讯 TARS 这类跨平台架构是怎么把两者缝合到一起的。
一、先纠正一个误解:Socket 根本不是协议
先说结论:Socket 是操作系统提供的编程接口,不是协议。
1983 年,加州大学伯克利分校在 4.2BSD 版本的 Unix 里集成了 TCP/IP 协议栈,同时留出一套系统调用,让普通程序也能用上网络——这套接口叫 Berkeley Socket API。从那时起,"Socket"这个词指的就不是某个协议,而是操作系统暴露给程序员、用来操纵协议栈的把手。
它的样子大致是这样的:
int fd = socket(AF_INET, SOCK_STREAM, 0); // 我要一个 TCP Socket
int fd = socket(AF_INET, SOCK_DGRAM, 0); // 我要一个 UDP Socket
第二个参数决定你拿到的是什么:
- SOCK_STREAM → TCP Socket:可靠字节流,建立连接前先三次握手;
- SOCK_DGRAM → UDP Socket:无连接,没有握手,只管发不管确认;
- SOCK_RAW → Raw Socket:绕过传输层直达网络层,能直接读写内核通常不处理的 IP 包。网络抓包工具(tcpdump、Wireshark 的底层)和全新自定义协议的实现,靠的都是它。
放到协议栈上看,三者各居其位:
┌──────────────────────────────┐
│ 应用层 │ WebSocket(消息帧)、HTTP……
├──────────────────────────────┤
│ 传输层 │ TCP / UDP ← Socket 接口在这层供你调用
├──────────────────────────────┤
│ 网络层 │ IP ← Raw Socket 直达这层
├──────────────────────────────┤
│ 链路层 / 物理层 │
└──────────────────────────────┘
这里藏着第二个常见误解:把 Socket 等同于 TCP 或 UDP 中的某一个。都不对。Socket 是那把扳手,TCP 和 UDP 是你用扳手去拧的螺丝。 传入不同参数,同一套接口给你不同的传输特性。
可以用一个比喻收束这一节:Socket 是地基和毛坯房,WebSocket 是在地基上按特定标准装修好的精装房。这个比喻还藏着下一节的引子——WebSocket 这个"精装房"是怎么盖起来的?它的出生方式相当取巧:借 HTTP 的壳。
二、WebSocket:借 HTTP 的壳出生,出生后就不认 HTTP 了
很多人以为"WebSocket 使用 HTTP 协议通信"。不准确。它的生命周期分成界限分明的两个阶段。
握手阶段:借用 HTTP。 客户端先发一个"长得很像普通网页请求"的 HTTP 请求:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
关键在 Upgrade: websocket 这一行——客户端在请求把协议升级。服务器如果支持,返回:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
数据传输阶段:彻底脱离 HTTP。 101 状态码一返回,这条 TCP 连接上跑的就不再是 HTTP,而是 WebSocket 自己的帧格式。HTTP 的使命到此结束——它只是个"启动器"。
为什么要绕这么一道?这是 WebSocket 设计上最精妙的一笔。全世界的防火墙、反向代理、企业网关,几乎都认 HTTP(80/443 端口)。一个新协议想穿过现成的互联网基础设施,最好的办法就是出生时伪装成 HTTP,握手成功后再"变身"。基础设施只需放行这一次升级请求,之后的数据帧对它们来说是普通的 TCP 流量。
这就是 WebSocket 能成为 Web 标准的关键,而无数更高效的自定义协议死在了防火墙面前。
握手之后,WebSocket 的数据以帧为单位传输:
- 每条消息可以是文本或二进制,类型在帧头里标明;
- 帧头只有 2–14 字节,极其轻量;
- 客户端发往服务器的数据强制做掩码处理,防的是中间代理的缓存投毒攻击;
- 协议自带消息边界——你
send("Hello"),对方onmessage收到的就是一条完整的"Hello"。
最后这一点看着不起眼,恰恰是 TCP Socket 最让人头疼的地方。
三、TCP Socket:一根水管,和绕不开的"粘包"
TCP Socket 给你的是一根水管:这头倒进去什么字节,那头就接住什么字节。但水是连成一片的,没有"一杯一杯"的概念。
经典问题立刻出现:
socket.write('Hello');
socket.write('World');
对端一次 data 事件收到的,很可能是 "HelloWorld",也可能是 "Hel" 加上下一轮的 "loworld"。这就是流传已久的"粘包"问题。
严格地说,这个词有点冤枉 TCP——TCP 从没承诺过消息边界,它是字节流协议,"包粘住了"是应用层的一厢情愿。但麻烦是真实的:用 TCP Socket 通信,你必须自己设计应用层协议。常见三种方案:
- 定长消息:每条消息固定 N 字节,不足就填充。实现最简单,也最浪费;
- 特殊分隔符:用
\n或某个魔法字节分隔,文本协议常用,要处理内容转义; - 长度前缀:用固定字节标明消息长度(比如前 4 字节存长度,后面跟 payload)。最通用的方案,绝大多数私有二进制协议和 RPC 框架都走这条路。
协议设计好之后,TCP Socket 就给你完全的控制权:超时、缓冲区大小、TCP_NODELAY(关闭 Nagle 算法——内核默认会把小包攒一波再发,省带宽但增加延迟)、keepalive 参数……全是你的。自由,但每一层都要自己负责。
两边都见过了,把所有区别摆上桌。
四、一张表看清两者的边界
| 维度 | TCP Socket | WebSocket |
|---|---|---|
| 本质 | 传输层编程接口 | 应用层协议 |
| 位置 | 操作系统层 | 跑在 TCP 之上 |
| 握手 | 三次握手,只认 IP + 端口 | 三次握手 + HTTP 协议升级 |
| 数据单位 | 字节流,自己处理边界 | 消息帧,天然有边界 |
| 运行环境 | 任意进程,系统级权限 | 浏览器沙箱 / 任意语言实现 |
| 可选传输 | TCP / UDP / Raw | 只能 TCP |
| 基础设施亲和度 | 无法被通用 HTTP 设施管理 | 轻松穿透防火墙和反向代理 |
表里有几个维度值得展开。
能力边界:沙箱特供 vs 系统工具箱。 WebSocket 在浏览器里是"特供品":它是浏览器内置的实时双向通信 API,被严格限制——只能发文本或二进制消息,碰不到原始 TCP 包,更碰不到 UDP;连接目标、内网地址访问都受浏览器管控。它的使命是在不破坏 Web 安全模型的前提下,给网页实时通信能力(为什么管这么严,第八节展开)。TCP Socket 则是系统级工具箱:收发任意字节流,调整一切参数。游戏服务器、数据库中间件这类后端基础设施,只能靠它建。
编程模型:消息 vs 字节。 WebSocket 面向消息,协议替你处理了粘包;TCP Socket 面向字节流,边界自己画。前者换来开发效率,后者换来性能和控制力。
表格里的能力差异是"质"的差别。还有一个"量"的问题常被追问:性能到底差多少?
五、性能差距是真实的,但多数场景无关紧要
开销从哪来?对比一条消息的处理路径:
- TCP Socket:收到字节流 → 按自定义规则拼包、解析 → 拿到业务数据;
- WebSocket:收到字节流 → 解析帧头 → 取出 payload → 解掩码 → 拿到业务数据。
每多出的一步都在消耗 CPU 和内存。一份典型的基准数据(局域网理想环境,纯协议层测试):
| 指标 | 原生 TCP Socket | WebSocket | 差距 |
|---|---|---|---|
| 吞吐量 | ~15,000 MB/s | ~1,200 MB/s | 约 12 倍 |
| 小消息延迟 | 2–5 微秒 | 50–100 微秒 | 约 20–50 倍 |
| 连接建立 | ~0.05 毫秒 | 1–5 毫秒(含 HTTP 握手) | 约 20–100 倍 |
数字看着悬殊,但有两个注脚:
- 这是把协议层单独拎出来测的极限数据。真实业务里,一次请求耗时的大头在网络 I/O、数据库、业务逻辑——协议层这几十微秒会被稀释到几乎看不见;
- 差距的绝对值是微秒级的,而网络本身的往返延迟就在几十到几百毫秒。对用户来说,50 微秒和 5 微秒没有任何可感知的差别。
结论一句话:只有当瓶颈真的卡在协议本身时——高频交易、实时对战、亿级并发下的转发层——才值得为那点性能提升去承担原生 TCP 的开发复杂度。 给一个网页聊天室纠结 WebSocket 的帧头开销,相当于纠结快递盒上胶带的重量。
数据看完了,落到具体场景怎么选?
六、选型实践:环境决定选择
用 Node.js 举例最直观,因为两种它都原生支持。
面向浏览器 → WebSocket(ws 库 / Socket.IO)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (data) => {
ws.send(`echo: ${data}`); // 发出去就是一条完整的消息,没有粘包
});
});
浏览器只认 WebSocket 这个协议。用它,握手和消息边界自动解决。Socket.IO 在这之上补了自动重连、房间广播、消息确认等工程能力——顺带提醒一句,很多人把 Socket.IO 简称 "Socket",但它和本文说的 Socket 不是一个东西,它内部封装的就是 WebSocket。
服务端之间 / 原生 App → TCP Socket(net 模块)
const net = require('net');
const server = net.createServer((socket) => {
let buf = Buffer.alloc(0);
socket.on('data', (chunk) => {
buf = Buffer.concat([buf, chunk]); // 字节流:先把碎片攒起来
while (buf.length >= 4) { // 约定协议:4 字节长度 + payload
const len = buf.readUInt32BE(0);
if (buf.length < 4 + len) break; // 包没收全,等下一个 data 事件
const payload = buf.subarray(4, 4 + len);
buf = buf.subarray(4 + len);
const reply = Buffer.from(`echo: ${payload}`);
const head = Buffer.alloc(4);
head.writeUInt32BE(reply.length, 0); // 回包也要自己封长度头
socket.write(Buffer.concat([head, reply]));
}
});
});
server.listen(8080);
注意中间那段 while 循环——这就是手动处理粘包、拆包的现场。两端都是自己可控的服务时,直接上 TCP:没有 HTTP 握手的额外开销,还可以定制极致紧凑的私有协议。
一个常见的例外:不少原生 App 为了省事(复用基建、多端统一),也直接集成 WebSocket 客户端连服务端。性能要求不苛刻时这完全合理;但追求极致性能或纯二进制私有协议的场景,首选仍是原生 TCP Socket。
"性能不苛刻时完全合理"——那性能真正苛刻的巨头,是怎么做的?
七、巨头怎么选:QQ 和微信为什么不用 WebSocket
先说事实:QQ、微信这类巨型 IM 的核心通信协议,都是基于 TCP Socket 的自定义私有协议,而不是 WebSocket。
原因不只是性能,更关键的是三个工程考量。
第一,弱网优化——这是决定性因素。 WebSocket 基于 TCP,而 TCP 有队头阻塞:一个包丢了,后面的包都得停下等重传。地铁里、电梯里、地下车库里的用户体验,就毁在这上面。微信的私有协议在 TCP 上层做了专门优化——比如在应用层多发冗余包,不必死等重传,让消息在极差网络下也能尽快送达。标准 WebSocket 给不了这种自由度。
第二,流量与电量控制。 微信的协议是二进制格式,紧凑到一个字节能表示多种状态;WebSocket 每条消息至少多出 2–14 字节帧头。在十亿级用户的规模下,省下的每个字节都是巨额带宽成本。更关键的是电量:私有协议可以自定义心跳策略,精准按需唤醒手机;标准 WebSocket 的心跳要应用层自己封装,相对低效且死板。
第三,多路复用。 QQ 能在同一个 TCP 连接里同时传输文字、图片、视频和心跳,这种高效复用是自研协议的基础能力。而标准场景下一个 WebSocket 连接通常只承载一个逻辑通道,多任务并发就得开多条连接,资源开销更大。
但这里有个关键反转:QQ 和微信用私有协议的前提,是它们的核心阵地是原生 App,不是浏览器。 原生客户端想用什么协议就用什么协议,自然选控制力最强的路。反过来,如果你的聊天应用跑在浏览器里,哪怕规模再大,首选依然是 WebSocket——因为你没得选,或者说,没必要为这点性能脱离整个 Web 生态。
"没得选"三个字值得深究:浏览器为什么不给网页 TCP Socket?这背后是 Web 安全模型的基石。
八、浏览器的锁:为什么网页碰不到 TCP Socket
严格来说,浏览器里的 JavaScript 无法建立任何原生 TCP 或 UDP 连接。不是技术做不到,是 W3C 和浏览器厂商刻意锁死的。
试想这道门要是开着,你随手点开一个恶意网页,它背后的脚本就能:
- 扫描你的内网:直接连
192.168.1.1:3306(MySQL)、:22(SSH)、:8080(内网 Jenkins)……这些设备没有公网防护,你的浏览器瞬间变成黑客进入内网的跳板; - 盗用你的身份:企业内网里,浏览器天然握着各种服务的登录态和网络凭证。恶意脚本可以连邮件服务器的 25 端口用你的身份发钓鱼邮件,连内网短信网关拦截你的验证码,甚至操纵打印机、摄像头这类物联网设备;
- 跨协议攻击:HTTP 有同源策略和严格校验,但 Redis、FTP 这些非 HTTP 协议没有防御。攻击者可以向它们发送精心构造的数据,利用协议间的格式歧义实现数据泄露或文件植入;
- 把你变成肉鸡:恶意脚本建立 TCP 连接收发外部指令,你的浏览器就是一台匿名僵尸机,被用来 DDoS、刷流量、中转非法内容;
- DNS 重绑定:更进阶的玩法——攻击者控制域名的解析记录,先指向恶意服务器(给你加载脚本),再改指向你的内网 IP。同源策略认域名不认 IP,恶意脚本就此获得"以内网身份"持续访问内网服务的权限,从一次性攻击升级为持久渗透。
所以结论很清晰:不是 TCP Socket 本身不安全,而是"任意网页代码 + 系统级网络权限"这个组合是灾难。 沙箱剥夺网页建立任意连接的能力,不是限制开发者,而是保护用户。
顺着这个逻辑还能得到一个更深的洞察:WebSocket 的安全性来自浏览器这层"外壳",而不是协议本身。 WebSocket 协议只定义数据怎么打包;真正把危险挡在门外的,是浏览器这个强制执行的中间人。它把网页的网络能力收束到几条可控通道里,每一条都被严格约束。
门锁死了,窗户开着。浏览器到底留了几扇窗?
九、沙箱里的三扇窗
浏览器里能用的实时、双向通信方案,就这三个:
| 方案 | 底层 | 定位 |
|---|---|---|
| WebSocket | TCP | 可靠的双向消息:聊天、金融行情、游戏指令 |
| WebRTC | UDP(DataChannel:SCTP over DTLS over UDP) | 低延迟双向流:音视频、P2P 数据传输 |
| fetch / SSE | HTTP | 请求-响应 / 服务端到浏览器的单向推送 |
其中 WebRTC 的 DataChannel 值得多说两句。它是浏览器里最接近"真 Socket"的存在:支持可靠或不可靠两种传输模式(类似 TCP / UDP 的语义可选),延迟远低于 WebSocket,还能在浏览器之间建立点对点连接。代价是架构复杂得多——信令服务器、STUN/TURN、ICE 协商,为了低延迟要养一整套基础设施。
如果你的需求是"连上后端的 TCP 私有协议",这条路在浏览器端是彻底堵死的。唯一的办法是协议转换:浏览器用 WebSocket 连到网关,网关再转成 TCP 连到后端。
而这个"网关",正是大型公司跨平台架构的核心部件。
十、跨平台架构的终极解法:协议转换网关
设想一个现实的难题:你的后端服务用的是 TCP 私有二进制协议(性能最优),现在产品要上 H5 页面(跑在 App 里的 Hybrid 页面),而浏览器只能用 WebSocket。怎么办?
腾讯 TARS(原 TAF)这类统一服务框架给出的答案是:在中间加一层协议转换网关。
H5 页面 ──WebSocket──┐
├──> 统一接入网关 ──TCP 私有协议──> 后端业务服务
原生 App ──TCP───────┘ │
└──> 注册中心(查实例、负载均衡)
网关同时监听两种端口:对 H5 用 WebSocket 协议,对原生 App 用原生 TCP 协议。一次 H5 请求的完整旅程是这样的:
- H5 里的 JS SDK 把业务数据打包成二进制,通过 WebSocket 发出;
- 网关收到 WebSocket 帧,解出里面的二进制负载——注意,网关不解析业务内容;
- 网关作为 TCP 客户端,把负载原样转发给后端业务服务;
- 后端处理完返回响应,网关再把它封装成 WebSocket 帧,发回给 H5。
整个流程里最妙的一点:对后端服务来说,请求来自 WebSocket 还是原生 TCP,它根本感知不到。
网关怎么知道该转发给哪个服务?靠两级路由,这套设计和 HTTP 世界的寻址惊人地同构:
- 第一级:Servant 名,类似域名。 格式是"应用名.服务名.对象名"(比如
order.UserServiceObj)。网关解析出这个名字,向注册中心查询:这个服务有哪些实例在线?IP 和端口分别是什么?再用负载均衡算法挑一个最空闲的; - 第二级:Func 名,类似 API 路径。 私有协议的包头里有专门字段写着要调用的方法,比如
getUserInfo,后端收到后交给对应的函数执行。
配套地,客户端两侧各有一个 SDK:原生 App SDK 直接把业务对象编码成二进制走 TCP;H5 的 JS SDK 暴露一模一样的 send(data) 接口,内部走 WebSocket。两个 SDK 对上层暴露的 API 和二进制编码格式完全一致,只有底层传输不同——业务代码完全无感。
这套架构的收益有三个:
- 安全隔离:后端服务不用直接暴露在公网;
- 架构统一:一套私有协议,服务所有客户端;
- 极致复用:鉴权、限流、日志全部在网关层统一完成,后端服务只关注业务。
同样的模式你在别处也见过:HTTP 世界叫 Nginx 反向代理 + 注册中心,gRPC 世界叫 gRPC Gateway。名字不同,骨架一致。
写在最后
回到开头那道面试题。
WebSocket 和 Socket 的区别,一句话的版本是:Socket 是操作系统递给你的网络工具箱,WebSocket 是跑在 TCP 之上、借 HTTP 握手出生的应用层协议。 一个是接口,一个是协议;一个在系统层,一个在应用层。
但真正理解这个区别,要看到它背后的三层逻辑:
- 环境决定选择:在浏览器里,WebSocket 几乎是唯一解;在系统级开发里,TCP Socket 才是主场;
- 规模决定深度:绝大多数应用,WebSocket 的性能绰绰有余;只有做到微信那个体量、要面对极端弱网那种场景,才值得为私有协议投入工程成本;
- 安全决定边界:浏览器锁死 TCP Socket 不是刁难开发者,而是保护用户。WebSocket 的安全来自沙箱外壳,而非协议本身。
一字之差,隔着整个协议栈。下次再有人把两者混为一谈,你可以把这台"从毛坯房到精装房"的对比,讲给他听。
©本文版权归作者所有,未经授权禁止转载。如需转载,请注明原文出处及链接。
Comments
No comments yet
Be the first to comment!