Python 脚本仅在缓冲区使用套接字接收数据并发送 KeyboardInterrupt 后才中断

标签 python sockets irc

我在 Windows 8.1 Enterprise 64 位上使用 Python 版本 2.7.9。

(Python 2.7.9(默认,2014 年 12 月 10 日,12:28:03)[MSC v.1500 64 位 (AMD64)] 在 win32 上)

所以我正在编写一个 python IRC 机器人,一切都在脚本内运行。

我遇到的问题是,如果我发送键盘中断,控制台窗口中的脚本显示它仍在运行,直到机器人收到数据。

情况:

Execute script to connect to IRC server

Logs onto the server no problem

In console window, I send a CTRL + C

Console window hangs making it seem like script is running

Send bot a query message / message is sent to the channel

Console shows interrupt and exit message I designated in the exception

一旦 CTRL + C 发送到控制台,脚本是否应该立即退出?我确实在脚本中有一部分可以在我发送退出消息时使其优雅地关闭,但这部分让我感到困扰。

这是我的代码,我认为它可能存在问题:

def main(NETWORK, NICK, CHAN, PORT):
   flag = True
   readbuffer = ""
   global CURRENTCHANNELS
   global MAXCHANNELS

   s.connect((NETWORK,PORT))
   s.send("NICK %s\r\n" % NICK)
   s.send("USER %s %s bla :%s\r\n" % (IDENTD, NETWORK, REALNAME))

   while(flag):
       try:
           readbuffer = readbuffer + s.recv(4096)
       except KeyboardInterrupt:
           print "Interrupt received"
       finally:
            s.close()

最佳答案

在 Windows 上,使用 Python 2.x,Ctrl-C 通常不会中断套接字调用。

在某些情况下,Ctrl-Break 有效。如果确实如此,并且这对您来说足够好,那么您就完成了。

但是,如果 Ctrl-Break 不起作用,或者如果这作为解决方法 Not Acceptable ,则唯一的选择是设置您自己的控制台 ctrl 键处理程序,使用 [SetConsoleControlHandler]( https://msdn.microsoft.com/en-us/library/windows/desktop/ms686016%28v=vs.85%29.aspx )。

对此有很好的讨论 on the PyZMQ issue tracker ,包括指向 some sample code 的链接解决这个问题。

如果您可以使用 PyWin32 中的 win32api 模块,代码可能会更简单,但假设您不能,我认为这就是您想要的代码:

from ctypes import WINFUNCTYPE, windll
from ctypes.wintypes import BOOL, DWORD

kernel32 = windll.LoadLibrary('kernel32')
PHANDLER_ROUTINE = WINFUNCTYPE(BOOL, DWORD)
SetConsoleCtrlHandler = kernel32.SetConsoleCtrlHandler
SetConsoleCtrlHandler.argtypes = (PHANDLER_ROUTINE, BOOL)
SetConsoleCtrlHandler.restype = BOOL
CTRL_C_EVENT = 0
CTRL_BREAK_EVENT = 1

@PHANDLER_ROUTINE
def console_handler(ctrl_type):
    if ctrl_type in (CTRL_C_EVENT, CTRL_BREAK_EVENT):
        # do something here
        return True
    return False

if __name__ == '__main__':
    if not SetConsoleCtrlHandler(console_handler, True):
        raise RuntimeError('SetConsoleCtrlHandler failed.')

问题是在# do some here 中放入什么内容。如果有一个简单的答案,Python 早就在这么做了。 :)

根据文档,HandlerRoutine函数实际上在不同的线程上运行。而且,IIRC,从主线程下关闭套接字总是会导致其 recv 唤醒并引发异常。 (不是您想要的,但仍然是您可以处理的。)

但是,我找不到文档来证明这一点 - 绝对不推荐(并且 WinSock2 似乎正式允许 closeWSAEINPROGRESS 失败,即使没有WinSock2 的 Microsoft 实现实际上就是这样做的...),但是您只是想摆脱困境并退出这里。

所以,我相信只需在 main 中定义和安装处理程序,这样您就可以编写 s.close() 作为 # do some here 会起作用的。


如果你想以一种保证安全有效的方式做到这一点,即使使用一些你从未听说过的奇怪的 WinSock2 实现,你需要做的还有更多复杂的。您需要使 recv 异步,并使用 Windows 异步 I/O(这对于 Python 2.x 来说非常痛苦,除非您使用像 Twisted 这样的重型库),或者编写跨基于平台 select 的代码(虽然不太符合 Windows 风格,但可以工作 — 只要您使用额外的 socket 而不是通常的 Unix 解决方案 管道)。就像这个(未经测试!)示例:

# ctypes setup code from above

def main():
    extrasock = socket.socket(socket.SOCK_DGRAM)
    extrasock.bind(('127.0.0.1', 0))
    extrasock.setblocking(False)

    @PHANDLER_ROUTINE
    def console_handler(ctrl_type):
        if ctrl_type in (CTRL_C_EVENT, CTRL_BREAK_EVENT):
            killsock = socket.socket(socket.SOCK_DGRAM)
            killsock.sendto('DIE', extrasock.getsockname())
            killsock.close()
            return True
        return False

    if not SetConsoleCtrlHandler(console_handler, True):
        raise RuntimeError('SetConsoleCtrlHandler failed.')

    # your existing main code here, up to the try
    # except that you need s.setblocking(False) too
    try:
        r, _, _ = select.select([s, extrasock], [], [])
        if extrasock in r:
            raise KeyboardInterrupt
        if s in r:
            readbuffer = readbuffer + s.recv(4096)
    except KeyboardInterrupt:
    # rest of your code here

如果这对您来说没有意义,Sockets HOWTO实际上对如何使用 select 以及在 Windows 上使用它的陷阱有一个很好的解释。

关于Python 脚本仅在缓冲区使用套接字接收数据并发送 KeyboardInterrupt 后才中断,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30361577/

相关文章:

ip - 应用程序多播的最佳教程?

c++ - 优雅地关闭 TCP 套接字

python - 在 Python 中的 Kmeans 之后确定簇的大小

java - 如何处理客户端/服务器应用程序以发送消息和/或文件

python - 导入用 Cython 制作的 .so 文件导致 ImportError : . .. undefined symbol

javascript - Socket.io 无法连接,求助于 "polling"

JAVA:java.net.BindException: 地址已被使用: JVM_Bind

go - 如何分析 Go (golang) 崩溃日志

python - SQLAlchemy:如何选择是在一个列表还是另一个列表中?

python - 自动化 REST 服务测试所需的建议