ComfyUI Websocket实时通信详解:从进度条显示到图片返回的全流程解析

📅 发布时间:2026/7/11 11:56:13 👁️ 浏览次数:
ComfyUI Websocket实时通信详解:从进度条显示到图片返回的全流程解析
ComfyUI Websocket实时通信详解从进度条显示到图片返回的全流程解析如果你正在构建一个需要与ComfyUI深度集成的交互式AI创作工具那么仅仅通过简单的HTTP API轮询获取最终结果体验上总感觉隔了一层。用户点击生成后只能盯着一个静态的加载图标不知道任务进行到哪一步是卡住了还是在正常计算生成的图片何时能返回这种不确定性在需要快速迭代和即时反馈的场景下尤其影响效率。这正是Websocket通信大显身手的地方。它像一条双向、实时的数据管道能将ComfyUI内部工作流的每一个心跳——从节点开始执行、采样进度推进到最终图像数据的流出——都即时地推送到你的前端界面或后端服务中。本文将彻底拆解这套机制不仅告诉你如何接收进度和图片更会深入探讨如何设计一个健壮、可扩展的客户端架构以应对从单次生成到批量调用的各种复杂场景。1. 理解ComfyUI的通信双通道HTTP与Websocket的分工在深入代码之前我们必须先厘清ComfyUI对外提供服务的两种核心方式HTTP API和Websocket。它们并非替代关系而是各司其职共同构成了完整的远程交互能力。HTTP API扮演着“指令下达者”和“元数据查询者”的角色。它是一个典型的请求-响应模型非常适合执行离散的、一次性的操作。当你需要提交一个新的生成任务、查询服务器状态、获取历史记录或是上传一个工作流定义时使用HTTPPOST或GET请求是最直接的方式。例如向/prompt端点提交一个包含完整工作流节点数据的JSON对象就是启动一次生成任务的入口。import requests import json def queue_prompt(workflow_json, server_address127.0.0.1:8188, client_idmy_app): 通过HTTP API提交生成任务到队列 payload { prompt: workflow_json, client_id: client_id } response requests.post(fhttp://{server_address}/prompt, jsonpayload) if response.status_code 200: return response.json() # 通常包含 prompt_id else: raise Exception(f提交失败: {response.status_code}, {response.text}) # 示例加载一个保存的工作流文件并提交 with open(my_workflow_api.json, r) as f: workflow json.load(f) result queue_prompt(workflow) prompt_id result[prompt_id] print(f任务已提交ID: {prompt_id})而Websocket则是“状态监听者”和“数据流接收者”。一旦通过HTTP API启动了任务Websocket连接就成为了你监听该任务生命周期的耳朵和眼睛。这是一个持久化的、全双工的连接服务器可以主动向客户端推送消息无需客户端反复轮询。这对于需要实时性的数据如生成进度、中间结果、最终的高清图像二进制流是唯一高效的方案。两者的协作流程可以概括为建立Websocket连接客户端首先连接到ComfyUI的Websocket端点例如ws://127.0.0.1:8188/ws并携带一个唯一的client_id。这个连接将在整个会话期间保持。通过HTTP提交任务使用上述的queue_prompt方法将工作流定义提交到服务器。此操作会返回一个prompt_id。通过Websocket监听在已建立的Websocket连接上客户端开始接收服务器推送的、与上述client_id和prompt_id相关的所有消息。处理流式事件客户端解析Websocket消息根据消息类型status,executing,progress,executed,execution_cached等更新UI状态、进度条并捕获输出的图像数据。提示client_id是连接Websocket和关联HTTP请求的关键。确保在HTTP请求和Websocket连接URL中使用相同的client_id服务器才能将正确的消息路由到你的连接。下面的表格清晰地对比了这两种通信方式的核心差异特性HTTP APIWebsocket通信模型请求-响应无状态持久连接全双工服务器可主动推送主要用途提交任务、查询信息、管理队列实时监听任务状态、接收进度、获取流式输出如图片数据时效性延迟较高依赖轮询实时事件驱动连接开销每次请求新建连接一个长连接承载多次消息适用场景任务触发、配置修改、结果获取非实时进度更新、实时预览、大文件图片/音频流式传输理解这个分工是设计任何ComfyUI集成应用的基础。接下来我们将聚焦于Websocket看看这条数据管道里究竟流淌着哪些信息。2. 解码Websocket消息从状态更新到图像数据流成功建立Websocket连接后你会收到一系列JSON格式的消息。这些消息是理解ComfyUI内部运作的窗口。每种消息类型都承载着特定的信息我们需要像解析协议一样处理它们。2.1 核心消息类型及其含义ComfyUI通过Websocket发送的消息其JSON根对象通常包含一个type字段和一个data字段。以下是最常见且关键的几种类型status: 服务器整体状态更新。例如队列中剩余任务数 (queue_remaining)。这对于显示全局负载非常有用。{type: status, data: {status: {exec_info: {queue_remaining: 3}}, sid: some-session-id}}execution_start/executing: 标志着某个节点开始执行。data中包含了节点ID (node) 和所属任务的ID (prompt_id)。这是更新当前“正在处理哪个节点”UI提示的理想时机。{type: executing, data: {node: 10, prompt_id: uuid-1234-..., display_node: 10}}progress:进度条的核心数据源。它提供了某个节点内部执行的细化进度。data中包含value当前值和max最大值通常对应着采样步数。node字段告诉你哪个节点在报告进度。{type: progress, data: {value: 15, max: 20, prompt_id: uuid-1234-..., node: 10}}executed: 一个节点执行完毕。data中包含该节点的输出信息。对于图片输出节点如SaveImage或SaveImageWebsocket这里是获取图片元数据如文件名的地方但图片二进制数据本身通常通过另一种方式传递见下文。{ type: executed, data: { node: 23, prompt_id: uuid-1234-..., output: {images: [{filename: ComfyUI_00001.png, subfolder: , type: output}]} } }execution_cached: 通知客户端哪些节点的输出结果是从缓存中读取的无需重新计算。这有助于理解执行流程。execution_error: 节点执行出错。data中包含错误详情和节点信息是进行错误处理和用户提示的关键。二进制消息: 当工作流中使用SaveImageWebsocket节点时生成的图片会以二进制帧的形式直接通过Websocket连接发送。这不是JSON文本而是原始的图片字节流。消息的前8个字节是一个标识头后续才是真正的图片数据如PNG格式的字节。这是实现图片“实时返回”的秘诀。2.2 构建消息处理器一个实战框架一个健壮的消息处理器需要能区分文本消息和二进制消息并根据type进行路由分发。下面是一个基础但功能完整的处理器类示例import json import websocket from threading import Thread, Event from queue import Queue from PIL import Image import io class ComfyUIWebsocketClient: def __init__(self, server_address127.0.0.1:8188, client_idNone): self.ws_url fws://{server_address}/ws self.client_id client_id or default_client self.ws None self.connected Event() self.message_queue Queue() self._image_callbacks {} # 注册图片回调node_id - callback_function self._progress_callbacks {} # 注册进度回调 def connect(self): 建立Websocket连接并启动接收线程 full_url f{self.ws_url}?clientId{self.client_id} self.ws websocket.WebSocketApp( full_url, on_openself._on_open, on_messageself._on_message, on_errorself._on_error, on_closeself._on_close ) # 在后台线程中运行Websocket self.ws_thread Thread(targetself.ws.run_forever) self.ws_thread.daemon True self.ws_thread.start() # 等待连接建立 if not self.connected.wait(timeout10): raise ConnectionError(无法在10秒内连接到Websocket服务器) def _on_open(self, ws): print(fWebsocket连接已建立 (client_id: {self.client_id})) self.connected.set() def _on_message(self, ws, message): # 处理二进制消息图片 if isinstance(message, bytes): self._handle_binary_message(message) return # 处理文本消息JSON try: data json.loads(message) msg_type data.get(type) msg_data data.get(data, {}) # 将消息放入队列供主逻辑消费 self.message_queue.put((msg_type, msg_data)) # 同时根据类型触发即时回调 if msg_type progress: node msg_data.get(node) if node and node in self._progress_callbacks: self._progress_callbacks[node](msg_data.get(value, 0), msg_data.get(max, 1)) except json.JSONDecodeError as e: print(f无法解析JSON消息: {e}\n原始消息: {message[:200]}) def _handle_binary_message(self, data: bytes): 处理来自SaveImageWebsocket节点的二进制图片数据。 ComfyUI发送的二进制消息前8字节是头部之后是图片字节。 if len(data) 8: return # 假设头部包含节点ID信息实际可能需要根据你的工作流调整解析逻辑 # 一种常见做法是在收到executed消息得知哪个节点输出了图片后 # 紧接着的二进制消息就对应那张图片。 image_bytes data[8:] try: image Image.open(io.BytesIO(image_bytes)) # 这里可以触发一个全局的图片接收回调或者根据上下文关联到具体节点 if hasattr(self, on_image_received): self.on_image_received(image) except Exception as e: print(f解析图片数据失败: {e}) def _on_error(self, ws, error): print(fWebsocket错误: {error}) def _on_close(self, ws, close_status_code, close_msg): print(fWebsocket连接关闭: {close_status_code} - {close_msg}) self.connected.clear() def register_image_callback(self, node_id, callback): 注册特定节点的图片接收回调函数 self._image_callbacks[node_id] callback def register_progress_callback(self, node_id, callback): 注册特定节点的进度更新回调函数 self._progress_callbacks[node_id] callback def get_next_message(self, blockTrue, timeoutNone): 从内部队列获取下一条消息用于主循环处理 try: return self.message_queue.get(blockblock, timeouttimeout) except Queue.Empty: return None这个框架将Websocket的异步接收与应用程序的主逻辑解耦。消息被接收后放入队列你的主程序可以以同步或异步的方式从队列中取出并处理。同时通过回调函数机制你可以为感兴趣的节点如采样器节点绑定具体的UI更新函数。3. 实现实时进度监控超越简单的进度条有了消息处理器实现一个进度条在技术上已经很简单监听progress消息用value/max更新UI组件。但一个面向高端用户的交互式工具其进度监控应该提供更丰富的上下文和信息密度。3.1 多节点进度追踪与聚合复杂的工作流可能包含多个耗时节点例如多个串联的KSampler或一个高清修复Hires Fix流程。简单的全局进度条会显得模糊。更好的做法是节点识别在收到executing消息时不仅记录当前节点ID还可以通过之前提交的prompt工作流JSON查询该节点的_meta信息或class_type获取一个对人类友好的节点名称如“基础文生图采样”、“面部细节修复”。进度关联将后续收到的progress消息与当前正在执行的节点关联起来。多进度条显示在UI上为每个活跃的耗时节点显示一个独立的进度条或者在一个主进度条上方用文字提示当前阶段。# 在消息处理循环中 msg_type, msg_data client.get_next_message() if msg_type executing: node_id msg_data.get(node) if node_id and node_id in prompt_graph: # prompt_graph是提交的工作流数据 node_info prompt_graph[node_id] node_name node_info.get(_meta, {}).get(title) or node_info.get(class_type, fNode {node_id}) print(f[阶段开始] {node_name}) # 更新UI设置当前阶段标题并重置该阶段的进度条 ui_set_current_stage(node_name) elif msg_type progress: node_id msg_data.get(node) current_value msg_data.get(value, 0) max_value msg_data.get(max, 1) # 更新对应node_id的进度条UI ui_update_progress_bar(node_id, current_value, max_value)3.2 预估剩余时间与队列感知单纯的进度百分比价值有限。结合status消息中的queue_remaining我们可以提供更强大的状态提示队列等待提示queue_remaining大于0时提示用户“前面还有X个任务在排队”。阶段时间预估记录每个节点从第一个progress消息到最后一个progress消息的时间差。经过几次运行后可以建立一个简单的历史数据库用于预估类似节点未来的执行时间。虽然AI生成时间波动较大但提供一个基于历史的范围如“预计还需30-50秒”能极大提升体验。智能跳过如果检测到某个节点的progress长时间不更新结合超时机制可以主动向用户提示可能遇到的问题甚至提供“取消任务”的选项。注意时间预估需要谨慎处理避免给用户绝对化的承诺。最好以“基于历史数据的估算”这样的表述呈现并随着任务进行动态修正。4. 高效接收与处理流式图像数据SaveImageWebsocket节点是实时获取图片的关键。与将图片保存到磁盘再通过HTTP下载相比它通过Websocket直接推送二进制数据延迟极低。4.1 配置工作流以使用SaveImageWebsocket在你的ComfyUI工作流中需要将最终的图像输出节点替换为SaveImageWebsocket。这个节点在ComfyUI管理器如ComfyUI-Manager中通常可以安装。它的配置非常简单通常不需要任何输入它会自动接收上游节点的图像输入并通过Websocket连接发送出去。关键点确保你的Websocket客户端代码能够将接收到的二进制图片数据与正确的生成任务prompt_id和输出节点关联起来。一种可靠的模式是在收到某个节点的executed消息且其class_type为SaveImageWebsocket时记录下这个node_id。随后紧接着收到的二进制消息bytes类型极有可能就是该节点输出的图像。使用这个关联关系将图片数据分派到正确的处理逻辑例如显示在对应任务预览窗口。4.2 处理多张图片与批量生成一个工作流可能输出多张图片如批量生成4张SaveImageWebsocket节点会为每张图片发送一个独立的二进制消息。你的客户端需要能够按顺序收集它们。对于批量调用连续提交多个任务情况更复杂一些。多个任务的Websocket消息会交织在同一个连接中。因此prompt_id成为了区分消息属于哪个任务的唯一标识符。你的消息处理器必须为每个prompt_id维护独立的状态上下文。class TaskContext: def __init__(self, prompt_id): self.prompt_id prompt_id self.current_node None self.received_images [] # 存储该任务收到的图片 self.progress {} # 存储各节点进度 self.completed Event() class BatchWebsocketHandler: def __init__(self, ws_client): self.ws_client ws_client self.tasks {} # prompt_id - TaskContext self.ws_client.on_image_received self._generic_image_handler def register_task(self, prompt_id): 注册一个新任务开始追踪其上下文 self.tasks[prompt_id] TaskContext(prompt_id) def _generic_image_handler(self, image): 收到图片时尝试将其关联到当前正在执行的任务 # 这里需要一个策略来确定图片属于哪个任务。 # 一个简单但并非绝对可靠的策略是图片属于最近收到executing消息且节点是SaveImageWebsocket的那个任务的prompt_id。 # 更稳健的做法需要自定义消息协议或在图片二进制头中嵌入ID。 for prompt_id, ctx in self.tasks.items(): if ctx.current_node and ctx.current_node.class_type SaveImageWebsocket: ctx.received_images.append(image) print(f任务 {prompt_id[:8]}... 收到一张图片) break def process_messages(self): 主处理循环 while True: msg self.ws_client.get_next_message(timeout1) if not msg: continue msg_type, msg_data msg prompt_id msg_data.get(prompt_id) if not prompt_id or prompt_id not in self.tasks: # 可能是全局status消息或未注册的任务简单处理或忽略 if msg_type status: print(f队列剩余任务: {msg_data.get(status,{}).get(exec_info,{}).get(queue_remaining, 0)}) continue ctx self.tasks[prompt_id] if msg_type executing: ctx.current_node msg_data.get(node) if ctx.current_node is None: # node为None表示整个prompt执行完毕 ctx.completed.set() print(f任务 {prompt_id[:8]}... 执行完成) elif msg_type progress: node msg_data.get(node) if node: ctx.progress[node] (msg_data.get(value), msg_data.get(max)) # ... 处理其他消息类型4.3 性能优化与错误处理图像解码异步化将接收到的图片字节流解码为PIL Image或numpy数组的操作可能比较耗时尤其是在高分辨率下。务必在独立的线程或异步任务中进行解码和后续的UI更新避免阻塞Websocket消息接收线程。连接重连与状态恢复网络可能不稳定。你的客户端应该监测Websocket连接状态在意外断开时尝试重连。重连后需要重新订阅或查询正在运行的任务状态ComfyUI的HTTP API提供了/history和/queue端点来获取这些信息。内存管理在批量生成或生成高分辨率图片时流式图像数据可能占用大量内存。及时处理如显示后释放或保存到磁盘接收到的图片对象避免内存泄漏。5. 构建完整的客户端应用从原型到生产将以上所有部分组合起来我们可以勾勒出一个面向生产的ComfyUI实时客户端架构。核心组件配置管理器管理服务器地址、默认工作流模板、客户端ID生成策略。HTTP API客户端封装queue_prompt,get_history,interrupt等RESTful调用。Websocket客户端即上文构建的ComfyUIWebsocketClient负责长连接管理和原始消息接收。消息派发与状态机核心逻辑层。它解析Websocket消息根据prompt_id将事件派发给对应的任务实例。每个任务实例是一个状态机跟踪该任务从“排队”到“执行中”再到“完成/失败”的全过程并管理其相关的进度数据和输出结果。UI层/业务逻辑层根据状态机的事件更新用户界面如更新进度条、显示图片或触发后续业务逻辑如自动保存图片到指定目录、调用后处理API。一个简化的主流程可能如下# 伪代码展示核心逻辑流 def main(): # 1. 初始化 config load_config() http_client ComfyUIHttpClient(config.server) ws_client ComfyUIWebsocketClient(config.server, generate_client_id()) ws_client.connect() task_dispatcher TaskDispatcher(ws_client) # 2. 加载工作流模板并设置参数 base_workflow load_workflow_template(txt2img.json) prompt_text a beautiful landscape # ... 修改workflow中的节点参数 # 3. 提交任务 task_future task_dispatcher.submit_task(base_workflow) # submit_task 内部会 # a. 生成唯一的prompt_id占位符或使用http_client.queue_prompt # b. 创建一个TaskContext对象并注册到dispatcher # c. 通过http_client提交任务 # 4. 监听任务完成阻塞或异步回调 # 方式A: 阻塞等待 final_images task_future.result(timeout300) # 等待5分钟 for img in final_images: img.show() # 方式B: 异步回调 # task_future.add_done_callback(lambda f: process_images(f.result())) if __name__ __main__: main()在实际开发中你可能会使用像asyncio和websockets库来处理高并发连接或者使用PyQt、Tkinter、Streamlit甚至Web前端通过JavaScript的WebSocket来构建图形界面。关键在于理解事件流HTTP请求 - Websocket事件 - 状态更新 - UI反馈并设计出清晰解耦的模块。最后别忘了异常处理。网络超时、服务器错误、工作流节点执行失败、收到的消息格式不符预期……这些都需要在代码中有妥善的应对给用户清晰的错误反馈并在可能时提供恢复选项如重试任务。