You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现WebSocket与Docker TTY的并发监听及浏览器CLI交互优化

解决WebSocket与TTY并发监听问题及优化浏览器CLI交互方案

一、FastAPI后端实现并发监听WebSocket与TTY

核心思路是利用Python asyncio 的协程并发能力,同时启动两个独立任务:一个持续读取TTY输出并推送到WebSocket,另一个持续接收WebSocket输入并写入TTY。彻底解决串行监听的超时问题,同时支持前端的中断操作。

关键实现步骤与代码示例

  1. 启动Docker交互式进程:确保容器开启TTY和标准输入,用asyncio.subprocess创建异步子进程(也可使用Docker异步SDK)。
  2. 并发读写任务:用asyncio.create_task分别启动读TTY、写TTY的协程,通过asyncio.gather等待任务完成。
  3. 处理中断信号:前端发送的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 17:52:52