使用标准输入和标准输出在 2 个进程之间进行通信

Posted

技术标签:

【中文标题】使用标准输入和标准输出在 2 个进程之间进行通信【英文标题】:comunicate between 2 processes with stdin and stdout 【发布时间】:2014-10-20 11:17:26 【问题描述】:

我想编写一个执行外部脚本 (B) 的简单脚本 (A)

A 应该通过写入其标准输入并读取其标准输出来与 B 通信 B 应该读取它的标准输入并打印出来

所有这些都应该在不关闭流的情况下完成

A.py

import subprocess
process = subprocess.Popen(['python', 'B.py'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
for _ in range(3):
    process.stdin.write(b'hello')
    print(process.stdout.read())

B.py

import sys

for line in sys.stdin:
    print(line)

输出应该是:

>>> b'hello'
>>> b'hello'
>>> b'hello'

问题是A只是在等待

print(process.stdout.read())

如果我修改 A,通过添加 close():

for _ in range(3):
    process.stdin.write(b'hello')
    process.stdin.close()
    print(process.stdout.read())

我明白了:

>>> b'hello\n'
>>> Traceback (most recent call last):
>>>   File "A.py", line 7, in <module>
>>>     process.stdin.write(b'hello')
>>> ValueError: write to closed file

【问题讨论】:

【参考方案1】:

使用communicate()

Python 已经实现了communicate() 方法(它转到A.pyB.py 很好)。但是,它只适合简单的通信(您知道要预先发送什么数据),如果您需要更复杂的通信,例如:

send data to process B
read stdout
if stdout ...
    do something bases on stdout
    write to stdin

你必须实现你自己的communicate(),原来的实现here。


一步一步

我已经一步一步地测试和调试了,结果如下:

# For Popen(bufsize!=0)
A: process.stdin.write(b'hello\r\n')
B: line = sys.stdin.readline() # Hangs

所以在添加bufsize=0(无缓冲)之后

# Popen(bufsize=0)
A: process.stdin.write(b'hello\r\n') # Without \r\n B still hangs
B: line = sys.stdin.readline()
B: print('Send back', line.strip()) # Without strip it prints empty line
A: process.stdout.readline() # Hangs

那么什么有效?

# Popen(bufsize=0)
A: process.stdin.write(b'hello\r\n')
B: line = sys.stdin.readline()
B: print('Send back', line.strip())
B: sys.stdout.flush()
A: process.stdout.readline()

解释

您已将 缓冲 设置为 io.DEFAULT_BUFFER_SIZE(通常为 4090B)。来自docs:

bufsize 将在创建 stdin/stdout/stderr 管道文件对象时作为 io.open() 函数的相应参数提供:0 表示无缓冲(读取和写入是一个系统调用,并且可以返回短),1 表示行缓冲,任何其他正值表示使用大约该大小的缓冲区。负 bufsize(默认值)表示将使用系统默认值 io.DEFAULT_BUFFER_SIZE。

所以一开始A 不会刷新,因为它还没有填满缓冲区,因此B 正在等待。 Windows下是not possible to simply process.stdin.flush(),所以你必须使用bufsize=0

另外写os.linesep (\r\n) 也很重要,因为readline()

注意:我相信它应该也适用于 bufsize=1(行缓冲),但它没有。我不知道为什么。

同样的情况发生在B,它不会刷新sys.stdout,令我惊讶的是,B:sys.stdout 没有设置为无缓冲,因为:

bufsize 将在创建 stdin/stdout/stderr 管道文件对象时作为 io.open() 函数的相应参数提供

无论如何,您必须在B 中致电sys.stdout.flush()

它适用于close(),因为它强制flush()


给我代码

A.py

import subprocess
import sys

process = subprocess.Popen([sys.executable, r'B.py'], stdin=subprocess.PIPE, 
                            stdout=subprocess.PIPE, bufsize=0)
for _ in range(3):
    process.stdin.write(b'hello\r\n')
    print(process.stdout.readline())

B.py

import sys

for line in sys.stdin:
    print('Send back', line.strip())
    sys.stdout.flush()

【讨论】:

添加 \n 后它仍然像以前一样等待 尝试添加\r\n,未成功。添加刷新时,我在第二次迭代时收到此错误:IOError: [Errno 22] Invalid argument 哇,很好的答案,也适用于我,只需稍作改动,我在 A 中写入后刷新,而在 B 中根本不刷新。我用的是Python3.2,这有区别吗? @OferHelman 我正在使用 Windows 7 运行 Python 3.2.5 (default, May 15 2013, 23:07:10) [MSC v.1500 64 bit (AMD64)] on win32,我认为它可能是特定于版本的。我觉得让这些东西正常工作有点神奇。 有趣,与我的设置非常相似,我在您的答案中添加了适合我的排列

以上是关于使用标准输入和标准输出在 2 个进程之间进行通信的主要内容,如果未能解决你的问题,请参考以下文章

在 C 中使用管道在父子之间创建双向通信

在不同语言的进程之间有效地共享数据

使用带有标准输入和标准输出重定向的 2 进程管道时如何避免标准输入上的重复输入

在进程通信中管道作为标准输入/标准输出。

两个程序之间的输入输出通信

C/C++ 服务器,通过标准输入/标准输出与客户端通信(阻塞标准输入,直到读取了多个字节)