如何在Windows的Python中检查stdin是否有可读数据?
这段代码
select.select([sys.stdin], [], [], 1.0)
在Linux上完全符合我的需求,但在Windows上却不行。
我之前用过kbhit()
这个函数,它在msvcrt
库里,可以用来检查标准输入(stdin)是否有数据可以读取,但在这种情况下,它总是返回0
,表示没有数据。此外,msvcrt.getch()
返回的是'\xff'
,而sys.stdin.read(1)
返回的是'\x01'
。看起来msvcrt
里的函数表现得不太正常。
不幸的是,我不能使用TCP套接字,因为我无法控制与我的Python程序通信的应用程序。
2 个回答
我运行了一个线程,这个线程从标准输入(stdin)读取数据,然后把这些数据转发到一个套接字(socket)。这个套接字是可以选择的,所以标准输入也可以选择。
在最近的一个项目中,我需要不断地从一个网络套接字读取数据,然后转发到另一个套接字,直到用户在控制台输入
q
。有人可能会想用多线程来解决这个问题,但我不想处理多线程的麻烦。最后,我找到了一种不太明显的解决方案,并且它有效。我创建了一个线程——没错,是线程,但没有多线程的烦恼——这个线程打开了一个服务器套接字,监听一个随机的端口,然后打开一个客户端套接字连接到这个服务器。服务器套接字接受连接后,调用
sys.stdin.read()
,这样就会阻塞,所有从标准输入读取的数据都会写入到这个接受的连接中。因此,客户端套接字就能接收到从标准输入读取的数据。现在,客户端套接字就成了一个可以选择的标准输入,并且是线程安全的。
源代码:
# coding=UTF-8
""" === Windows stdio ===
@author ideawu@163.com
@link http://www.ideawu.net/
File objects on Windows are not acceptable for select(),
this module creates two sockets: stdio.s_in and stdio.s_out,
as pseudo stdin and stdout.
@example
from stdio import stdio
stdio.write('hello world')
data = stdio.read()
print stdio.STDIN_FILENO
print stdio.STDOUT_FILENO
"""
import thread
import sys, os
import socket
# socket read/write in multiple threads may cause unexpected behaviors
# so use two separated sockets for stdin and stdout
def __sock_stdio():
def stdin_thread(sock, console):
""" read data from stdin, and write the data to sock
"""
try:
fd = sys.stdin.fileno()
while True:
# DO NOT use sys.stdin.read(), it is buffered
data = os.read(fd, 1024)
#print 'stdin read: ' + repr(data)
if not data:
break
while True:
nleft = len(data)
nleft -= sock.send(data)
if nleft == 0:
break
except:
pass
#print 'stdin_thread exit'
sock.close()
def stdout_thread(sock, console):
""" read data from sock, and write to stdout
"""
try:
fd = sys.stdout.fileno()
while True:
data = sock.recv(1024)
#print 'stdio_sock recv: ' + repr(data)
if not data:
break
while True:
nleft = len(data)
nleft -= os.write(fd, data)
if nleft == 0:
break
except:
pass
#print 'stdin_thread exit'
sock.close()
class Console:
def __init__(self):
self.serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.serv.bind(('127.0.0.1', 0))
self.serv.listen(5)
port = self.serv.getsockname()[1]
# data read from stdin will write to this socket
self.stdin_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.stdin_sock.connect(('127.0.0.1', port))
self.s_in, addr = self.serv.accept()
self.STDIN_FILENO = self.s_in.fileno()
thread.start_new_thread(stdin_thread, (self.stdin_sock, self))
# data read from this socket will write to stdout
#self.stdout_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#self.stdout_sock.connect(('127.0.0.1', port))
#self.s_out, addr = self.serv.accept()
#self.STDOUT_FILENO = self.s_out.fileno()
#thread.start_new_thread(stdout_thread, (self.stdout_sock, self))
self.read_str = '' # read buffer for readline
def close(self):
self.s_in.close()
self.s_out.close()
self.stdin_sock.close()
self.stdout_sock.close()
self.serv.close()
def write(self, data):
try:
return self.s_out.send(data)
except:
return -1
def read(self):
try:
data = self.s_in.recv(4096)
except:
return ''
ret = self.read_str + data
self.read_str = ''
return ret
def readline(self):
while True:
try:
data = self.s_in.recv(4096)
except:
return ''
if not data:
return ''
pos = data.find('\n')
if pos == -1:
self.read_str += data
else:
left = data[0 : pos + 1]
right = data[pos + 1 : ]
ret = self.read_str + left
self.read_str = right
return ret
stdio = Console()
return stdio
def __os_stdio():
class Console:
def __init__(self):
self.STDIN_FILENO = sys.stdin.fileno()
self.STDOUT_FILENO = sys.stdout.fileno()
def close(self):
pass
def write(self, data):
try:
return os.write(self.STDOUT_FILENO, data)
except:
return -1
def read(self):
try:
return os.read(self.STDIN_FILENO, 4096)
except:
return ''
def readline(self):
try:
return sys.stdin.readline()
except:
return ''
stdio = Console()
return stdio
if os.name == 'posix':
stdio = __os_stdio()
else:
stdio = __sock_stdio()
在一些少见的情况下,你可能会关心标准输入(stdin)连接到了什么地方。大多数时候,你并不在意——你只需要读取stdin。
在 someprocess | python myprogram.py
这种情况下,stdin 连接到了一个管道;也就是前一个程序的输出。你只需从 sys.stdin
读取数据,这样就能从另一个程序那里获取信息。 [不过在Windows系统中,可能还有一个“CON”设备和键盘连接。只不过这不会是 sys.stdin
。]
在 python myprogram.py <someFile
中,stdin 连接到了一个文件。你只需从 sys.stdin
读取数据,这样就能从文件中获取信息。
在 python myprogram.py
中,stdin 连接到了控制台(在类Unix系统中是 /dev/ttyxx
)。你只需从 sys.stdin
读取数据,这样就能从键盘获取输入。
注意上面这三种情况的共同点。你只需从 sys.stdin
读取数据,而你的程序环境已经为你定义了一切。你不需要检查“stdin上是否有数据可以读取”。数据已经可以用了。
有时候,你可能需要一个键盘中断(或者其他一些操作)。顺便说一下,Python将键盘中断作为输入输出的一个重要特性。按下Control-C会在输入输出过程中引发中断(它不会打断一个紧密的循环,但会给一个定期打印的程序发送信号)。
有时你需要找出stdin连接的是哪种类型的文件。
可以使用类似 os.isatty( sys.stdin.fileno() )
的方法。如果 sys.stdin
是一个TTY设备,说明你的程序连接到了Windows的“CON”(键盘)。如果 sys.stdin
不是TTY设备,那它连接的是一个文件或管道。
示例
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\slott>python
Python 2.5.2 (r252:60911, Feb 21 2008, 13:11:45) [MSC v.1310 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> import sys
>>> os.isatty( sys.stdin.fileno() )
True
>>>
如果返回值是 True
,说明Python是在没有文件或管道连接的情况下运行的。此时 sys.stdin
是键盘。使用Windows的 kbhit
就没必要了。
如果返回值是 False
,说明Python是在有文件或管道连接的情况下运行的。此时 sys.stdin
不是键盘。检查 kbhit
可能就有意义了。此外,我还可以打开 CON:
设备,直接读取键盘输入,这与 sys.stdin
是分开的。
我不太明白你为什么需要“检查stdin上是否有数据可以读取”。如果你能提供更多你想要实现的目标的细节,可能会更有帮助。