在 Windows 上将 ^C 发送到 Python 子进程对象 [英] Sending ^C to Python subprocess objects on Windows

查看:44
本文介绍了在 Windows 上将 ^C 发送到 Python 子进程对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

proc.send_signal(signal.SIGINT)

完美运行.在 Windows 上,这会引发错误(不支持信号 2"或类似内容).我在 Windows 上使用 Python 2.7,所以我觉得我应该可以这样做

proc.send_signal(signal.CTRL_C_EVENT)

但这根本没有任何作用.我需要做什么?这是创建子进程的代码:

# Windows 需要一个额外的参数传递给 subprocess.Popen,# 但是这个常量在 Unix 上没有定义.尝试:kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP除了 AttributeError: passproc = subprocess.Popen(argv,stdin=open(os.path.devnull, "r"),标准输出=子进程.PIPE,stderr=subprocess.PIPE,**夸格)

解决方案

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

包装器代码:

#wrapper.py导入子进程、时间、信号、系统、操作系统定义信号处理程序(信号,帧):时间.睡眠(1)打印 'Ctrl+C 在 wrapper.py 中收到'信号.信号(信号.SIGINT,信号处理程序)打印wrapper.py 开始"subprocess.Popen("python demo.py")time.sleep(3) #在这里替换为你的 IPC 代码,它等待一个火 CTRL-C 请求os.kill(信号.CTRL_C_EVENT,0)

捕获CTRL-C的程序代码:

#demo.py导入信号、系统、时间定义信号处理程序(信号,帧):打印 'Ctrl+C 在 demo.py 中收到'时间.睡眠(1)sys.exit(0)信号.信号(信号.SIGINT,信号处理程序)打印demo.py 开始"#signal.pause() # 在 Windows 下不起作用而(真):时间.睡眠(1)

启动包装器,例如:

PythonPrompt>导入子流程PythonPrompt>subprocess.Popen("启动python wrapper.py", shell=True)

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

说明:

前置信息

  • 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 shell 命令 start 让您的程序在不同的 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.语法与子流程模块非常相似.

用法:

<预><代码>>>>进口控制>>>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()

代码

导入套接字导入子流程导入时间随机导入导入信号,操作系统,系统类Popen:_port = random.randint(10000, 50000)_连接 = ''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()类包装器:TERMINATION_REQ = "用 CTRL-C 终止"def _create_connection(self, port):s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.bind(('localhost', 端口))s.听(1)conn, addr = s.accept()返回连接def _wait_on_ctrl_c_request(self, conn):为真:数据 = conn.recv(1024)如果数据== self.TERMINATION_REQ:ctrl_c_received = 真休息别的:ctrl_c_received = 假返回 ctrl_c_receiveddef _cleanup_and_fire_ctrl_c(self, conn):conn.close()os.kill(信号.CTRL_C_EVENT,0)def _signal_handler(自我,信号,帧):时间.睡眠(1)sys.exit(0)def __init__(self, cmd, port):信号.信号(信号.SIGINT,self._signal_handler)subprocess.Popen(cmd)conn = self._create_connection(port)ctrl_c_req_received = self._wait_on_ctrl_c_request(conn)如果 ctrl_c_req_received:self._cleanup_and_fire_ctrl_c(conn)别的:sys.exit(0)如果 __name__ == "__main__":command_string = sys.argv[1]port_no = int(sys.argv[2])包装器(command_string,port_no)

I have a test harness (written in Python) that needs to shut down the program under test (written in C) by sending it ^C. On Unix,

proc.send_signal(signal.SIGINT)

works perfectly. On Windows, that throws an error ("signal 2 is not supported" or something like that). I am using Python 2.7 for Windows, so I have the impression that I should be able to do instead

proc.send_signal(signal.CTRL_C_EVENT)

but this doesn't do anything at all. What do I have to do? This is the code that creates the subprocess:

# 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)

解决方案

There is a solution by using a wrapper (as described in the link Vinay provided) which is started in a new console window with the Windows start command.

Code of the wrapper:

#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)

Code of the program catching 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)

Launch the wrapper like e.g.:

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

You need to add some IPC code which allows you to control the wrapper firing the os.kill(signal.CTRL_C_EVENT, 0) command. I used sockets for this purpose in my application.

Explanation:

Preinformation

  • send_signal(CTRL_C_EVENT) does not work because CTRL_C_EVENT is only for os.kill. [REF1]
  • os.kill(CTRL_C_EVENT) sends the signal to all processes running in the current cmd window [REF2]
  • Popen(..., creationflags=CREATE_NEW_PROCESS_GROUP) does not work because CTRL_C_EVENT is ignored for process groups. [REF2] This is a bug in the python documentation [REF3]

Implemented solution

  1. Let your program run in a different cmd window with the Windows shell command start.
  2. Add a CTRL-C request wrapper between your control application and the application which should get the CTRL-C signal. The wrapper will run in the same cmd window as the application which should get the CTRL-C signal.
  3. The wrapper will shutdown itself and the program which should get the CTRL-C signal by sending all processes in the cmd window the CTRL_C_EVENT.
  4. The control program should be able to request the wrapper to send the CTRL-C signal. This might be implemnted trough IPC means, e.g. sockets.

Helpful posts were:

I had to remove the http in front of the links because I'm a new user and are not allowed to post more than two links.

Update: IPC based CTRL-C Wrapper

Here you can find a selfwritten python module providing a CTRL-C wrapping including a socket based IPC. The syntax is quite similiar to the subprocess module.

Usage:

>>> 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()

Code

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)

这篇关于在 Windows 上将 ^C 发送到 Python 子进程对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