# Socket
30 秒速记
Socket= 网络通信的端点,IP+ 端口,是操作系统给应用的通信接口TCP Socket:可靠、有序的字节流,没有消息边界 → 要自己处理粘包、拆包WebSocket:HTTP升级出来的应用层协议,按消息收发,浏览器能直接用;和TCP Socket不是一个层次Node里:net模块写TCP,dgram写UDP,WebSocket用ws库或Node 22起内置的客户端- 长连接要管:心跳、超时、断线重连、消息去重、关闭时释放资源
Socket 可以理解成两台机器之间通信的插座,IP 加端口确定一端,操作系统通过它把数据收发出去。 前端常听到的 WebSocket 和这里的 TCP Socket 不是一回事:TCP Socket 是传输层的字节流,你发两次 hello,对方可能一次收到 hellohello,消息边界得自己定;WebSocket 是跑在 TCP 上的应用层协议,帮你把数据切成一条条消息。Node 里写 TCP 用 net 模块,几行就能起一个服务。
const net = require('net')
net.createServer((socket) => {
socket.on('data', (buf) => socket.write('echo: ' + buf)) // 原样回显
socket.on('error', () => socket.destroy()) // 出错要释放
}).listen(9000)
// 另开终端:nc localhost 9000,输入 hi → 收到 echo: hi
理解「Socket」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 network 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
Socket 这个词在不同语境下指的东西不一样,先分清层次:
- 操作系统层面,
Socket是进程进行网络通信的接口,由协议、本地IP端口、远端IP端口确定一条连接。 - 传输层有
TCP和UDP两种。TCP面向连接、可靠有序,但它是字节流,没有消息边界;UDP无连接、不保证送达,但每个数据报是独立的。 - 应用层的
WebSocket运行在TCP之上,通过HTTP的Upgrade握手建立连接,之后用帧传输消息,浏览器可以直接使用。Socket.IO又是在WebSocket之上再封装的一层,自带重连、房间、降级轮询,它的客户端不能直接连原生WebSocket服务。
在 Node 中对应的模块:
require('net') // TCP 服务端和客户端
require('dgram') // UDP
require('ws') // 第三方 WebSocket 服务端,最常用
// Node 22 起全局有 WebSocket 客户端(和浏览器同一套 API)
写长连接服务时,要额外处理这些问题:
- 消息边界:
TCP上要自己做分帧,常见是长度前缀或分隔符。 - 存活检测:网络中断时连接可能变成「半开」,要靠心跳或空闲超时发现并清理。
- 背压:对方读得慢时,
socket.write()会返回false,要等drain事件再继续写,不然内存会一直涨。 - 多实例:内存里的连接表只在本进程有效,多台机器广播要借助
Redis的发布订阅等外部消息通道。 - 重连与去重:断线重连后可能收到重复消息,业务上要带消息
id做幂等。
面试官追问
追问 1客户端连续发了两条消息,服务端 data 事件只触发一次,内容粘在一起了,为什么?
TCP 是字节流,没有消息边界,内核会合并或拆分数据。要自己定协议:按换行分隔,或者每条消息前加 4 字节长度头,服务端攒够长度再切。
追问 2TCP Socket 和 WebSocket 是什么关系?
WebSocket 跑在 TCP 之上,先用一次 HTTP 请求升级协议,之后按帧收发消息,帮你处理了边界和掩码。浏览器出于安全不开放裸 TCP,只能用 WebSocket。
追问 3服务端测试跑完了,Node 进程却一直不退出,查什么?
多半是还有活着的句柄:没关的连接、没 close() 的 server、没清的定时器。用 why-is-node-running 这类工具能列出来;测试里要在 afterAll 里把连接和服务都关掉。
追问 4长连接服务上线后,连接数只涨不降,怎么回事?
客户端异常断网时,服务端未必能马上感知,死连接一直挂着。要加心跳和空闲超时,比如 socket.setTimeout(60000) 后在 timeout 事件里 destroy,或者开 setKeepAlive。
# WebSocket
30 秒速记
WebSocket= 一条全双工长连接,服务端能主动推,客户端也能随时发- 握手借
HTTP:请求带Upgrade: websocket,服务端回101 Switching Protocols,之后就不是HTTP了 - 地址
ws:///wss://,HTTPS页面只能连wss:// - 浏览器
API:onopen/onmessage/onerror/onclose+send()/close() - 工程必备:心跳、断线重连(指数退避)、消息确认去重、多实例靠
Redis发布订阅广播
WebSocket 是浏览器和服务器之间的一条长连接电话线,接通之后双方随时都能说话,不用像 HTTP 那样一问一答。 它借 HTTP 完成握手,服务端回 101 之后,这条 TCP 连接就切换成 WebSocket 协议,按帧收发消息,每帧头部只有几个字节,比每次带一堆 HTTP 头轻得多。真正难的是连接的生命周期:网断了浏览器不一定马上知道,所以要心跳;断了要自动重连,而且别所有客户端同一秒一起重连。
// Node 服务端,用 ws 库
const { WebSocketServer } = require('ws')
const wss = new WebSocketServer({ port: 8080 })
wss.on('connection', (ws) => {
ws.on('message', (data) => ws.send(`收到:${data}`))
})
// 浏览器:new WebSocket('ws://localhost:8080').onmessage = e => console.log(e.data)
理解「WebSocket」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 network 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
WebSocket 的连接过程分两步:先是一个普通的 HTTP/1.1 请求,带上升级头;服务端同意后,这条 TCP 连接就改为 WebSocket 协议传输帧。
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Accept 是服务端用客户端的 Key 拼上一个固定字符串后做 SHA-1 再 Base64 得到的,用来证明对方真的理解 WebSocket 协议,不是什么安全机制。
浏览器端一个带心跳和重连的最小封装:
function connect(url, onMessage, retry = 0) {
const ws = new WebSocket(url)
let timer
ws.onopen = () => {
retry = 0
timer = setInterval(() => ws.send('{"type":"ping"}'), 30000) // 心跳
}
ws.onmessage = (e) => onMessage(JSON.parse(e.data))
ws.onclose = () => {
clearInterval(timer)
const delay = Math.min(30000, 1000 * 2 ** retry) + Math.random() * 1000
setTimeout(() => connect(url, onMessage, retry + 1), delay) // 指数退避重连
}
}
