当您使用简单的ZeroMQ REQ / REP模式时,您依赖于固定的send() - > recv()/ recv() - > send()序列。 正如this文章描述的那样,当参与者在请求中间断开连接时会遇到麻烦,因为那时你不能重新开始接收来自另一个连接的下一个请求,但状态机会强制你发送请求断开连接的请求。
自上述文章撰写以来,是否有更优雅的方法来解决这个问题?
重新连接解决此问题的唯一方法(除了不使用REQ / REP但使用其他模式)
答案 0 :(得分:21)
作为公认的答案似乎很可怕伤心对我来说,我做了一些研究,发现这一切我们需要的实际上是文档。
带有正确参数的 .setsockopt()
可以帮助您重置套接字状态机,而不会残酷地销毁它,并在前一个尸体上重建另一个。
(是的,我喜欢这个形象)。
ZMQ_REQ_CORRELATE:
将回复与请求进行匹配REQ
套接字的默认行为是依赖于消息的排序来匹配请求和响应,这通常就足够了。当此选项设置为1
时,REQ
套接字将使用包含请求id
的额外框架为外发邮件添加前缀。这意味着完整的消息是(请求id
,identity
,0
,user frames…
)。REQ
套接字将丢弃所有不以这两个帧开头的传入消息 选项值类型int
选项值单位0
,1
默认值0
适用的插座类型ZMQ_REQ
ZMQ_REQ_RELAXED:
放宽请求和回复之间的严格更改 默认情况下,REQ
套接字不允许使用zmq_send(3)
发起新请求,直到收到对前一个请求的回复为止。设置为1
时,允许发送另一条消息,并且具有断开与期望回复的对等方的基础连接的效果,从而触发对支持它的传输的重新连接尝试。重置请求 - 回复状态机,并向下一个可用对等体发送新请求 如果设置为1
,还要启用ZMQ_REQ_CORRELATE
以确保请求和回复的正确匹配。否则,可以将对已中止请求的延迟回复报告为对取代请求的回复 选项值类型int
选项值单位0
,1
默认值0
适用的套接字类型ZMQ_REQ
答案 1 :(得分:6)
好消息是,从ZMQ 3.0及更高版本(现代)开始,您可以在套接字上设置超时。正如其他人在其他地方所指出的那样,您必须在创建套接字之后但在连接之前执行此操作:
zmq_req_socket.setsockopt( zmq.RCVTIMEO, 500 ) # milliseconds
然后,当您实际尝试接收回复时(在向REP套接字发送消息之后),您可以捕获在超出超时时将断言的错误:
try:
send( message, 0 )
send_failed = False
except zmq.Again:
logging.warning( "Image send failed." )
send_failed = True
然而!当发生这种情况时,正如在别处观察到的那样,你的套接字将处于一个有趣的状态,因为它仍然会期待响应。此时,除了重新启动套接字之外,我找不到任何可靠的工作方式。请注意,如果断开()套接字然后重新连接()它,它仍将处于这种不良状态。因此你需要
def reset_my_socket:
zmq_req_socket.close()
zmq_req_socket = zmq_context.socket( zmq.REQ )
zmq_req_socket.setsockopt( zmq.RCVTIMEO, 500 ) # milliseconds
zmq_req_socket.connect( zmq_endpoint )
您还会注意到,因为我关闭()d套接字,接收超时选项“丢失”,所以在新套接字上设置它是很重要的。
我希望这会有所帮助。我希望这不会成为这个问题的最佳答案。 :)
答案 2 :(得分:1)
这有一个解决方案,即为所有呼叫添加超时。由于ZeroMQ本身并不真正提供简单的超时功能,我建议使用ZeroMQ套接字的子类,为所有重要的调用添加超时参数。
因此,不是调用s.recv()而是调用s.recv(timeout = 5.0),如果响应在5秒钟内没有返回,它将返回None并停止阻塞。当我遇到这个问题时,我对此做了一个徒劳的尝试。
答案 3 :(得分:1)
我此刻正在研究这个问题,因为我对传统系统进行了改造。
我经常遇到代码,“需要”了解连接状态。但问题是我想转向图书馆宣传的传递范式。
我找到了以下功能:zmq_socket_monitor
它的作用是监视传递给它的套接字并生成事件然后传递给“inproc”端点 - 此时你可以添加处理代码来实际执行某些操作。
此处还有一个示例(实际上是测试代码):github
我目前还没有任何特定的代码(可能在本周末),但我的目的是响应连接和断开连接,以便我可以实际执行所需的逻辑重置。
希望这会有所帮助,尽管引用了4.2文档,但我使用的是4.0.4,它似乎具有该功能 同样。
注意我注意到你在谈论上面的python,但问题是标记为C ++,所以这就是我的答案来自......
答案 4 :(得分:0)
这里的答案似乎都不是准确或有用的。 OP不在寻找有关BSD套接字编程的信息。他正在尝试找出如何在ZMQ中强健处理 accept()客户端套接字故障,以防止服务器挂起或崩溃的方法。
如前所述-ZMQ试图假装服务器的listen()套接字与accept()套接字是相同的,因此这个问题变得很复杂(并且文档中没有任何地方描述如何在此类套接字上设置基本超时。)
我的答案
:在深入研究了代码之后,传递给accept()袜子的唯一相关套接字选项似乎是父listen()er的保持活动选项。因此解决方案是在调用send或recv之前在侦听套接字上设置以下选项:
void zmq_setup(zmq::context_t** context, zmq::socket_t** socket, const char* endpoint)
{
// Free old references.
if(*socket != NULL)
{
(**socket).close();
(**socket).~socket_t();
}
if(*context != NULL)
{
// Shutdown all previous server client-sockets.
zmq_ctx_destroy((*context));
(**context).~context_t();
}
*context = new zmq::context_t(1);
*socket = new zmq::socket_t(**context, ZMQ_REP);
// Enable TCP keep alive.
int is_tcp_keep_alive = 1;
(**socket).setsockopt(ZMQ_TCP_KEEPALIVE, &is_tcp_keep_alive, sizeof(is_tcp_keep_alive));
// Only send 2 probes to check if client is still alive.
int tcp_probe_no = 2;
(**socket).setsockopt(ZMQ_TCP_KEEPALIVE_CNT, &tcp_probe_no, sizeof(tcp_probe_no));
// How long does a con need to be "idle" for in seconds.
int tcp_idle_timeout = 1;
(**socket).setsockopt(ZMQ_TCP_KEEPALIVE_IDLE, &tcp_idle_timeout, sizeof(tcp_idle_timeout));
// Time in seconds between individual keep alive probes.
int tcp_probe_interval = 1;
(**socket).setsockopt(ZMQ_TCP_KEEPALIVE_INTVL, &tcp_probe_interval, sizeof(tcp_probe_interval));
// Discard pending messages in buf on close.
int is_linger = 0;
(**socket).setsockopt(ZMQ_LINGER, &is_linger, sizeof(is_linger));
// Start internal enclave event server.
printf("Host: Starting enclave event server\n");
(**socket).bind(endpoint);
}
这是要告诉操作系统积极检查客户端套接字是否超时,并在客户端未及时返回心跳时重新获取它们以进行清理。结果是操作系统将SIGPIPE发送回您的程序,套接字错误将冒泡发送/接收-修复挂起的服务器。然后,您需要再做两件事:
1。处理SIGPIPE错误,以使程序不会崩溃
#include <signal.h>
#include <zmq.hpp>
// zmq_setup def here [...]
int main(int argc, char** argv)
{
// Ignore SIGPIPE signals.
signal(SIGPIPE, SIG_IGN);
// ... rest of your code after
// (Could potentially also restart the server
// sock on N SIGPIPEs if you're paranoid.)
// Start server socket.
const char* endpoint = "tcp://127.0.0.1:47357";
zmq::context_t* context;
zmq::socket_t* socket;
zmq_setup(&context, &socket, endpoint);
// Message buffers.
zmq::message_t request;
zmq::message_t reply;
// ... rest of your socket code here
}
2。检查send或recv返回的-1并捕获ZMQ错误。
// E.g. skip broken accepted sockets (pseudo-code.)
while (1):
{
try
{
if ((*socket).recv(&request)) == -1)
throw -1;
}
catch (...)
{
// Prevent any endless error loops killing CPU.
sleep(1)
// Reset ZMQ state machine.
try
{
zmq::message_t blank_reply = zmq::message_t();
(*socket).send (blank_reply);
}
catch (...)
{
1;
}
continue;
}
注意到在套接字失败时尝试发送答复的奇怪代码吗?在ZMQ中,REP服务器“套接字”是另一个程序的端点,该程序在该服务器上建立了REQ套接字。结果是,如果您使用挂起的客户端在REP套接字上执行recv,服务器袜子将陷入一个中断的接收循环中,它将永远等待接收有效的答复。
要在状态机上强制进行更新,请尝试发送答复。 ZMQ检测到套接字已损坏,并将其从队列中删除。服务器套接字变为“未塞住”,下一个recv调用从队列中返回一个新客户端。
要在异步客户端上启用超时(在 Python 3 中),代码应如下所示:
import asyncio
import zmq
import zmq.asyncio
@asyncio.coroutine
def req(endpoint):
ms = 2000 # In milliseconds.
sock = ctx.socket(zmq.REQ)
sock.setsockopt(zmq.SNDTIMEO, ms)
sock.setsockopt(zmq.RCVTIMEO, ms)
sock.setsockopt(zmq.LINGER, ms) # Discard pending buffered socket messages on close().
sock.setsockopt(zmq.CONNECT_TIMEOUT, ms)
# Connect the socket.
# Connections don't strictly happen here.
# ZMQ waits until the socket is used (which is confusing, I know.)
sock.connect(endpoint)
# Send some bytes.
yield from sock.send(b"some bytes")
# Recv bytes and convert to unicode.
msg = yield from sock.recv()
msg = msg.decode(u"utf-8")
现在,当出现问题时,您会遇到一些失败的情况。
顺便说一句-如果有人好奇的话-Linux中TCP空闲超时的默认值似乎是7200秒或2小时。因此,您将等待很长的时间让挂起的服务器执行任何操作!
来源:
免责声明:
我已经测试了这段代码,并且似乎可以正常工作,但是ZMQ确实使测试变得复杂了很多,因为客户端在失败时重新连接了吗?如果有人想在生产中使用此解决方案,我建议首先编写一些基本的单元测试。
服务器代码还可以通过线程或轮询进行大量改进,以便能够一次处理多个客户端。就目前而言,恶意客户端可能会暂时从服务器上占用资源(3秒超时),这是不理想的。