Python: subprocess.stdin.write 不正常

3 投票
4 回答
7609 浏览
提问于 2025-04-16 21:24

我刚开始学Python,虽然能写点代码和调试,但最近遇到一个问题,想了好几天也没找到答案。希望能得到一些帮助,非常感谢!

任务是:我想做一个互动的telnet(我知道有一个telnet库,但由于各种原因我们不打算使用它)。为此,我使用了subprocess.popen。

p = subprocess.Popen(telnet_command,
                         stdin = subprocess.PIPE,
                         stdout = outputfileobj,
                         stderr = errorfileobj)

我使用poll()来检查服务器是否已经连接到会话。一旦确认连接成功,我就开始向标准输入写入数据,以便进行互动交流。

inputTxt = 'GET / HTTP/1.1\nHost: ' + hostheader + '\n\n'
p.stdin.write(inputTxt)
p.stdin.flush()

问题就出现在这里。我能收到5/6次的http响应(或者至少是输出),但有1/6的情况下,我没有收到输出,而且子进程就结束了——这可不应该发生。

我对失败的情况进行了系统跟踪,下面是相关信息。

16129 write(7, "GET / HTTP/1.1\nHost: www.google."..., 37) = 37
16129 gettimeofday({1310538497, 134474}, NULL) = 0
16129 futex(0x81993a8, FUTEX_WAKE, 1)   = 0
16129 stat64("/etc/localtime", {st_mode=S_IFREG|0644, st_size=56, ...}) = 0
16129 fstat64(4, {st_mode=S_IFREG|0644, st_size=520689, ...}) = 0
16129 _llseek(4, 520689, [520689], SEEK_SET) = 0
16129 stat64("/etc/localtime", {st_mode=S_IFREG|0644, st_size=56, ...}) = 0
16129 write(4, "2011-07-13 06:28:17,134 : pconns"..., 170) = 170
16129 futex(0x81d3b30, FUTEX_WAKE, 1)   = 0
16129 waitpid(16198, 0xffa945e8, WNOHANG) = 0
16129 gettimeofday({1310538497, 135160}, NULL) = 0
16129 fstat64(6, {st_mode=S_IFREG|0644, st_size=81, ...}) = 0
16129 fstat64(6, {st_mode=S_IFREG|0644, st_size=81, ...}) = 0
16129 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf74b9000
16129 _llseek(6, 0, [0], SEEK_SET)      = 0
16129 fstat64(6, {st_mode=S_IFREG|0644, st_size=81, ...}) = 0
16129 _llseek(6, 0, [0], SEEK_CUR)      = 0
16129 read(6, "Trying 74.125.236.48...\nConnecte"..., 4096) = 81
16129 read(6, "", 4096)                 = 0
16129 close(6)                          = 0
16129 munmap(0xf74b9000, 4096)          = 0
16129 gettimeofday({1310538497, 135778}, NULL) = 0
16129 futex(0x81993a8, FUTEX_WAKE, 1)   = 0
16198 <... select resumed> )            = 1 (in [0])
16198 read(0, "GET / HTTP/1.1\nHost: www.google."..., 8191) = 37
16129 stat64("/etc/localtime",  <unfinished ...>
16198 ioctl(1, TCFLSH, 0)               = -1 ENOTTY (Inappropriate ioctl for device)
16129 <... stat64 resumed> {st_mode=S_IFREG|0644, st_size=56, ...}) = 0
16198 select(4, [0 3], [3], [3], {0, 0}) = 1 (out [3], left {0, 0})
16129 fstat64(4, {st_mode=S_IFREG|0644, st_size=520859, ...}) = 0
16198 send(3, "GET / HTTP/1.1\r\nHos\377\363\377\375\6", 24, 0 <unfinished ...>
16129 _llseek(4, 520859,  <unfinished ...>
16198 <... send resumed> )              = 24
16198 select(4, [0 3], [3], [3], {0, 0} <unfinished ...>
16129 <... _llseek resumed> [520859], SEEK_SET) = 0
16198 <... select resumed> )            = 1 (out [3], left {0, 0})
16198 send(3, ": www.google.com\r\n\r\n", 20, 0 <unfinished ...>
16129 stat64("/etc/localtime",  <unfinished ...>
16198 <... send resumed> )              = 20

如果你仔细看看跟踪中的这一行 16198 send(3, "GET / HTTP/1.1\r\nHos\377\363\377\375\6", 24, 0 ,你会发现字符串 "Host" 被替换成了一些 "Hos\377\363\377\375\6"。我不太明白为什么会偶尔出现这种情况,而且这也导致我建立的telnet连接关闭了。如果你需要更多信息,请告诉我。

4 个回答

0

如果不需要进行交互,我就不使用 communicate,而是提前把文本放进一个管道里。到现在为止,我还没有找到更符合 Python 风格的 "text_to_stream" 方法。

import os
import sys
import subprocess


def text_to_stream(text):
    p = os.pipe()
    os.write(p[1], text.encode('utf-8'))
    os.close(p[1])
    return os.fdopen(p[0], "r")


def run(cmd, stdin_text=None):
    stdin = text_to_stream(stdin_text) if stdin_text else sys.stdin
    result = subprocess.run(
        cmd, stdout=sys.stdout, stderr=sys.stderr, stdin=stdin)
    result.check_returncode()
1

虽然communicate方法更好,但如果你真的需要使用stdin.write,可以通过以下方式让调用变成非阻塞的:

fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)

想要更好地理解,可以阅读这个:

io 非阻塞

1

如果你尝试使用 communicate() 方法来向子进程发送数据,而不是使用 stdin,因为后者在 Python 的文档 中是不推荐的:

警告:使用 communicate() 而不是 .stdin.write、.stdout.read 或 .stderr.read,以避免由于其他操作系统管道缓冲区填满而导致的死锁,这会阻塞子进程。

可以试试这个:

p.communicate(input='GET / HTTP/1.1\nHost: ' + hostheader + '\n\n')

撰写回答