如何实现WebSocket与Docker TTY的并发监听及浏览器CLI交互优化
解决WebSocket与TTY并发监听问题及优化浏览器CLI交互方案
一、FastAPI后端实现并发监听WebSocket与TTY
核心思路是利用Python asyncio 的协程并发能力,同时启动两个独立任务:一个持续读取TTY输出并推送到WebSocket,另一个持续接收WebSocket输入并写入TTY。彻底解决串行监听的超时问题,同时支持前端的中断操作。
关键实现步骤与代码示例
- 启动Docker交互式进程:确保容器开启TTY和标准输入,用
asyncio.subprocess创建异步子进程(也可使用Docker异步SDK)。 - 并发读写任务:用
asyncio.create_task分别启动读TTY、写TTY的协程,通过asyncio.gather等待任务完成。 - 处理中断信号:前端发送的
Ctrl+C对应ASCII码\x03,后端捕获后向Docker进程发送SIGINT信号。
import asyncio import signal from fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI() @app.websocket("/ws/cli/{container_id}") async def websocket_cli(websocket: WebSocket, container_id: str): await websocket.accept() # 启动Docker交互式bash进程(异步方式) try: proc = await asyncio.create_subprocess_exec( "docker", "exec", "-it", container_id, "/bin/bash", stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT, start_new_session=True # 允许向进程组发送信号 ) except Exception as e: await websocket.send_text(f"启动容器终端失败: {str(e)}") await websocket.close() return # 持续读取TTY输出并发送给前端 async def read_tty_output(): while True: data = await proc.stdout.read(1024) if not data: await websocket.send_text("\n[进程已退出]") break # 处理编码异常,避免崩溃 await websocket.send_text(data.decode("utf-8", errors="replace")) # 持续接收前端输入并写入TTY async def write_tty_input(): while True: try: data = await websocket.receive_text() # 处理Ctrl+C信号 if data == "\x03": proc.send_signal(signal.SIGINT) continue # 写入输入到进程标准输入 await proc.stdin.write(data.encode("utf-8")) await proc.stdin.drain() except WebSocketDisconnect: break # 创建并发任务 read_task = asyncio.create_task(read_tty_output()) write_task = asyncio.create_task(write_tty_input()) try: await asyncio.gather(read_task, write_task) except Exception: pass finally: # 清理资源 read_task.cancel() write_task.cancel() proc.terminate() await proc.wait() await websocket.close()
注意事项
- 编码处理:TTY输出可能包含非UTF-8字符,需添加错误替换避免崩溃。
- 进程信号:
start_new_session=True确保能向整个进程组发送信号,避免只杀死子进程而无法中断主进程。 - 连接保活:可添加WebSocket心跳机制,避免长时间无通信被网关断开。
二、更优的浏览器CLI交互方案
1. 前端使用专业终端库
直接使用xterm.js,它支持完整的终端特性:
- 自动处理光标移动、颜色输出、Tab补全、快捷键(如
Ctrl+C)。 - 提供终端尺寸调整、滚动等交互能力,比自定义输入框体验好得多。
2. 后端使用成熟工具(无需从零开发)
如果不想自己实现WebSocket+TTY逻辑,可直接使用ttyd:
- 它是开源的Web终端工具,基于WebSocket,支持直接连接Docker容器、本地命令行。
- 只需一行命令即可启动:
ttyd docker exec -it <container_id> /bin/bash,无需编写后端代码。
3. 自定义开发优化
若坚持自主开发,可结合:
- 前端
xterm.js处理终端渲染与输入捕获。 - 后端用Docker异步SDK(而非直接调用
docker exec),更精准控制容器的exec会话,支持获取终端尺寸、调整窗口大小等高级特性。
内容的提问来源于stack exchange,提问作者Farel Musyaffa
相关产品推荐
相关产品推荐

