只有在缓冲区使用socket和KeyboardInterrupt发送的数据接收数据后,Python脚本才会中断

时间:2015-05-20 22:39:34

标签: python sockets irc

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

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

所以我正在编写一个python IRC bot,一切都在脚本中运行。

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

情况:

  

执行脚本以连接到IRC服务器

     

登录服务器没问题

     

在控制台窗口中,我发送了一个CTRL + C

     

控制台窗口挂起,看起来好像脚本正在运行

     

发送机器人查询消息/消息被发送到频道

     

控制台显示我在异常

中指定的中断和退出消息

一旦将CTRL + C发送到控制台,该脚本是否应该立即退出?如果我发送一个退出消息,我确实在脚本中有一个部分可以让它优雅地关闭,但是这部分让我烦恼。

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

class Animal 
{
 // ...
}

class Cat : public Animal
{
 // ...
}

class Dog : public Animal
{
 // ...
}

// + Several other derived classes

1 个答案:

答案 0 :(得分:1)

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

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

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

对此on the PyZMQ issue tracker进行了很好的讨论,包括some sample code的链接,以便解决此问题。

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

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 something here。如果有一个简单的答案,Python就已经在做了。 :)

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

但是,我找不到文档来证明 - 它绝对不推荐(并且WinSock2似乎正式允许close失败WSAEINPROGRESS,即使没有微软实施的WinSock2实际上就是这样......),但是你只是试图拯救并退出这里。

所以,我相信只需在main中定义和安装处理程序,这样就可以编写s.close(),因为# do something here可以正常工作。

如果您希望以保证保证安全有效的方式执行此操作,即使使用了一些奇怪的WinSock2实现,您也从未听说过,您需要什么要做的事情有点复杂。您需要使recv异步,并使用Windows异步I / O(这是非常痛苦的Python 2.x,除非您使用像Twisted这样的重型库),或者编写跨平台{{基于1}}的代码(它不是Windows-y,但只要您使用额外的select而不是socket的常用Unix解决方案,就可以工作。像这样(未经测试!)示例:

pipe

如果这对您没有意义,Sockets HOWTO实际上对如何使用# 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 有很好的解释,以及在Windows上使用它的缺陷。