在Windows上的os.pipe上读取非阻塞

时间:2015-12-29 05:29:02

标签: python windows pipe

这个问题 - How to read from an os.pipe() without getting blocked? - 显示了如何检查os.pipe是否有Linux数据的解决方案,为此您需要将管道置于非阻塞模式:

import os, fcntl
fcntl.fcntl(thePipe, fcntl.F_SETFL, os.O_NONBLOCK)

在Windows上我们有这个:

ImportError: No module named fcntl

但是os.pipe就在那里:

>>> os.pipe()
(3, 4)

那么,是否可以在Windows上进行非阻塞读取或查看os.pipe的内容?

2 个答案:

答案 0 :(得分:11)

通过StackOverflow挖掘一段时间后回答我自己的问题。

更新:由于@HarryJohnston,事情发生了变化。

首先答案是,在Windows上os.pipe无法进行无阻塞读取。从this answer我得到了:

  

Windows中非阻塞/异步I / O的术语是“重叠” - 这就是您应该关注的内容。

Windows上的

os.pipe是通过CreatePipe API实现的(请参阅here并且......好吧,我在Python sources中找不到os.pipe代码) 。 CreatePipe制作匿名管道,anonymous pipes do not support asynchronous I/O

@HarryJohnston评论说SetNamedPipeHandleState doc允许将匿名管道置于非阻塞模式。我写了测试,但OSError: [Errno 22] Invalid argument失败了。错误消息似乎是错误的,所以我试图检查当数据不可用时非阻塞读取操作应该返回什么结果,并且在阅读MSDN note on named pipe modes之后我发现它应该是ERROR_NO_DATA int值232.添加ctypes.WinError()调用异常处理程序会显示预期的[Error 232] The pipe is being closed.

所以,答案是,可以在Windows上对os.pipe进行无阻塞读取,这是证明:

import msvcrt
import os

from ctypes import windll, byref, wintypes, GetLastError, WinError
from ctypes.wintypes import HANDLE, DWORD, POINTER, BOOL

LPDWORD = POINTER(DWORD)

PIPE_NOWAIT = wintypes.DWORD(0x00000001)

ERROR_NO_DATA = 232

def pipe_no_wait(pipefd):
  """ pipefd is a integer as returned by os.pipe """

  SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState
  SetNamedPipeHandleState.argtypes = [HANDLE, LPDWORD, LPDWORD, LPDWORD]
  SetNamedPipeHandleState.restype = BOOL

  h = msvcrt.get_osfhandle(pipefd)

  res = windll.kernel32.SetNamedPipeHandleState(h, byref(PIPE_NOWAIT), None, None)
  if res == 0:
      print(WinError())
      return False
  return True


if __name__  == '__main__':
  # CreatePipe
  r, w = os.pipe()

  pipe_no_wait(r)

  print os.write(w, 'xxx')
  print os.read(r, 1024)
  try:
    print os.write(w, 'yyy')
    print os.read(r, 1024)
    print os.read(r, 1024)
  except OSError as e:
    print dir(e), e.errno, GetLastError()
    print(WinError())
    if GetLastError() != ERROR_NO_DATA:
        raise

答案 1 :(得分:0)

这个答案基本上是@anatolytechtonik 的答案,但有课程。

import msvcrt
import os

# No idea what is going on here but if it works, it works.
from ctypes import windll, byref, wintypes, GetLastError, WinError, POINTER
from ctypes.wintypes import HANDLE, DWORD, BOOL

# ???
LPDWORD = POINTER(DWORD)
PIPE_NOWAIT = wintypes.DWORD(0x00000001)
ERROR_NO_DATA = 232


