ComfyUI WebSocket 实战:构建高效实时交互界面的技术解析
·
背景与痛点:轮询的“老毛病”
第一次把 ComfyUI 塞进网页里做实时预览,我图省事直接上了 setInterval:每 500 ms 发一次 GET,把画布状态拉回来。
结果本地调试挺欢,一上公网立刻翻车:
- 浏览器疯狂打转,F12 里全是 304
- 手机端发热,流量像漏水
- 后端日志 1 分钟刷出 3 万行,NGINX 直接 502
痛点一句话:轮询=假装实时,高延迟+高负载双杀。
WebSocket 正好反着来:一次握手,全双双工,服务器有消息再推,省流量、省 CPU、真·实时。

技术选型:REST 与 WebSocket 的“擂台赛”
| 维度 | REST(轮训) | WebSocket |
|---|---|---|
| 连接数 | 每次新建 TCP,+TLS 三次握手 | 一次 TCP,长驻内存 |
| 头部开销 | 每次 800 B+ | 帧头 2 B |
| 延迟 | ≥ 1 RTT × 轮询间隔 | ≤ 1 RTT |
| 服务端推送 | 做不到 | 天生支持 |
| 反向代理 | 无脑缓存 | 需配置 proxy_ws |
| 代码复杂度 | 低 | 中(要处理重连、心跳) |
结论:纯展示用 REST 够;只要带“实时”二字,WebSocket 就是底线。
核心实现:ComfyUI 里“长”出一条 WebSocket
下面代码基于 ComfyUI 0.2.0,Python 3.10,前端用原生 ES Module,无框架依赖。
目录结构:
comfyui-websocket-demo/
├── server/
│ ├── websocket_node.py // 自定义节点
│ └── ws_server.py // 独立 ws 服务
└── ui/
└── websocket_client.js // 页面脚本
1. 后端:独立 WebSocket 服务
# ws_server.py
import asyncio
import json
import websockets
from comfyui_execution_queue import prompt_queue # ComfyUI 内部队列
CLIENTS = set()
async def register(websocket):
CLIENTS.add(websocket)
await websocket.send(json.dumps({"type": "hello", "msg": "ComfyUI WS Ready"}))
async def unregister(websocket):
CLIENTS.discard(websocket)
async def handler(websocket):
await register(websocket)
try:
async for msg in websocket:
data = json.loads(msg)
if data["type"] == "prompt":
# 把前端传来的工作流塞进 ComfyUI 队列
prompt_id = prompt_queue.put(data["workflow"])
await websocket.send(json.dumps({"type": "queued", "id": prompt_id}))
except websockets.exceptions.ConnectionClosedOK:
pass
finally:
await unregister(websocket)
# 广播函数:ComfyUI 节点执行完会回调这里
async def broadcast_update(message: dict):
if CLIENTS:
await asyncio.gather(*(ws.send(json.dumps(message)) for ws in CLIENTS))
start_server = websockets.serve(handler, "0.0.0.0", 8188)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
2. 自定义节点:把执行结果推出去
# websocket_node.py
from nodes import NODE_CLASS_MAPPINGS
from ws_server import broadcast_update
class WebSocketSender:
@classmethod
def INPUT_TYPES(cls):
return {"required": {"images": ("IMAGE",)}}
RETURN_TYPES = ()
FUNCTION = "send"
CATEGORY = "websocket"
def send(self, images):
# 取第一张图转 base64
from PIL import Image
import base64, io
img = Image.fromarray(images[0].cpu().numpy())
buf = io.BytesIO()
img.save(buf, format="PNG")
b64 = base64.b64encode(buf.getvalue()).decode()
asyncio.run(broadcast_update({"type": "preview", "payload": bgt}))
return {}
NODE_CLASS_MAPPINGS["WebSocketSender"] = WebSocketSender
把上面两个文件扔进 custom_nodes,重启 ComfyUI,WS 服务就挂在 8188 端口。
3. 前端:两行代码重连,三行代码渲染
<!-- index.html 片段 -->
<canvas id="preview"></canvas>
<script type="module" src="ui/websocket_client.js"></script>
// websocket_client.js
const canvas = document.getElementById('preview');
const ctx = canvas.getContext('2d');
const wsUrl = `ws://${location.hostname}:8188`;
let ws, reconnectTimer;
function connect() {
ws = new WebSocket(wsUrl);
ws.binaryType = 'blob';
ws.onopen = () => console.log('[WS] 握手成功');
ws.onmessage = (e) =>厚实的知识点内容必须丰富到1000字左右及以上,绝不允许内容偏少。如原文内容不满,请主动结合实际经验、相关原理、应用场景等多角度补充和拓展,确保内容完整详实,表达清晰。
鼓励将中间内容分为多个小段落展示,每段可采用“1. 2. 3. 4. ...”等序号进行markdown格式的分点描述。每个点可聚焦一个具体细节、要点或流程,包括但不限于:项目背景、主要功能实现、关键步骤讲解、遇到的难点及应对、常见问题分析、优化思路、实际应用案例、经验总结和拓展方向等。通过分点方式,将复杂流程细致拆分,便于补充和扩充字数,让内容更加结构化且易于理解。
如发现内容长度明显不足,可以从如下维度适当增加说明:任务背景和意义、功能划分和实现逻辑、每一步的操作思路、常见易错点与解决方法、相关技术的经验分享、实际应用中遇到的案例或收获、优化建议和未来可能拓展点等,力求让新手也能跟随说明掌握核心流程与知识点。
每一个分段建议主题清晰,按问题或环节“一段一事”展开叙述,便于读者逐步消化和理解,避免无序堆砌。
用通俗直白、易于理解的语言,讲清楚整个实现过程、背后的思路、涉及的重点细节,不要过度理论化,内容以实用为主。
所有描述须聚焦实际操作、项目实现、经验与总结,突出“原文亮点+实践体会”与“平台快速落地思路”两大核心,帮助内容自然扩展到1000字左右。
[](https://t.csdnimg.cn/Y21s)
---更多推荐


所有评论(0)