ZMQ套接字在Python中正常终止

时间:2014-10-06 20:16:21

标签: python sockets signals zeromq

我有以下ZMQ脚本

#!/usr/bin/env python2.6


import signal
import sys
import zmq


context = zmq.Context()
socket = context.socket(zmq.SUB)

def signal_term_handler(signal, fname):
    socket.close()
    sys.exit(0)

def main():
    signal.signal(signal.SIGTERM, signal_term_handler)

    socket.connect('tcp://16.160.163.27:8888')
    socket.setsockopt(zmq.SUBSCRIBE, '')
    print 'Waiting for a message'

    while True:
        (event, params) = socket.recv().split()
        # ... doing something with that data ...

if __name__ == '__main__':
    main()

当我Ctrl-C时,我收到以下错误:

Traceback (most recent call last):
  File "./nag.py", line 28, in <module>
    main()
  File "./nag.py", line 24, in main
    (event, params) = socket.recv().split()
  File "socket.pyx", line 628, in zmq.backend.cython.socket.Socket.recv (zmq/backend/cython/socket.c:5616)
  File "socket.pyx", line 662, in zmq.backend.cython.socket.Socket.recv (zmq/backend/cython/socket.c:5436)
  File "socket.pyx", line 139, in zmq.backend.cython.socket._recv_copy (zmq/backend/cython/socket.c:1771)
  File "checkrc.pxd", line 11, in zmq.backend.cython.checkrc._check_rc (zmq/backend/cython/socket.c:5863)
KeyboardInterrupt

现在,我以为我处理了套接字的关闭,当收到用户的终止信号时,很好,那为什么我会得到这个丑陋的消息。我错过了什么。

注意我在Google和StackOverflow上进行了一些搜索,但还没有找到解决此问题的方法。

感谢。

编辑对于那些已经走到这一步的人 - user3666197提出了一种非常好且强大的方法来处理执行期间的终止或任何异常。

3 个答案:

答案 0 :(得分:3)

事件处理方法

虽然演示代码很小,但实际系统中,多主机/多进程通信系统通常应处理主控制循环中所有不利影响事件。

try:
    context = zmq.Context()         # setup central Context instance
    socket  = ...                   # instantiate/configure all messaging archetypes
    # main control-loop ----------- # ----------------------------------------
    #
    # your app goes here, incl. all nested event-handling & failure-resilience
    # ----------------------------- # ----------------------------------------
except ...:
    #                               # handle IOErrors, context-raised exceptions
except Keyboard Interrupt:
    #                               # handle UI-SIG
except:
    #                               # handle other, exceptions "un-handled" above
finally:
    #                               # GRACEFULL TERMINATION
    # .setsockopt( zmq.LINGER, 0 )  #           to avoid hanging infinitely
    # .close()                      # .close()  for all sockets & devices
    #
    context.term()                  #           Terminate Context before exit

答案 1 :(得分:1)

答案 2 :(得分:0)

清洁时存在

人们可能会想到下面的代码!但是不需要!为了套接字关闭!

套接字自动关闭!

然而,这是手动完成的方式!

此外,我还列出了所有不同的有用信息,以了解有关销毁、关闭或清理主题的含义!

try:
   context = zmq.Context()
   socket = context.socket(zmq.ROUTER)
   socket.bind(SOCKET_PATH)
   # ....
finally :
    context.destroy() # Or term() for graceful destroy 

KeyboardInterupt 错误并修复

在进一步之前!错误原因:

Traceback (most recent call last):
  File "/usr/lib/python3.9/runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
...
    msg = self.recv(flags)
  File "zmq/backend/cython/socket.pyx", line 781, in zmq.backend.cython.socket.Socket.recv
  File "zmq/backend/cython/socket.pyx", line 817, in zmq.backend.cython.socket.Socket.recv
  File "zmq/backend/cython/socket.pyx", line 186, in zmq.backend.cython.socket._recv_copy
  File "zmq/backend/cython/checkrc.pxd", line 13, in zmq.backend.cython.checkrc._check_rc
KeyboardInterrupt

这只是KeyboardInterrupt错误!

刚抓住它!会解决问题!

例如:

try:
    context = zmq.Context()
    socket = context.socket(zmq.ROUTER)
    socket.bind(SOCKET_PATH)
    # ...
except KeyboardInterrupt:
    print('> User forced exit!')

错误不再显示!

enter image description here

现在不需要终止上下文了!它会自动完成!

也请注意:如果您没有捕捉到KeyboardInterrupt!只需创建一个 finally: 块并单独运行 context.term()!该过程将永远挂起!

finally:
    socket.close() # assuming one socket within the context
    context.term()

finally:
    context.destroy()

会抛出同样的错误!这证明错误是键盘中断引起的!应该是从图书馆里抓到的!又扔了!

只有捕捉KeyboardInterrupt 才能做到!

except KeyboardInterrupt:
    print('> User forced exit!')
finally:
    context.destroy() # manual (not needed)

会的!但是添加finally块完全没用!并手动销毁(关闭套接字+终止)

让我告诉你为什么

如果赶时间,请转到在python中无需在退出时清理部分最后!

终止的工作原理及原因

来自 zguide:Making-a-Clean-Exit

它声明我们需要关闭所有消息!还有所有的插座!只有在此之前,终止解除阻塞并使代码退出

还有嘭! api 通过 zmq_ctx_destroy() 并关闭套接字并销毁消息!

有很多事情要知道:

<块引用>

内存泄漏是一回事,但 ZeroMQ 对退出应用程序的方式非常挑剔。原因是技术性的和痛苦的,但结果是,如果您让任何套接字保持打开状态,zmq_ctx_destroy() 函数将永远挂起。即使您关闭所有套接字,zmq_ctx_destroy() 默认情况下,如果有挂起的连接或发送,将永远等待,除非您在关闭它们之前将这些套接字上的 LINGER 设置为零.

