且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Python 子进程输出不正确?

更新时间:2021-11-13 00:25:20

这里的问题是您正在向进程发送 SIGINT.如果您只是close stdinnc 将关闭它的套接字并退出,这正是您想要的.

The problem here is that you're sending SIGINT to the process. If you just close the stdin, nc will close its socket and quit, which is what you want.

听起来您实际上在实际程序中为客户端(尽管不是服务器)使用了 nc,这意味着您有两个简单的修复方法:

It sounds like you're actually using nc for the client (although not the server) in your real program, which means you have two easy fixes:

代替lookup_client.send_signal(subprocess.signal.SIGINT),只需执行lookup_client.stdin.close().nc 会将其视为输入的 EOF,然后正常退出,此时您的服务器也将退出.

Instead of lookup_client.send_signal(subprocess.signal.SIGINT), just do lookup_client.stdin.close(). nc will see this as an EOF on its input, and exit normally, at which point your server will also exit.

#!/usr/bin/env python
import subprocess
lookup_server = subprocess.Popen("nc -l 5050", shell=True)
lookup_client = subprocess.Popen("nc localhost 5050", shell=True, stdin=subprocess.PIPE) 
print lookup_client.poll()
lookup_client.stdin.write("magic\n")
lookup_client.stdin.close()
print lookup_client.poll()
lookup_server.wait()
print "Lookup server terminated properly"

当我运行这个时,最常见的输出是:

When I run this, the most common output is:

None
None
magic
Lookup server terminated properly

偶尔第二个 None 是一个 0,和/或它出现在 magic 之后而不是之前,但除此之外,它总是全部四行.(我在 OS X 上运行.)

Occasionally the second None is a 0 instead, and/or it comes after magic instead of before, but otherwise, it's always all four lines. (I'm running on OS X.)

对于这个简单的案例(虽然可能不是你的真实案例),只需使用 communicate 而不是尝试手动进行.

For this simple case (although maybe not your real case), just use communicate instead of trying to do it manually.

#!/usr/bin/env python
import subprocess
lookup_server = subprocess.Popen("nc -l 5050", shell=True)
lookup_client = subprocess.Popen("nc localhost 5050", shell=True, stdin=subprocess.PIPE) 
print lookup_client.communicate("magic\n")
lookup_server.wait()
print "Lookup server terminated properly"

同时:

此外,如果我将 Popen 的第一个参数更改为所有这些参数的数组,则所有 nc 调用都不会正确执行,脚本会运行而无需等待.为什么会这样?

Also, if I change the first argument of Popen to an array of all of those arguments, none of the nc calls execute properly and the script runs through without ever waiting. Why does that happen?

正如文档所说:

在带有 shell=True 的 Unix 上……如果 args 是一个序列,则第一项指定命令字符串,任何附加项都将被视为 shell 本身的附加参数.

On Unix with shell=True… If args is a sequence, the first item specifies the command string, and any additional items will be treated as additional arguments to the shell itself.

所以,subprocess.Popen(["nc", "-l", "5050"], shell=True)/bin/sh -c 'nc' -l5050,而 sh 不知道如何处理这些参数.

So, subprocess.Popen(["nc", "-l", "5050"], shell=True) does /bin/sh -c 'nc' -l 5050, and sh doesn't know what to do with those arguments.

您可能确实想要使用参数数组,但随后您必须摆脱shell=True——无论如何这是个好主意,因为shell在这里没有帮助你.

You probably do want to use an array of args, but then you have to get rid of shell=True—which is a good idea anyway, because the shell isn't helping you here.

还有一件事:

lookup_client.send_signal(subprocess.signal.SIGINT)
print lookup_client.poll()

这可能会打印 -2 或 None,这取决于客户端是否已完成对 SIGINT 的响应并在您 poll 之前被杀死.如果你想真正得到 -2,你必须调用 wait 而不是 poll(或者做其他事情,比如循环直到 poll 返回非无).

This may print either -2 or None, depending on whether the client has finished responding to the SIGINT and been killed before you poll it. If you want to actually get that -2, you have to call wait rather than poll (or do something else, like loop until poll returns non-None).

最后,为什么你的原始代码不起作用?好吧,发送 SIGINT 是异步的;无法保证何时生效.举一个可能出错的例子,它可能在客户端打开套接字之前生效,在这种情况下,服务器仍然坐在那里等待一个从未出现的客户端.

Finally, why didn't your original code work? Well, sending SIGINT is asynchronous; there's no guarantee as to when it might take effect. For one example of what could go wrong, it could take effect before the client even opens the socket, in which case the server is still sitting around waiting for a client that never shows up.

你可以在 signal 调用之前加入一个 time.sleep(5) 来测试这个——但显然这不是一个真正的修复,甚至不是一个可以接受的黑客;它只对测试问题有用.您需要做的不是杀死客户端,直到它完成了您希望它做的所有事情.对于复杂的情况,你需要建立一些机制来做到这一点(例如,读取它的标准输出),而对于简单的情况,communicate 已经是你需要的一切(并且没有理由杀死孩子首先).

You can throw in a time.sleep(5) before the signal call to test this—but obviously that's not a real fix, or even an acceptable hack; it's only useful for testing the problem. What you need to do is not kill the client until it's done everything you want it to do. For complex cases, you'll need to build some mechanism to do that (e.g., reading its stdout), while for simple cases, communicate is already everything you need (and there's no reason to kill the child in the first place).