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

Docker容器中跨脚本访问子进程无响应问题求助

问题:Docker Attach 向游戏服务器子进程发送命令无响应

我用Script 1作为Docker容器的入口点,通过asyncio.create_subprocess_exec启动游戏服务器子进程,同时监听控制台输入维持服务器运行。另外有Script 2作为API,接收外部命令后通过docker attach尝试向该子进程的stdin写入命令。API返回200状态码,但子进程完全无响应;手动执行docker attach并输入命令时,子进程响应正常。怀疑和PTY(伪终端)有关,寻求帮助。

Script 1 核心代码

import asyncio
import aioconsole
import sys

def console_print(msg):
    # 自定义打印函数
    print(msg)

async def running_server_inpipe(game_server: asyncio.subprocess.Process):
    while True:
        line = await aioconsole.ainput("Enter Command: ")
        game_server.stdin.write(line.encode('utf-8') + b"\n")
        await game_server.stdin.drain()
        console_print("INPIPE - Command Sent: " + line)

async def running_server_outpipe(game_server: asyncio.subprocess.Process):
    async for out_line in game_server.stdout:
        console_print("OUTPIPE - Server Says: " + out_line.decode('utf-8', 'replace'))

async def running_server_errpipe(game_server: asyncio.subprocess.Process):
    async for err_line in game_server.stderr:
        console_print("ERRORPIPE - Server Error: " + err_line.decode('utf-8', 'replace'))

async def main():
    run_command = ["your-game-server-command"]  # 替换为实际启动命令
    server_start_string = "Server started"  # 替换为服务器启动完成的标识字符串

    game_server = await asyncio.create_subprocess_exec(*run_command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE)
    async for startup_out_line in game_server.stdout:
        console_print(startup_out_line.decode())
        if server_start_string in startup_out_line.decode('utf-8', 'replace'):
            print("Server Started")
            break

    async with asyncio.TaskGroup() as tg:
        in_task = tg.create_task(running_server_inpipe(game_server))
        out_task = tg.create_task(running_server_outpipe(game_server))
        err_task = tg.create_task(running_server_errpipe(game_server))
        retcode = await game_server.wait()
        if retcode == 0:
            print("Server exited normally")
        else:
            print(f"Error: server exited with code {retcode}")
        in_task.cancel()
        out_task.cancel()
        err_task.cancel()
        
    sys.exit(0 if retcode == 0 else 1)

if __name__ == "__main__":
    asyncio.run(main())

Script 2 核心代码

from flask import Flask, request, jsonify
import subprocess

API1 = Flask(__name__)

@API1.route('/quit', methods=['POST'])
def receive_quit_data():
    data = request.get_json()
    try:
        server = data['server']
    except KeyError:
        return jsonify({'status': 'No Server Provided'})
    engage_container = ["docker", "attach", f"{server}"]
    container_process = subprocess.Popen(engage_container, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, text=True)
    container_process.communicate(input="quit\n")
    container_process.wait()
    if container_process.return_code == 0:
        return jsonify({'status': 'Command Sent'})
    return jsonify({'status': 'Command Failed'})

if __name__ == "__main__":
    API1.run()

问题原因

手动执行docker attach时,Docker会自动为容器分配PTY(伪终端),游戏服务器的终端会话能识别来自PTY的输入。但通过subprocess.Popen调用docker attach时,默认不会分配PTY,导致输入的命令无法被服务器识别。同时,Script 1中启动游戏服务器时未分配PTY,也会让服务器忽略非终端来源的输入。

修复方案

方案1:修改Script 2,强制分配PTY

在调用docker attach时添加-it参数,并让subprocess使用PTY作为标准输入:

engage_container = ["docker", "attach", "-it", f"{server}"]
container_process = subprocess.Popen(
    engage_container,
    stdout=subprocess.DEVNULL,
    stderr=subprocess.DEVNULL,
    stdin=subprocess.PTY,
    text=True
)

-it参数强制Docker分配PTY,stdin=subprocess.PTY让subprocess模拟终端输入环境。

方案2:修改Script 1,为游戏服务器分配PTY

启动游戏服务器时通过PTY创建终端会话,确保服务器能接收终端输入:

import pty
import os
import asyncio

async def main():
    run_command = ["your-game-server-command"]
    server_start_string = "Server started"

    # 创建PTY主从设备
    master_fd, slave_fd = pty.openpty()
    # 启动子进程,绑定slave端作为标准输入输出
    game_server = await asyncio.create_subprocess_exec(
        *run_command,
        stdin=slave_fd,
        stdout=slave_fd,
        stderr=slave_fd,
        start_new_session=True
    )
    os.close(slave_fd)  # 关闭slave端,子进程已继承

    # 将master_fd包装为asyncio流
    reader, writer = await asyncio.open_unix_connection(fd=master_fd)

    # 读取启动输出
    while True:
        line = await reader.readline()
        if not line:
            break
        output = line.decode('utf-8', 'replace')
        console_print(output)
        if server_start_string in output:
            print("Server Started")
            break

    async def running_server_inpipe():
        while True:
            line = await aioconsole.ainput("Enter Command: ")
            writer.write((line + "\n").encode('utf-8'))
            await writer.drain()
            console_print("INPIPE - Command Sent: " + line)

    async def running_server_outpipe():
        while True:
            line = await reader.readline()
            if not line:
                break
            console_print("OUTPIPE - Server Says: " + line.decode('utf-8', 'replace'))

    async with asyncio.TaskGroup() as tg:
        in_task = tg.create_task(running_server_inpipe())
        out_task = tg.create_task(running_server_outpipe())
        retcode = await game_server.wait()
        if retcode == 0:
            print("Server exited normally")
        else:
            print(f"Error: server exited with code {retcode}")
        in_task.cancel()
        out_task.cancel()
        
    sys.exit(0 if retcode == 0 else 1)

这种方式让游戏服务器运行在PTY环境中,无论输入来自手动docker attach还是API调用的docker attach,都能被识别。

方案3:替代方案——使用docker exec而非docker attach

如果游戏服务器支持通过向进程标准输入写入命令,可直接用docker exec在容器内执行写入操作,无需依赖PTY交互:

# Script 2中替换engage_container和执行逻辑
engage_container = ["docker", "exec", f"{server}", "sh", "-c", "echo 'quit' > /proc/$(pidof your-game-server-process)/fd/0"]
subprocess.run(engage_container, check=True)
return jsonify({'status': 'Command Sent'})

需要替换your-game-server-process为游戏服务器进程的名称,确保能通过pidof找到进程ID。


内容的提问来源于stack exchange,提问作者R4pt0r

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 04:06:04