4

短版(如果你能回答它对我有用的短版,其余的主要是为了其他有类似任务的人的利益):

在 Windows 的 python 中,我想创建 2 个文件对象,附加到同一个文件(它不必是硬盘上的实际文件),一个用于读取,一个用于写入,这样如果读取结束尝试读取它永远不会得到 EOF(它只会阻塞直到写入某些内容)。我认为在 linux os.mkfifo() 中可以完成这项工作,但在 Windows 中它不存在。可以做什么?(我必须使用文件对象)。

一些额外的细节:我有一个 python 模块(不是我写的),它通过标准输入和标准输出(使用 raw_input() 和打印)玩某个游戏。我也有一个 Windows 可执行文件通过标准输入和标准输出玩同样的游戏。我想让他们互相对战,并记录他们所有的交流。

这是我可以编写的代码(该get_fifo()功能未实现,因为这是我不知道在 Windows 上做的):

class Pusher(Thread):
        def __init__(self, source, dest, p1, name):
                Thread.__init__(self)
                self.source = source
                self.dest = dest
                self.name = name
                self.p1 = p1

        def run(self):
                while (self.p1.poll()==None) and\
                      (not self.source.closed) and (not self.source.closed):
                        line = self.source.readline()
                        logging.info('%s: %s' % (self.name, line[:-1]))
                        self.dest.write(line)
                        self.dest.flush()


exe_to_pythonmodule_reader, exe_to_pythonmodule_writer =\
                          get_fifo()
pythonmodule_to_exe_reader, pythonmodule_to_exe_writer =\
                          get_fifo()

p1 = subprocess.Popen(exe, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE)

old_stdin = sys.stdin
old_stdout = sys.stdout

sys.stdin = exe_to_pythonmodule_reader
sys.stdout = pythonmodule_to_exe_writer

push1 = Pusher(p1.stdout, exe_to_pythonmodule_writer, p1, '1')
push2 = Pusher(pythonmodule_to_exe_reader, p1.stdin, p1, '2')

push1.start()
push2.start()
ret = pythonmodule.play()
sys.stdin = old_stdin
sys.stdout = old_stdout
4

4 回答 4

16

按照上面的两个答案,我不小心碰到了答案。os.pipe() 完成了这项工作。谢谢您的回答。

我发布完整的代码以防其他人正在寻找这个:

import subprocess
from threading import Thread
import time
import sys
import logging
import tempfile
import os

import game_playing_module

class Pusher(Thread):
    def __init__(self, source, dest, proc, name):
        Thread.__init__(self)
        self.source = source
        self.dest = dest
        self.name = name
        self.proc = proc

    def run(self):
        while (self.proc.poll()==None) and\
              (not self.source.closed) and (not self.dest.closed):
            line = self.source.readline()
            logging.info('%s: %s' % (self.name, line[:-1]))
            self.dest.write(line)
            self.dest.flush()

def get_reader_writer():
    fd_read, fd_write = os.pipe()
    return os.fdopen(fd_read, 'r'), os.fdopen(fd_write, 'w')

def connect(exe):
    logging.basicConfig(level=logging.DEBUG,\
                        format='%(message)s',\
                        filename=LOG_FILE_NAME,
                        filemode='w')

    program_to_grader_reader, program_to_grader_writer =\
                              get_reader_writer()

    grader_to_program_reader, grader_to_program_writer =\
                              get_reader_writer()

    p1 = subprocess.Popen(exe, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE)        

    old_stdin = sys.stdin
    old_stdout = sys.stdout

    sys.stdin = program_to_grader_reader
    sys.stdout = grader_to_program_writer

    push1 = Pusher(p1.stdout, program_to_grader_writer, p1, '1')
    push2 = Pusher(grader_to_program_reader, p1.stdin, p1, '2')

    push1.start()
    push2.start()

    game_playing_module.play()

    sys.stdin = old_stdin
    sys.stdout = old_stdout

    fil = file(LOG_FILE, 'r')
    data = fil.read()
    fil.close()
    return data

if __name__=='__main__':
    if len(sys.argv) != 2:
        print 'Usage: connect.py exe'
        print sys.argv
        exit()
    print sys.argv
    print connect(sys.argv[1])
于 2010-05-27T16:17:34.517 回答
6

在 Windows 上,您正在查看(Named or Anonymous) Pipes

管道是进程用于通信的共享内存的一部分。创建管道的进程是管道服务器。连接到管道的进程是管道客户端。一个进程将信息写入管道,然后另一个进程从管道中读取信息。

要使用 Windows 管道,您可以使用Python for Windows 扩展 (pywin32)Ctypes 模块。一个特殊的实用程序模块win32pipe为 win32 管道 API 提供了一个接口。它包括popen[234]()便利功能的实现。

请参阅how-to-use-win32-apis-with-python和类似的 SO 问题(不特定于 Pipes,但指向有用的信息)。

于 2010-05-27T15:20:28.023 回答
5

对于跨平台解决方案,我建议在 localhost (127.0.0.1) 上的套接字顶部构建类似文件的对象——这就是 IDLE 默认情况下解决与您的问题非常相似的问题。

于 2010-05-27T15:20:40.000 回答
2

os.pipe()返回匿名管道,或者 Windows 上的命名管道,非常轻量级和高效。

TCP 套接字(如user1495323所建议的)更重量级:例如,您可以使用 netstat 查看它们,每个都需要一个端口号,并且每个对等方的可用端口数限制为 64k(例如,从 localhost 到 localhost 的 64k)。

另一方面,命名管道(在 Windows 上)受到限制,因为:

  • 您不能在 Windows 上将select() 用于非阻塞 I/O,因为它们不是套接字。
  • 没有明显的方法可以read()超时,并且
  • 即使使它们不阻塞也很困难

并且可以使用 将套接字包装在与 Python 兼容的文件句柄中makefile(),这允许它们用于重定向 stdout 或 stderr。这使得这对于某些用例来说是一个有吸引力的选择,例如stdout从一个线程发送到另一个线程。

可以使用这样的自动分配的端口号构造套接字(基于优秀的 Python 套接字 HOWTO):

with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as input_socket:
    # Avoid socket exhaustion by setting SO_REUSEADDR <https://stackoverflow.com/a/12362623/648162>:
    input_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # localhost doesn't work if the definition is missing from the hosts file,
    # and 127.0.0.1 only works with IPv4 loopback, but socket.gethostname()
    # should always work:
    input_socket.bind((socket.gethostname(), 0))
    random_port_number = input_socket.getsockname()[1]
    input_socket.listen(1)

    # Do something with input_socket, for example pass it to another thread.

    output_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # close() should not strictly be necessary here, but since connect() could fail, it avoids leaking fds
    # in that case. "If a file descriptor is given, it is closed when the returned I/O object is closed".
    with output_socket:
        output_socket.connect((socket.gethostname(), random_port_number))

然后input_socket(例如另一个线程)的用户可以执行以下操作:

with input_socket:
    while True:
        readables, _, _ = select.select([input_socket], [], [input_socket], 1.0)

        if len(readables) > 0:
            input_conn, addr = self.input_socket.accept()
            break

    with input_conn:
        while True:
            readables, _, errored = select.select([input_conn], [], [input_conn], 1.0)
            if len(errored) > 0:
                print("connection errored, stopping")
                break

            if len(readables) > 0:
                read_data = input_conn.recv(1024)
                if len(read_data) == 0:
                    print("connection closed, stopping")
                    break
                else:
                    print(f"read data: {read_data!r}")
于 2018-11-28T13:41:01.073 回答