NodeJS+WebSocket连接Docker容器Web终端无法输入命令求助
解决Web终端无法向Docker容器输入命令及支持交互式命令的问题
问题核心分析
当前代码存在三个关键问题导致输入无响应、交互式命令失效:
container.modem.demuxStream直接将Docker输出流导向Socket.IO的socket,但Socket.IO的socket并非标准Node.js可写流,数据传递逻辑不匹配- 未设置
StdinOnce: false,导致容器stdin在首次输入后自动关闭,无法持续接收输入 - 缺少终端尺寸同步机制,
vim、top等交互式命令依赖终端尺寸信息才能正常渲染
修改后端代码
const express = require('express'); const http = require('http'); const { Server } = require("socket.io"); const Docker = require('dockerode'); const docker = new Docker({ socketPath: '/var/run/docker.sock' }); const app = express(); const server = http.createServer(app); const io = new Server(server, { transports: ['websocket'], allowEIO3: true }); app.use(express.static('public')); io.on('connection', async (socket) => { console.log('A user connected'); let container = null; let stream = null; try { // 创建容器:添加StdinOnce保持stdin持续打开 container = await docker.createContainer({ Image: 'ubuntu', Cmd: ['/bin/bash'], AttachStdin: true, AttachStdout: true, AttachStderr: true, Tty: true, OpenStdin: true, StdinOnce: false // 关键参数:禁用stdin自动关闭 }); await container.start(); // 附加流:启用hijack和raw模式适配交互式终端 stream = await container.attach({ stream: true, stdin: true, stdout: true, stderr: true, hijack: true, raw: true }); // 转发Docker输出到前端 stream.on('data', (data) => { socket.emit('output', data); }); // 接收前端输入并写入Docker流 socket.on('input', (data) => { if (stream) { stream.write(Buffer.from(data)); } }); // 处理终端尺寸调整 socket.on('resize', (size) => { if (container) { container.resize({ h: size.rows, w: size.cols }).catch(err => console.error('Resize error:', err)); } }); // 流结束时清理容器 stream.on('end', async () => { if (container) { try { await container.stop(); await container.remove(); } catch (err) { console.error('Cleanup error:', err); } } socket.disconnect(); }); // 用户断开时清理容器资源 socket.on('disconnect', async () => { console.log('User disconnected'); if (container) { try { await container.stop(); await container.remove(); } catch (err) { console.error('Disconnect cleanup error:', err); } } stream?.destroy(); }); } catch (err) { console.error('Initialization error:', err); socket.emit('error', err.message); socket.disconnect(); } }); const port = 3000; server.listen(port, () => console.log(`Server running on port ${port}`));
前端配套代码(public/index.html)
<!DOCTYPE html> <html> <head> <title>Web Terminal</title> <style> #terminal { width: 800px; height: 600px; border: 1px solid #ccc; padding: 10px; font-family: monospace; white-space: pre; overflow-y: auto; } </style> </head> <body> <div id="terminal"></div> <script src="/socket.io/socket.io.js"></script> <script> const socket = io(); const terminal = document.getElementById('terminal'); // 解析后端二进制输出并渲染 socket.on('output', (data) => { terminal.textContent += new TextDecoder('utf-8').decode(data); terminal.scrollTop = terminal.scrollHeight; }); // 捕获键盘输入并发送到后端 document.addEventListener('keydown', (e) => { let input; switch(e.key) { case 'Enter': input = '\r\n'; e.preventDefault(); break; case 'Backspace': input = '\x7f'; break; case 'Tab': input = '\t'; e.preventDefault(); break; default: input = e.key; } socket.emit('input', input); }); // 发送终端尺寸信息 function sendTerminalSize() { const cols = Math.floor(terminal.offsetWidth / 8); const rows = Math.floor(terminal.offsetHeight / 16); socket.emit('resize', { cols, rows }); } window.addEventListener('resize', sendTerminalSize); sendTerminalSize(); // 处理错误提示 socket.on('error', (msg) => { terminal.textContent += `\nError: ${msg}\n`; }); </script> </body> </html>
关键修改说明
- 容器参数优化:添加
StdinOnce: false确保stdin持续打开,支持多次输入 - 流模式适配:启用
hijack: true和raw: true,匹配Docker交互式终端的流协议 - 数据传递逻辑:通过Socket.IO事件手动转发输入输出,适配非标准流的Socket.IO通信
- 终端尺寸同步:前端监听窗口变化,实时发送终端行列数,后端调用
container.resize适配交互式命令渲染 - 资源清理:在流结束、用户断开时强制停止并移除容器,避免资源泄漏
内容的提问来源于stack exchange,提问作者dennis
相关产品推荐
相关产品推荐