class AdvancedFD:
    """
    A wrapper for a file descriptor so that we can call:
        `<AdvancedFD>.read(number_of_bytes)` and
        `<AdvancedFD>.write(data_as_bytes)`

    It also makes the `read_fd` non blocking. When reading from a non-blocking
    pipe with no data it returns b"".

    Methods:
        write(data: bytes) -> None
        read(number_of_bytes: int) -> bytes
        rawfd() -> int
        close() -> None
    """
    def __init__(self, fd: int):
        self.fd = fd
        self.closed = False

    def __del__(self) -> None:
        """
        When this object is garbage collected close the fd
        """
        self.close()

    def close(self) -> None:
        """
        Closes the file descriptor.
        Note: it cannot be reopened and might raise an error if it is
        being used. You don't have to call this function. It is automatically
        called when this object is being garbage collected.
        """
        self.closed = True

    def write(self, data: bytes) -> None:
        """
        Writes a string of bytes to the file descriptor.
        Note: Must be bytes.
        """
        os.write(self.fd, data)

    def read(self, x: int) -> bytes:
        """
        Reads `x` bytes from the file descriptor.
        Note: `x` must be an int
              Returns the bytes. Use `<bytes>.decode()` to convert it to a str
        """
        try:
            return os.read(self.fd, x)
        except OSError as error:
            err_code = GetLastError()
            # If the error code is `ERROR_NO_DATA`
            if err_code == ERROR_NO_DATA:
                # Return an empty string of bytes
                return b""
            else:
                # Otherwise raise the error
                website = "https://docs.microsoft.com/en-us/windows/win32/" +\
                          "debug/system-error-codes--0-499-"
                raise OSError("An exception occured. Error code: %i Look up" +\
                              " the error code here: %s" % (err_code, website))

    def config_non_blocking(self) -> bool:
        """
        Makes the file descriptor non blocking.
        Returns `True` if sucessfull, otherwise returns `False`
        """

        # Please note that this is kindly plagiarised from:
        # https://stackoverflow.com/a/34504971/11106801
        SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState
        SetNamedPipeHandleState.argtypes = [HANDLE, LPDWORD, LPDWORD, LPDWORD]
        SetNamedPipeHandleState.restype = BOOL

        handle = msvcrt.get_osfhandle(self.fd)

        res = windll.kernel32.SetNamedPipeHandleState(handle,
                                                      byref(PIPE_NOWAIT), None,
                                                      None)
        return not (res == 0)

    def rawfd(self) -> int:
        """
        Returns the raw fd as an int.
        """
        return self.fd


class NonBlockingPipe:
    """
    Creates 2 file descriptors and wrapps them in the `AdvancedFD` class
    so that we can call:
        `<AdvancedFD>.read(number_of_bytes)` and
        `<AdvancedFD>.write(data_as_bytes)`

    It also makes the `read_fd` non blocking. When reading from a non-blocking
    pipe with no data it returns b"".

    Methods:
        write(data: bytes) -> None
        read(number_of_bytes: int) -> bytes
        rawfds() -> (int, int)
        close() -> None
    """
    def __init__(self):
        self.read_fd, self.write_fd = self.create_pipes()
        self.read_fd.config_non_blocking()

    def __del__(self) -> None:
        """
        When this object is garbage collected close the fds
        """
        self.close()

    def close(self) -> None:
        """
        Note: it cannot be reopened and might raise an error if it is
        being used. You don't have to call this function. It is automatically
        called when this object is being garbage collected.
        """
        self.read_fd.close()
        self.write_fd.close()

    def create_pipes(self) -> (AdvancedFD, AdvancedFD):
        """
        Creates 2 file descriptors and wrapps them in the `Pipe` class so
        that we can call:
            `<Pipe>.read(number_of_bytes)` and
            `<Pipe>.write(data_as_bytes)`
        """
        read_fd, write_fd = os.pipe()
        return AdvancedFD(read_fd), AdvancedFD(write_fd)

    def write(self, data: bytes) -> None:
        """
        Writes a string of bytes to the file descriptor.
        Note: Must be bytes.
        """
        self.write_fd.write(data)

    def read(self, number_of_bytes: int) -> bytes:
        """
        Reads `x` bytes from the file descriptor.
        Note: `x` must be an int
              Returns the bytes. Use `<bytes>.decode()` to convert it to a str
        """
        return self.read_fd.read(number_of_bytes)

    def rawfds(self) -> (int, int):
        """
        Returns the raw file descriptors as ints in the form:
            (read_fd, write_fd)
        """
        return self.read_fd.rawfd(), self.write_fd.rawfd()


if __name__  == "__main__":
    # Create the nonblocking pipe
    pipe = NonBlockingPipe()

    pipe.write(b"xxx")
    print(pipe.read(1024)) # Check if it can still read properly

    pipe.write(b"yyy")
    print(pipe.read(1024)) # Read all of the data in the pipe
    print(pipe.read(1024)) # Check if it is non blocking