ZeroMQ 教程 002 : 高级技巧 (10)

一般情况下, Linux上的程序在接收到诸如SIGINT和SIGTERM这样的信号时, 其默认动作是让进程退出. 这种退出信号的默认行为, 只是简单的把进程干掉, 不会管什么缓冲区有没有正确刷新, 也不会管文件以及其它资源句柄是不是正确被释放了.

这对于实际应用场景中的程序来说是不可接受的, 所以在编写后台应用的时候一定要注意这一点: 要妥善的处理POSIX Signal. 限于篇幅, 这里不会对Signal进行进一步讨论, 如果对这部分内容不是很熟悉的话, 请参阅<Unix环境高级编程>(<Advanced Programming in the UNIX Environment>)第十章(chapter 10. Signals).

下面是妥善处理Signal的一个例子

#include <stdlib.h> #include <stdio.h> #include <signal.h> #include <unistd.h> #include <fcntl.h> #include <assert.h> #include <string.h> #include <zmq.h> #define S_NOTIFY_MSG " " #define S_ERROR_MSG "Error while writing to self-pipe.\n" static int s_fd; static void s_signal_handler(int signal_value) { int rc = write(s_fd, S_NOTIFY_MSG, sizeof(S_NOTIFY_MSG)); if(rc != sizeof(S_NOTIFY_MSG)) { write(STDOUT_FILENO, S_ERROR_MSG, sizeof(S_ERROR_MSG) - 1); exit(1); } } static void s_catch_signals(int fd) { s_fd = fd; struct sigaction action; action.sa_handler = s_signal_handler; action.sa_flags = 0; sigemptyset(&action.sa_mask); sigaction(SIGINT, &action, NULL); sigaction(SIGTERM, &action, NULL); } int main(void) { int rc; void * context = zmq_ctx_new(); assert(context); void * socket = zmq_socket(context, ZMQ_REP); assert(socket); if(zmq_bind(socket, "tcp://*:5555") == -1) { printf("E: bind failed: %s\n", strerror(errno)); return -__LINE__; } int pipefds[2]; rc = pipe(pipefds); if(rc != 0) { printf("E: creating self-pipe failed: %s\n", strerror(errno)); return -__LINE__; } for(int i = 0; i < 2; ++i) { int flags = fcntl(pipefds[0], F_GETFL, 0); if(flags < 0) { printf("E: fcntl(F_GETFL) failed: %s\n", strerror(errno)); return -__LINE__; } rc = fcntl(pipefds[0], F_SETFL, flags | O_NONBLOCK); if(rc != 0) { printf("E: fcntl(F_SETFL) failed: %s\n", strerror(errno)); return -__LINE__; } } s_catch_signals(pipefds[1]); zmq_pollitem_t items[] = { { 0, pipefds[0], ZMQ_POLLIN, 0 }, { socket, 0, ZMQ_POLLIN, 0 }, }; while(1) { rc = zmq_poll(items, 2, -1); if(rc == 0) { continue; } else if(rc < 0) { if(errno == EINTR) { continue; } else { printf("E: zmq_poll failed: %s\n", strerror(errno)); return -__LINE__; } } // Signal pipe FD if(items[0].revents & ZMQ_POLLIN) { char buffer[2]; read(pipefds[0], buffer, 2); // clear notifying bytes printf("W: interrupt received, killing server...\n"); break; } // Read socket if(items[1].revents & ZMQ_POLLIN) { char buffer[255]; rc = zmq_recv(socket, buffer, 255, ZMQ_NOBLOCK); if(rc < 0) { if(errno == EAGAIN) { continue; } if(errno == EINTR) { continue; } printf("E: zmq_recv failed: %s\n", strerror(errno)); return -__LINE__; } printf("W: recv\n"); // Now send message back; // ... } } printf("W: cleaning up\n"); zmq_close(socket); zmq_ctx_destroy(context); return 0; }

上面这个程序的逻辑流程是这样的:

首先这是一个典型的服务端应用程序. 先创建了一个类型为ZMQ_REP的zmq socket, 并将之bind在本地5555端口上

然后程序创建了一个管道, 并将管道0(写端)置为非阻塞模式

然后程序为信号SIGINT与SIGTERM挂载了自定义的信号处理函数, 信号处理函数做的事如下:

向管道1(写端)写入字符串" "

若写入失败, 则向标准输出写入错误字符串"Err while writing to self-pipe"并调用exit()退出程序

然后将zmq socket与管道1读端均加入zmq_poll

在zmq socket收到请求时, 正常处理请求

在管道1收到数据时, 说明接收到了SIGINT或SIGTERM信号, 则退出数据处理循环, 之后将依次调用zmq_close()与zmq_ctx_destroy()

这种写法使用了管道, 逻辑上清晰了, 代码上繁琐了, 但这都不是重点, 重点是这个版本的服务端程序在接收到SIGINT与SIGTERM时, 虽然也会退出进程, 但在退出之前会妥善的关闭掉zmq socket与zmq context.

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zypgfs.html