更新:我在这里用这个优秀的资源更新这个答案:https://blog.cloudflare.com/when-tcp-sockets-refuse-to-die/ 套接字编程很复杂,所以请查看这篇文章中的参考资料。
这里的答案似乎都不准确或有用。 OP 不是在寻找有关 BSD 套接字编程的信息。他正试图弄清楚如何在 REP 套接字上的 ZMQ 中稳健地处理 accept()ed 客户端套接字故障,以防止服务器挂起或崩溃。
如前所述——这个问题很复杂,因为 ZMQ 试图假装服务器监听()套接字与接受()套接字相同(并且文档中没有描述如何在此类套接字上设置基本超时。)
我的回答:
在对代码进行大量挖掘之后,传递给 accept()ed socks 的唯一相关套接字选项似乎是来自父级 listener()er 的保持活动选项。所以解决方法是在调用send或recv之前在listen socket上设置如下选项:
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));
// TCP user timeout on unacknowledged send buffer
int is_user_timeout = 2;
(**socket).setsockopt(ZMQ_TCP_MAXRT, &is_user_timeout, sizeof(is_user_timeout));
// 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,服务器 sock 会陷入中断的接收循环,它将永远等待接收有效回复。
要强制更新状态机,请尝试发送回复。 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 秒超时),这并不理想。