<块引用>

我们需要担心的ZeroMQ对象消息套接字上下文< /强>。幸运的是,它非常简单,至少在简单的程序中是这样:

<块引用>
  • 尽可能使用 zmq_send() 和 zmq_recv(),因为它避免了使用 zmq_msg_t 对象的需要。
<块引用>
  • 如果您确实使用了 zmq_msg_recv(),请务必在使用完后立即通过调用 zmq_msg_close() 释放收到的消息。
<块引用>
  • 如果您要打开和关闭大量套接字,这可能表明您需要重新设计应用程序。在某些情况下,除非您销毁上下文,否则不会释放套接字句柄。
<块引用>
  • 退出程序时,关闭套接字,然后调用 zmq_ctx_destroy()。这破坏了上下文。

用于销毁上下文和终止的Python api

在 pyzmq 中! Context.term() 呼叫 zmq_ctx_destroy()

另一方面,方法 Context.destroy() 不仅是 zmq_ctx_destroy(),而且它会关闭上下文的所有套接字!然后调用 Context.term() 调用 zmq_ctx_destroy()

来自蟒蛇doc

销毁()

注意 destroy() 不是 zmq_ctx_destroy()term() 是!

destroy() = context socket close() + term()

<块引用>

destroy(linger=None)

<块引用>

关闭与此上下文关联的所有套接字,然后终止该上下文。

<块引用>

警告 destroy 涉及调用 zmq_close(),这不是线程安全的。如果其他线程中有活动套接字,则不得调用此方法。

<块引用>

参数 linger (int, optional) – 如果指定,在关闭套接字之前在套接字上设置 LINGER。

term()

<块引用>

term()

<块引用>

关闭或终止上下文。

<块引用>

上下文终止按以下步骤执行:

  • 当前在上下文中打开的套接字上正在进行的任何阻塞操作都将引发 zmq.ContextTerminated。除了 socket.close() 之外,在此上下文中打开的套接字上的任何进一步操作都将引发 zmq.ContextTerminated。
  • 在中断所有阻塞调用后,term应阻塞直到满足以下条件: 在上下文中打开的所有套接字都已关闭。
  • 对于上下文中的每个套接字,在套接字上发送的所有消息都已物理传输到网络对等方,或者使用 zmq.LINGER 套接字选项设置的套接字的逗留期已过期。
  • 有关套接字延迟行为的更多详细信息,请参阅 ZMQ_LINGER 的 libzmq 文档。 可以调用它来手动关闭上下文。如果不调用此方法,则上下文将在垃圾收集时自动关闭。

如果您想手动关闭,这很有用!

这取决于人们可能想要以一种或另一种方式进行的想要的行为! term() 将为打开套接字操作引发 zmq.ContextTerminated 异常!如果逼出来!可以简单地调用destroy()!为了优雅退出!可以使用term()!然后在捕获的 zmq.ContextTerminated 异常块中!应该关闭套接字!并做任何处理!要关闭套接字,可以使用 socket.close()!一个接一个地做!我想知道如果我们此时调用 destroy() 会发生什么!它可能有效!套接字将关闭!但是随后对 context.term() 的第二次调用将会执行!可能没问题!可能不会!没试过!

灵儿

勾选ZMQ_LINGER: Set linger period for socket shutdown 标题! (ctrl + f)

http://api.zeromq.org/2-1:zmq-setsockopt

<块引用>

ZMQ_LINGER 选项应设置指定套接字的延迟时间。延迟周期决定了在使用 zmq_close(3) 关闭套接字后,尚未发送到对等方的未决消息将在内存中停留多长时间,并进一步影响使用 zmq_term(3) 终止套接字上下文。以下概述了不同的行为:

<块引用>
  • 默认值 -1 指定无限的逗留期。调用 zmq_close() 后不应丢弃挂起的消息;尝试使用 zmq_term() 终止套接字的上下文将阻塞,直到所有未决消息都已发送到对等方。
  • 值 0 指定没有延迟期。当使用 zmq_close() 关闭套接字时,应立即丢弃待处理的消息。
  • 正值指定延迟时间的上限(以毫秒为单位)。调用 zmq_close() 后不应丢弃挂起的消息;尝试使用 zmq_term() 终止套接字的上下文将被阻塞,直到所有挂起的消息都已发送到对等方,或逗留期到期,之后任何挂起的消息都将被丢弃。
<块引用>

选项值类型:int
选项值单位:毫秒
默认值:-1(无限)
适用插座类型:所有

在python中不需要在退出时清理

如果您想手动销毁上下文,您只能使用 destroy()term()destroy() 的组合!如果您想对 zmq.ContextTerminated 异常进行一些处理!或者在处理多个上下文时!而您正在创建它们并关闭它们!尽管通常我们从不这样做!或者代码运行正常时的一些原因!

否则如 zguide

中所述 <块引用>

至少对于 C 开发来说是这样。 在具有自动对象销毁功能的语言中,套接字和上下文将在您离开作用域时被销毁。如果您使用异常,则必须在“最终”块中进行清理,与任何资源相同。

您可以在上面 Context.term() 的 pyzmq 文档中看到它:

<块引用>

可以调用这个来手动关闭上下文。如果不调用此方法,则上下文将在垃圾收集时自动关闭。

当变量超出范围时,它们会被销毁!并且销毁和退出将自动处理!当程序退出时!让我们说即使在最终代码之后!所有变量都会被销毁!所以清洁工作会在那里进行!

再来一次!如果您遇到一些问题!确保它不是上下文、套接字和消息关闭相关!并确保使用最新版本的pyzmq