subprocess读取stdout阻塞问题及替代pwntools的实现方案
问题解答
代码文件展示
new.py
print("Gib number") x = input() print('numba is ', x)
launcher.py(存在问题的实现)
import subprocess as sp p = sp.Popen(['python', 'new.py'], stdout=sp.PIPE, stderr=sp.STDOUT, stdin=sp.PIPE, text=True) while True: line = p.stdout.readline() print('line is', line) p.stdin.write('10') p.wait()
_launcher.py(使用pwntools的实现)
from pwn import process p = process(['python', 'new.py'], stderr=process.PTY, stdout=process.PTY) while True: output = p.recv() print('line is', output) p.sendline(10) # Sends the answer with a newline p.wait()
1. 为什么launcher.py无法在第二次循环读取new.py的stdout?
核心问题有三个:
- 缺少换行符:
new.py的input()需要读取到换行符才会返回,而p.stdin.write('10')没有写入换行,导致子进程一直卡在input(),不会执行后续的print,自然没有新的stdout输出。 - 未刷新缓冲区:Python的
stdin管道默认有缓冲,write后没有调用flush(),数据不会立即发送到子进程,子进程持续等待输入,流程卡住。 - 子进程执行完毕后退出:即使解决前两个问题,
new.py执行完所有代码后会直接退出,此时子进程的stdout被关闭,第二次readline()会返回空字符串,循环只会持续打印空内容,不会有新输出。
2. pwntools的recv和sendline采用的机制
- sendline():
- 自动在发送内容末尾添加换行符,匹配
input()的读取要求; - 立即刷新输出缓冲区,确保数据即时发送到子进程;
- 配合
process.PTY(伪终端),让子进程认为自己运行在终端环境中,输出使用行缓冲而非管道默认的块缓冲,保证输出能被及时读取。
- 自动在发送内容末尾添加换行符,匹配
- recv():
- 主动读取子进程输出流中的所有可用数据(直到超时或EOF);
- 处理伪终端的缓冲逻辑,避免因缓冲导致的读取阻塞或遗漏;
- 自动管理子进程的文件描述符,处理输入输出的同步问题。
3. 不使用pwntools实现new.py的启动与通信
有两种可靠的实现方式:
方式一:使用pty模块模拟伪终端(推荐)
通过模拟终端环境解决缓冲问题,让子进程的输出行为和在终端中一致:
import subprocess as sp import pty import os # 创建伪终端主从设备 master, slave = pty.openpty() # 启动子进程,绑定伪终端的从设备作为输入输出 p = sp.Popen( ['python', 'new.py'], stdout=slave, stderr=slave, stdin=slave, text=True, close_fds=True ) os.close(slave) # 关闭不需要的从设备文件描述符 try: while True: # 读取子进程输出,最多读取1024字节 output = os.read(master, 1024).decode() if not output: # 子进程退出后,读取到空内容 break print('line is', repr(output)) # 发送带换行的输入,确保input()能识别 os.write(master, b'10\n') finally: p.wait() os.close(master)
方式二:手动处理缓冲与换行
通过设置行缓冲、添加换行、刷新缓冲区实现:
import subprocess as sp # 设置bufsize=1启用行缓冲 p = sp.Popen( ['python', 'new.py'], stdout=sp.PIPE, stderr=sp.STDOUT, stdin=sp.PIPE, text=True, bufsize=1 ) try: # 读取第一行输出 line = p.stdout.readline() if line: print('line is', line.strip()) # 发送带换行的输入并刷新缓冲区 p.stdin.write('10\n') p.stdin.flush() # 读取第二行输出 line = p.stdout.readline() if line: print('line is', line.strip()) finally: p.wait()
参考资料
- Python官方文档:
subprocess模块中Popen类的缓冲配置、管道通信说明 - Python官方文档:
pty模块的伪终端创建与使用说明 - 终端缓冲机制:行缓冲、块缓冲的差异与触发条件
内容的提问来源于stack exchange,提问作者Udeshya D.
相关产品推荐
相关产品推荐

