想象一个场景:你给朋友寄了一封信,问他“周末有空吗”,信寄出去后你只能干等回信。这就是传统 HTTP 的交流方式——客户端发一个请求,服务器回一个响应,一来一回,像寄明信片。可如果朋友是个话痨,想一边想一边说、不间断地跟你分享想法,寄信就完全没法用。于是有了电话:拨号(握手)、接通(101)、然后双方你一言我一语随时开聊,谁想开口都能开口,这就是 WebSocket。它让服务器不再是被动等提问的“信箱”,而是能主动开口的“电话那头的真人”。本文就用“打电话”这个画面,带你拆解 WebSocket 的双向通信原理:怎么拨号、怎么通话、怎么挂断,以及它如何支撑 AI 大模型的流式输出——让 LLM 像真人一样边想边说,而不是憋一篇长信一次性寄给你。
一句话价值:搞懂 WebSocket 双向通信,你就能让 LLM 应用做到“边生成边显示”的流式体验,并掌握实时通知、连接保活与高并发管理这组 AI 应用工程师的核心基本功。
目录
可视化演示
展示WebSocket全双工双向通信机制、连接生命周期状态机、LLM实时交互模式,理解服务器主动推送消息的底层原理与应用。
本节配有交互动效(进入视口后自动加载,约 3KB)。
WebSocket全双工双向通信 基于TCP全双工连接,服务器和客户端可同时收发数据互不干扰 服务器可主动推送消息,无需客户端轮询请求 效率 = 1 - (轮询开销/总带宽) 全双工实时通信
HTTP请求响应模式(寄信的旧常态)
想象“寄信”:你写一封“在吗?”,贴上邮票寄出去,然后望着邮筒等回信。HTTP 就是这样——客户端永远是提问方,服务器永远是答复方。在 LLM 时代这就尴尬了:让模型写一篇 1000 字的文章,它得花十几秒甚至更久。如果你想实时看到文字蹦出来,用 HTTP 就要不停寄信问“写完没?”,这就是“轮询”。
轮询就像每秒钟跑去邮筒看一次信,大多数时候空手而归,浪费邮费也浪费人力。长轮询稍微聪明些——邮递员在你家楼下等着,有新信立刻送来,但本质上还是“你问、它答”。真正的矛盾在于:服务器有消息时无法主动开口,只能干等下一次提问。
1. 理解轮询次数:\(N_{\text{poll}}\) 指客户端在等待期间发起请求的总次数——次数越高,浪费越大。
2. 请求响应耗时:\(T_{\text{req}} + T_{\text{resp}}\) 是一次完整的 HTTP 请求加响应所花的时间,包含网络往返与服务器处理。
3. 总开销倍增:把轮询次数与单次往返耗时相乘,得到整个等待过程付出的总时间成本。
寄信模式就像只能你问一句、它答一句的单向线,问题在于:服务器声音再好,也得你主动打电话过去才听得到。这正是 WebSocket 要打破的旧常态。
WebSocket握手过程(拨号接通)
打电话的第一步是拨号。号码拨通后,接线员确认“你找谁、你身份对不对”,然后“咔哒”一声接通——之后你们才能畅聊。WebSocket 的握手就是这个“拨号 + 身份确认”。
握手使用 HTTP 协议发起:客户端发一个带 Upgrade: websocket 的 GET 请求,并在 Sec-WebSocket-Key 字段放一个随机密钥。服务器拿到密钥后,拼上一个规定的固定字符串(GUID),做一次 SHA-1 哈希,再 Base64 编码,得到 Sec-WebSocket-Accept。把它放进响应,状态码返回 101 Switching Protocols,表示“从现在起,这条链路升级为 WebSocket”。
这一步既是身份校验,也是“拨号成功”的信号。此后连接不再走 HTTP 的请求-响应,而是双工通道。
1. 拼接密钥与固定 GUID:把客户端发来的 Key 与全局固定字符串 GUID 前后拼接成一个待哈希的原始字符串。
2. SHA-1 哈希压缩:对拼接结果做 SHA-1 运算,无论输入多长都输出固定 20 字节的摘要。
3. Base64 编码传输:摘要转成 Base64 文本,便于放进 HTTP 头,完成握手响应,客户端再对比验证。
就像电话接通前双方互报姓名,握完手你俩都知道“对面是真人”,线路正式开放。从这一刻起,不再有“寄信人”和“收信人”之分,只有平等的两个通话方。
消息帧格式(说话的音节)
通了话,声音怎么传?说出的每句话被压缩成一组音节编码,对方耳朵解码还原。WebSocket 把每条消息切成“帧(frame)”来传输,帧就是编好码的音节。
一个 WebSocket 帧像一张表格:第一行是帧头,包含 FIN(这句话说完了吗)、RSV(扩展保留位)、Opcode(这是什么类型的帧——文本/二进制/关闭/Ping/Pong)、Mask(有没有掩码)、Length(数据多长),之后才是数据 Payload。文本消息的 opcode 是 1,二进制是 2,Ping 是 9,Pong 是 10,关闭连接是 8。
有个有趣的规则:客户端发给服务器的数据帧必须加掩码(Mask=1),服务器发给客户端的帧则禁止掩码。这是协议为了防止早期代理缓存投毒。就像打电话时,你这边要经过一个语音加密器,对方那边不需要。
1. FIN 与 Opcode 标定类型:FIN 表示这是不是最后一个分片,Opcode 告诉接收方帧的用途。
2. Mask 与 Length 描述数据:第 2 个字节的最高位是 Mask,低 7 位是短长度;超长时用扩展长度字段表示。
3. 载荷经过掩码还原:客户端数据用 4 字节掩码逐字节异或还原,得到真正的消息内容。
音节编得再炫,双方解码规则一致才能听懂——WebSocket 的帧格式就是通话双方共同的“语言编码表”。只要遵循同一张表,任何语言(文本、二进制文件)都能在这条电话线上传输。
双向通信数据流(全双工通话)
打电话和寄信最大的不同:电话里你可以同时说、同时听,不用等对方闭嘴。WebSocket 建立后就是这样的全双工通道——两个方向的流量互不阻塞,各走各的“车道”。
在 WebSocket 连接上,客户端可以随时发消息,服务器也可以随时发消息,双方平等。不存在“必须你先问,我才能答”的约束。整个链路由 TCP 保证字节顺序与不丢包,应用层只要把消息拆帧拼帧就行。传输层为每个连接维护两个方向的滑动窗口,数据抵达后由事件循环分发。
这种双向性让延迟大幅降低:服务器收到一个新事件(比如监控告警、新的 token),毫秒级就能把消息推到客户端,而不需要客户端先来问。
1. 上行数据量:窗口内客户端发给服务器的字节总数,比如发送的聊天内容、控制指令。
2. 下行数据量:窗口内服务器推给客户端的字节总数,比如 LLM token、通知消息。
3. 除以时间窗口:用总数据量除以统计时长,得到平均双向吞吐速率。
就像电话里你俩可以同时开口,虽然现实中人习惯轮流说,但线路本身支持真正的同时双向。WebSocket 的数据流也是如此——技术上完全对等,谁主动都不稀奇。
连接生命周期(通话的起止)
一次电话有完整的起止:拨号→接通→通话→挂断。中间如果信号不好,还会“喂,喂,还在吗?”。WebSocket 连接的生命周期也一样:打开(open)、消息往来(active)、关闭(close),还有“喂还在吗”的心跳。
连接打开后,服务器和客户端都会维护状态。为了探测死连接(比如对方悄悄断线、中间路由器把空闲连接清了),双方周期性地发 Ping 帧,收到 Pong 帧就说明对方还活着——这就是心跳保活。关闭连接时,一方发 Close 帧(opcode 8),另一方回 Close 帧,然后 TCP 才真正断开。
如果连接异常断开(网络抖动、进程崩溃),客户端需要自动重连,并恢复之前的订阅状态。这就是为什么工程上要有“断线重连 + 状态恢复”机制。
1. 预算时间计算:链路中代理空闲超时与 TCP 超时减网络延迟相比,取较小者作为心跳预算。
2. 心跳间隔上限:心跳周期必须严格小于这个预算,才能保证连接不被误杀。
3. 留出安全余量:工程上通常取预算的一定比例,例如 60%,避免网络抖动导致误判。
会管理通话的人不会让电话线空转太久,定期“喂”一声,才能一直保持联系。WebSocket 的心跳就是这声“喂”——廉价、及时,让连接常青。
服务器推送机制(主动开口的服务员)
想象你坐在餐厅,服务员不会等你每次举手才来上菜,菜好了她端着盘子主动走过来。WebSocket 让服务器变成了“主动上菜的服务员”——推送机制是服务器端的技术核心。
服务器的推送通常是事件驱动的:内部有一个事件源(数据库变更、消息队列、LLM token 生成器),事件一产生,服务器立刻找到对应的连接把消息推出去。按范围分有“单播”(只发给指定用户)和“广播”(发给所有连接)。控制推送时机也很重要:如果客户端消费慢,服务端不能无限狂推,否则内存会爆,这叫“背压”(backpressure)——类似给服务员说“上菜慢一点,我这桌还没吃完”。
工程上常用发送队列实现背压:每个连接维护一个消息队列,当队列超过阈值就降速或断开慢消费者。
1. 速率与等待时长:\(\lambda_i\) 是第 i 个连接上的消息到达速率,\(d_i\) 是消息在队列中的平均等待时长。
2. 每队列积压量:一个连接上速率与等待时长的乘积就是该队列的积压消息量。
3. 汇总所有队列:累加所有连接得到总积压,用来判断是否需要触发背压策略。
聪明的服务员会观察每桌出菜进度,该上就上、该缓就缓——服务器推送讲究的就是“时机”和“节奏”。事件驱动加背压控制,就是这桌菜的“上菜节奏”。
LLM流式输出(AI 边想边说)
真人聊天时是边想边说,说一半停顿一下、再继续,你不需要等他把整段话想完才听到开头。LLM 大模型生成回答也一样,它是一次一个 token(词元)地“想”出来的。如果等全部生成完再用 HTTP 一次性返回,用户对着转圈圈的面板等十几秒,体验极差。
WebSocket 让服务器把生成的 token 增量地实时推给前端:生成第一个 token 就推第一个,前端收到一个渲染一个,用户看到文字“打字机式”蹦出来。这个方案还支持双向控制——比如用户在生成过程中点击“停止”,客户端可以通过同一连接发一个停止指令,服务器立刻中断生成。对比 SSE(Server-Sent Events),SSE 是单向的(只能服务器推),WebSocket 是双向的,还可以传二进制。
从延迟角度,用户体验从“等全部”变成“等第一个字”。
1. 首 token 延迟:\(T_{\text{TTFT}}\)(Time To First Token)是发请求到收到第一个 token 的时间,包含排队和模型首步计算。
2. 后续生成耗时:剩余 token 数除以生成速率,得到后续所有 token 输出的总耗时。
3. 感知延迟合计:两者相加就是用户从提问到看到完整答案的总时间,流式让等待变得“可感知地缩短”。
就像电话辅导老师边说边讲,你听到第一个字就安心了,而不是等寄来的整封信。LLM 配上 WebSocket,用户听到的不是“信到了”的敲门声,而是 AI 开口说话的第一个音节。
并发连接管理(总机调度)
公司前台有根总机线,能同时接很多电话。每个 WebSocket 连接也占一份资源:一个文件描述符、一块收发缓冲区、一份连接状态。当应用有十万用户同时在线聊天或与 LLM 对话,服务器必须像总机一样调度好每条线路。
并发管理的关键有三件:一是限制与监控,设定最大连接数,防止内存被耗尽;二是高效的数据结构,用连接管理器(Map 或哈希表)快速找到某个用户的连接,实现单播推送;三是生命周期清理,连接关闭或心跳超时后立刻释放资源,否则会出现“幽灵连接”吃内存。
断线重连也是并发下的刚需:客户端掉线后自动重连,并重新注册订阅(比如重新加入某个房间或会话),服务端要幂等处理,避免重复订阅导致重复推送。
1. 单连接内存开销:每个连接的缓冲区、消息队列、状态对象加起来的内存均值。
2. 可用内存预算:服务器进程允许用于连接的内存上限,扣除其他业务后剩余的部分。
3. 容量上限计算:用总预算除以单连接开销,得到理论上限,超出则拒绝新连接或触发扩容。
总机线路再多也有上限,懂调度的工程师才能保证“人人能打通、个个不占线”。WebSocket 的并发管理,本质上就是为每一条通话线路分配合理的资源,并在挂断后及时回收。
AI 应用场景
LLM 流式对话
阶段:模型推理与流式传输
解决的问题:传统 HTTP 请求-响应模式下,用户必须等 LLM 完整生成几百字才能看到结果,等待十几秒极易流失。AI 应用需要“边生成边显示”的流式体验。WebSocket 建立一次连接后,服务器把生成的 token 增量推送,并支持用户中途发送“停止”指令,实现互动的流式对话。
具体案例:某智能客服机器人接入大模型后,用户提问的响应从“转圈 8 秒”变为“1 秒出第一个字”。服务端用 WebSocket 把大模型流式输出的 token 实时推送到前端,用户看到文字逐字出现,还能点击“停止回答”打断生成,显著提升满意度。
多人在线协作白板
阶段:实时数据同步与状态广播
解决的问题:多人同时编辑同一张 AI 生成的白板/文档时,需要把每个人的操作实时同步给其他人。HTTP 轮询延迟高且浪费资源,WebSocket 的服务器主动推送能力让编辑器状态秒级同步,还支持二进制图片与光标坐标的高频传输。
具体案例:一款 AI 绘画协作工具,所有成员在同一块画布上作画,AI 实时生成作品。每个画笔动作、每个 AI 增量结果都通过 WebSocket 广播给房间内所有成员,延迟低于 100ms,体验如同面对面在白板上画。
实时监控告警推送
阶段:事件驱动与智能预警
解决的问题:运维监控系统需要在异常发生时第一时间通知值班人员,而不是等客户端来轮询。WebSocket 让服务器在算法检测到指标异常后,立刻主动推送告警详情到浏览器大屏或手机端,并支持多通道广播。
具体案例:某公司的大模型服务监控平台,用 WebSocket 连接每个运维人员的浏览器。当模型推理延迟 P99 超过阈值或 GPU 显存告急时,服务器立即推送包含日志摘要的告警卡片,运维人员可点击“一键拉起”按钮反向控制,形成闭环。
Python 演示
# -*- coding: utf-8 -*-"""mini_ws.py 迷你 WebSocket 服务器:演示握手与服务器主动推送"""import base64import hashlibimport socketimport structimport threadingimport time GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"clients = set() def accept_key(key): return base64.b64encode( hashlib.sha1((key + GUID).encode()).digest() ).decode() def encode_frame(text): payload = text.encode("utf-8") n = len(payload) if n < 126: header = bytes([0x81, n]) elif n < 65536: header = bytes([0x81, 126]) + struct.pack(">H", n) else: header = bytes([0x81, 127]) + struct.pack(">Q", n) return header + payload def decode_frame(data): if len(data) < 2: return None, data b1, b2 = data[0], data[1] opcode = b1 & 0x0F masked = b2 & 0x80 length = b2 & 0x7F off = 2 if length == 126: length = struct.unpack(">H", data[off:off + 2])[0] off += 2 elif length == 127: length = struct.unpack(">Q", data[off:off + 8])[0] off += 8 if masked: mask = data[off:off + 4] off += 4 raw = bytearray(data[off:off + length]) if masked: for i in range(len(raw)): raw[i] ^= mask[i % 4] return {"opcode": opcode, "payload": bytes(raw)}, data[off + length:] def broadcast(msg): for c in list(clients): try: c.sendall(encode_frame(msg)) except Exception: clients.discard(c) def handle(conn): data = conn.recv(65536) headers = {} for line in data.decode("iso-8859-1").split("\r\n")[1:]: if ": " in line: k, v = line.split(": ", 1) headers[k.lower()] = v key = headers.get("sec-websocket-key", "") resp = ( "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Accept: " + accept_key(key) + "\r\n\r\n" ) conn.sendall(resp.encode()) clients.add(conn) conn.sendall(encode_frame("连接已建立,服务器会主动推送消息")) buf = b"" while True: try: chunk = conn.recv(65536) except OSError: break if not chunk: break buf += chunk while buf: msg, buf = decode_frame(buf) if msg is None: break if msg["opcode"] == 8: broadcast("客户端离开") conn.close() return if msg["opcode"] == 1: broadcast("收到: " + msg["payload"].decode("utf-8")) clients.discard(conn) conn.close() def pusher(): while True: time.sleep(2) broadcast(time.strftime("服务器推送 %H:%M:%S")) def main(): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(("127.0.0.1", 8765)) server.listen(5) threading.Thread(target=pusher, daemon=True).start() print("WebSocket 服务器运行在 ws://127.0.0.1:8765") while True: try: conn, _ = server.accept() threading.Thread(target=handle, args=(conn,), daemon=True).start() except KeyboardInterrupt: break server.close() if __name__ == "__main__": main()
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"协议规定的固定魔数,握手计算 Accept 时与 Key 拼接。accept_key(key)实现 SHA-1 + Base64 的握手密钥计算:Base64(SHA-1(Key + GUID))。encode_frame(text)服务端发送帧的封装:0x81表示 FIN + 文本帧,第二个字节直接写长度(服务端不掩码)。decode_frame(data)解析客户端发来的帧:读取 opcode、掩码位、长度,并用 4 字节掩码逐字节异或还原数据。length == 126` 与 `struct.unpack(">H", ...)当消息超过 125 字节时,用 16 位扩展长度字段表示真实长度。if masked:` 分支对 `raw[i] ^= mask[i % 4]去掉客户端掩码,还原真正的 payload。broadcast(msg)遍历所有连接并发送消息,实现“服务器主动推送”;发送失败就从集合中丢弃该连接。resp` 中 `"HTTP/1.1 101 Switching Protocols\r\n"握手成功的标志,告诉浏览器“电话接通了”。pusher()` 中 `time.sleep(2); broadcast(...)模拟服务器 2 秒一次主动推送,验证 WebSocket 双向通信。threading.Thread(target=handle, ...)每个客户端连接一个线程,互不阻塞,是并发管理的极简原型。
常见误区
误区我听说 WebSocket 很先进,以为它跟我们学的 HTTP 完全没关系,是凭空发明的新协议。握手只是象征性地走个 HTTP 流程。
正解WebSocket 的握手完全建立在 HTTP 之上:客户端发一个 HTTP 升级请求(Upgrade: websocket),服务器返回 101 状态码,之后才切换到 WebSocket 帧协议。它就像一个“借电话线拨号”的过程——拨号用的是电信网络,接通后才是通话。没有 HTTP 的升级机制,WebSocket 连门都进不去。
为何常见:因为看到协议名不同、端口常用 ws://,且帧格式与 HTTP 报文差异巨大,容易误以为两者毫无关系。
误区既然做双向通信,服务器往客户端发数据帧,肯定也要像客户端一样加掩码,否则不安全。
正解RFC 6455 明确规定:客户端→服务器的帧必须掩码,服务器→客户端的帧禁止掩码。掩码不是加密,而是为了防止早期代理缓存投毒。就像打电话时只有你的话要经过一个防窃听滤波器,对方传回来的声音不需要再滤一遍。如果服务端错误地加了掩码,客户端反而会解析出乱码。
为何常见:很多人在看协议文档时只记住了“数据帧要掩码”,忽略了掩码的传输方向限制,写服务端代码时照搬客户端逻辑。
误区WebSocket 连接建立后就一直挂着,服务器和客户端都在线,反正不会断,所以没必要搞什么心跳。
正解连接虽然持久,但链路上可能布满 Nginx、负载均衡、云厂商网关,它们往往会在一段时间空闲后自动断开连接。没有心跳,服务器可能守着一条早已死亡的“僵尸连接”,消息发出去石沉大海。心跳的意义是定期“喂”一声,让对方和中间设备都知道这条线还活着。
为何常见:本地开发时没有中间代理,连接总是好好的,一上生产环境才发现空闲连接被网关回收,才意识到心跳的必要性。
误区我看到 WebSocket 既能推送又能接收,功能全面,那所有需要实时的场景都该优先选它,SSE 是过时技术。
正解如果场景只需要服务器单向推送(比如股票价格、日志流),SSE 更轻量:基于普通 HTTP 实现,自动重连机制成熟,兼容性好;WebSocket 的优势在于全双工交互(比如生成中随时打断 LLM)和二进制传输。微信语音和短信各有适用场景,不是越复杂的工具越好。
为何常见:学习时只看到 WebSocket 的宣传亮点,忽略了“单向推送”这类高频场景用 SSE 更省成本、更稳。
面试题
Q1. HTTP 与 WebSocket 最主要的区别是?
- A.WebSocket 基于 UDP 提供更快传输
- B.WebSocket 建立后支持双向通信← 正确答案
- C.WebSocket 无需任何网络连接
- D.WebSocket 只能传输二进制
答案:B。WebSocket 在握手后建立持久连接,客户端和服务器双方可以随时发送消息,实现真正的双向通信。A 错在 WebSocket 基于 TCP;C 显然错;D 错在 WebSocket 支持文本和二进制两种帧。打个比方:HTTP 是寄信,WebSocket 是打电话。
Q2. WebSocket 握手成功后,服务器返回的状态码是?
- A.200请求成功
- B.301永久重定向
- C.101协议切换成功← 正确答案
- D.404资源不存在
答案:C。握手响应使用 101 Switching Protocols,表示“从 HTTP 切换到 WebSocket 协议”。200 表示普通 HTTP 响应成功,301 是重定向,404 是资源未找到。就像电话拨号后听到“咔哒”一声接通,101 就是那声接通提示。
Q3. Sec-WebSocket-Accept 是由 Sec-WebSocket-Key 与什么拼接后哈希生成的?
- A.随机生成的临时串
- B.固定的魔数GUID串← 正确答案
- C.当前时间的时间戳
- D.服务端会话ID串
答案:B。RFC 6455 规定了一个固定 GUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11),服务器把客户端 Key 拼上该 GUID,做 SHA-1 哈希再 Base64 编码得到 Accept 值。使用固定魔数是为了让双方可验证,而不是随机值。就像接线员用统一的话术确认双方身份。
Q4. 关于 WebSocket 数据帧的掩码,下列说法正确的是?
- A.客户端发送必须加掩码← 正确答案
- B.服务器发送必须加掩码
- C.掩码使用 2 字节密钥
- D.掩码等同于加密保护
答案:A。协议规定客户端→服务器必须掩码(4 字节 Masking Key),服务器→客户端禁止掩码。掩码不是加密,而是防止代理缓存投毒。C 错在密钥是 4 字节,D 错在混淆了掩码与加密的关系。
Q5. WebSocket 连接生命周期中,用于探测对端是否存活的帧是?
- A.心跳探测帧← 正确答案
- B.心跳回复帧
- C.文本消息帧
- D.二进制数据帧
答案:A。Ping 帧(opcode 9)用于探测,对端收到后自动回复 Pong 帧(opcode 10),这就是“喂,还在吗?”的心跳机制。文本帧和二进制帧承载业务数据,不负责存活探测。心跳能及时发现死连接,避免推送石沉大海。
Q6. 关于 WebSocket 与 HTTP 轮询的对比,正确的有?
- A.轮询会产生大量无效请求← 正确答案
- B.WebSocket 只需一次握手升级← 正确答案
- C.WebSocket 支持服务器主动推送← 正确答案
- D.轮询的网络延迟一定更低
答案:ABC。轮询每次都要重复发起 HTTP 请求,大部分是空响应,开销大;WebSocket 一次握手后长连接复用,且支持服务器主动推送。D 错:轮询的延迟通常更高,因为服务器有新消息也要等客户端下一次来问,存在空白期。
Q7. 客户端发送 Close 帧请求关闭连接后,服务器应该怎么做?
- A.立即断开底层 TCP 连接
- B.回应 Close 帧后再关闭连接← 正确答案
- C.忽略 Close 继续收发消息
- D.发送 Ping 帧探测对方是否在线
答案:B。关闭握手是双方协作的:收到 Close 帧后应回一个 Close 帧,然后关闭 TCP 连接。立即断开会导致对端来不及处理残留数据;忽略请求则违反协议。就像先说“挂了”,等对方回“好”,再真正挂断电话。
Q8. 关于 WebSocket 分片传输(Fragmentation),下列说法正确的是?
- A.每个分片都必须带掩码
- B.控制帧不能插入分片之间
- C.分片只能用于二进制消息
- D.中间分片的结束标记必须为 0← 正确答案
答案:D。一个消息分多帧传输时,除最后一片外 FIN 均为 0,因此“中间分片 FIN=0”正确。掩码只看传输方向,不看分片;控制帧(如 Ping)可以插入分片流之间;分片同时支持文本和二进制。A、B、C 均与协议相悖。
Q9. 在 LLM 流式输出场景下,WebSocket 相比 SSE 的主要优势是?
- A.浏览器原生支持自动重连
- B.支持双向交互和二进制传输← 正确答案
- C.兼容所有 HTTP 代理网关
- D.不需要维护任何连接状态
答案:B。SSE 只能服务器向客户端单向推送,浏览器 EventSource 自带重连,但客户端无法通过同一通道发指令;WebSocket 是全双工,适合“生成中随时停止/追问”的交互,也能传二进制。A、C 是 SSE 的优势,D 对两者都不成立。
Q10. 关于 WebSocket 并发连接管理的正确做法有?
- A.限制最大连接数防内存耗尽← 正确答案
- B.用心跳检测连接是否失效← 正确答案
- C.断线自动重连恢复订阅状态← 正确答案
- D.连接关闭后及时清理释放资源← 正确答案
答案:ABCD。并发管理是一个闭环:限制容量(A)防止资源耗尽,心跳(B)及时淘汰无效连接,断线重连(C)保证业务连续性,最后还要清理资源(D)避免泄漏。四者缺一不可,就像总机既要限制线路总数,也要及时清理占线不挂的电话。