在Windows上发送^ C到Pythonsubprocess对象

我有一个testing工具(用Python编写),需要通过发送^ C来closures被测程序(用C编写)。 在Unix上,

proc.send_signal(signal.SIGINT) 

完美的作品。 在Windows上,这会引发一个错误(“信号2不支持”或类似的东西)。 我正在使用Python 2.7 for Windows,所以我有这样的印象,我应该能够做到

 proc.send_signal(signal.CTRL_C_EVENT) 

但是这根本没有做任何事情。 我需要做什么? 这是创buildsubprocess的代码:

 # Windows needs an extra argument passed to subprocess.Popen, # but the constant isn't defined on Unix. try: kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP except AttributeError: pass proc = subprocess.Popen(argv, stdin=open(os.path.devnull, "r"), stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) 

有一个解决方案是使用一个包装器(如Vinay提供的链接中所述),该解决方案是使用Windows 启动命令在新的控制台窗口中启动的

包装的代码:

 #wrapper.py import subprocess, time, signal, sys, os def signal_handler(signal, frame): time.sleep(1) print 'Ctrl+C received in wrapper.py' signal.signal(signal.SIGINT, signal_handler) print "wrapper.py started" subprocess.Popen("python demo.py") time.sleep(3) #Replace with your IPC code here, which waits on a fire CTRL-C request os.kill(signal.CTRL_C_EVENT, 0) 

捕捉CTRL-C的程序代码:

 #demo.py import signal, sys, time def signal_handler(signal, frame): print 'Ctrl+C received in demo.py' time.sleep(1) sys.exit(0) signal.signal(signal.SIGINT, signal_handler) print 'demo.py started' #signal.pause() # does not work under Windows while(True): time.sleep(1) 

启动包装,如:

 PythonPrompt> import subprocess PythonPrompt> subprocess.Popen("start python wrapper.py", shell=True) 

您需要添加一些IPC代码,允许您控制封装器触发os.kill(signal.CTRL_C_EVENT,0)命令。 我在我的应用程序中使用了套接字。

说明:

Preinformation

  • send_signal(CTRL_C_EVENT)不起作用,因为CTRL_C_EVENT仅适用于os.kill 。 [REF1]
  • os.kill(CTRL_C_EVENT)将信号发送到当前cmd窗口中运行的所有进程[REF2]
  • Popen(..., creationflags=CREATE_NEW_PROCESS_GROUP)不起作用,因为进程组被忽略了CTRL_C_EVENT 。 [REF2]这是Python文档[REF3]中的一个错误

实施解决方案

  1. 让您的程序在Windows外壳程序命令启动的另一个cmd窗口中运行。
  2. 在控制应用程序和应该获得CTRL-C信号的应用程序之间添加一个CTRL-C请求包装器。 包装器将运行在与获得CTRL-C信号的应用程序相同的cmd窗口中。
  3. 包装器将自行关闭,程序将通过在cmd窗口中发送所有进程的CTRL_C_EVENT来获得CTRL-C信号。
  4. 控制程序应该能够请求包装发送CTRL-C信号。 这可能通过IPC手段实现,例如插座。

有用的职位是:

我不得不删除链接前面的http,因为我是一个新用户,不允许发布两个以上的链接。

更新:基于IPC的CTRL-C包装器

在这里你可以找到一个自写的python模块,提供一个CTRL-C的包装,包括一个基于套接字的IPC。 语法与子流程模块非常相似。

用法:

 >>> import winctrlc >>> p1 = winctrlc.Popen("python demo.py") >>> p2 = winctrlc.Popen("python demo.py") >>> p3 = winctrlc.Popen("python demo.py") >>> p2.send_ctrl_c() >>> p1.send_ctrl_c() >>> p3.send_ctrl_c() 

 import socket import subprocess import time import random import signal, os, sys class Popen: _port = random.randint(10000, 50000) _connection = '' def _start_ctrl_c_wrapper(self, cmd): cmd_str = "start \"\" python winctrlc.py "+"\""+cmd+"\""+" "+str(self._port) subprocess.Popen(cmd_str, shell=True) def _create_connection(self): self._connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._connection.connect(('localhost', self._port)) def send_ctrl_c(self): self._connection.send(Wrapper.TERMINATION_REQ) self._connection.close() def __init__(self, cmd): self._start_ctrl_c_wrapper(cmd) self._create_connection() class Wrapper: TERMINATION_REQ = "Terminate with CTRL-C" def _create_connection(self, port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('localhost', port)) s.listen(1) conn, addr = s.accept() return conn def _wait_on_ctrl_c_request(self, conn): while True: data = conn.recv(1024) if data == self.TERMINATION_REQ: ctrl_c_received = True break else: ctrl_c_received = False return ctrl_c_received def _cleanup_and_fire_ctrl_c(self, conn): conn.close() os.kill(signal.CTRL_C_EVENT, 0) def _signal_handler(self, signal, frame): time.sleep(1) sys.exit(0) def __init__(self, cmd, port): signal.signal(signal.SIGINT, self._signal_handler) subprocess.Popen(cmd) conn = self._create_connection(port) ctrl_c_req_received = self._wait_on_ctrl_c_request(conn) if ctrl_c_req_received: self._cleanup_and_fire_ctrl_c(conn) else: sys.exit(0) if __name__ == "__main__": command_string = sys.argv[1] port_no = int(sys.argv[2]) Wrapper(command_string, port_no) 

尝试使用ctypes调用GenerateConsoleCtrlEvent函数。 在创建新进程组时,进程组ID应与pid相同。 所以,像

 import ctypes ctypes.windll.kernel32.GenerateConsoleCtrlEvent(0, proc.pid) # 0 => Ctrl-C 

应该管用。

更新:你是对的,我错过了细节的一部分。 这里有一个帖子提出了一个可能的解决方案,尽管它有点笨拙。 更多细节在这个答案 。

我一直在尝试这个,但出于某种原因ctrl + break的作品,而ctrl + c不。 所以使用os.kill(signal.CTRL_C_EVENT, 0)失败,但是做os.kill(signal.CTRL_C_EVENT, 1) 。 我被告知这与创建进程所有者是唯一一个可以通过ctrl c有关的东西? 那有意义吗?

为了澄清,在命令窗口中手动运行fio时,它似乎按预期运行。 按预期方式使用CTRL + BREAK中断不存储日志,CTRL + C也按预期写入文件。 问题出现在CTRL_C_EVENT的信号中。

它几乎看起来是Python中的一个bug,但可能是Windows中的一个bug。 还有一件事,我有一个cygwin的版本运行,并在python中发送CTRL + C那里工作,但是,然后再次我们不是真的在那里运行本地窗口。

例:

 import subprocess, time, signal, sys, os command = '"C:\\Program Files\\fio\\fio.exe" --rw=randrw --bs=1M --numjobs=8 --iodepth=64 --direct=1 ' \ '--sync=0 --ioengine=windowsaio --name=test --loops=10000 ' \ '--size=99901800 --rwmixwrite=100 --do_verify=0 --filename=I\\:\\test ' \ '--thread --output=C:\\output.txt' def signal_handler(signal, frame): time.sleep(1) print 'Ctrl+C received in wrapper.py' signal.signal(signal.SIGINT, signal_handler) print 'command Starting' subprocess.Popen(command) print 'command started' time.sleep(15) print 'Timeout Completed' os.kill(signal.CTRL_C_EVENT, 0)