ZeroMQ:12---基础篇之(ØMQ的信号处理)

一、ØMQ的信号处理

  • 在应用程序运行时会收到各种各样的信号,如SIGTERM中断信号。默认情况下,这些信号简单地杀掉进程,这回造成信息不会被刷新、文件将无法完全关闭等一系列问题
  • ØMQ对信号的处理:ØMQ对信号的处于与POSIX C一样,都是使用相同的信号处理接口来对信号进行处理
  • 关于POSIX C的信号处理系列文章可以参阅:https://blog.csdn.net/qq_41453285/category_8841776.html
  • ØMQ的接口在接收到信号时一般有如下的返回情况:
    • 对于系统自带的接口,例如zmq_msg_recv()、zmq_msg_send()、zmq_poll(),如果在阻塞期间接收到信号,那么这些函数会出错返回,并且将errno设置为EINTR
    • 如果是自己封装的接口,例如s_recv()等函数,那么会返回NULL。关于s_recv()参阅:https://blog.csdn.net/qq_41453285/article/details/105991716

二、信号处理演示案例1

  • 下面的程序的大致流程是:
    • 在程序处理数据之前,调用s_catch_signal()函数初始化对消息的处理,信号处理函数为s_signal_handler()
    • 我们添加了对SIGINT信号(接收到Ctrl+C中断时产生)和SIGTERM信号(接收到kill命令产生)的处理
    • 设置一个s_interrupted标志,默认初始化为0,当接受到信号时在信号处理函数s_signal_handler()中将s_interrupted设置为1
    • 在主程序的while(1)循环中一直接收消息,当判断s_interrupted为1时说明接收到信号了,那么就退出程序自己去主动清除关闭一些列资源

代码如下

//interrupt1.c
#include <stdio.h>
#include <zmq.h>
#include <signal.h>

// 表示是否接收到信号, 默认为0表示没有接收到信号
static int s_interrupted = 0;

// 信号处理函数, 在内部将s_interrupted设置为1
static void s_signal_handler(int signal_value)
{
    s_interrupted = 1;
}

void s_catch_signal()
{
    // 信号处理结构体, 关于这些接口如何使用请参阅: https://blog.csdn.net/qq_41453285/article/details/89278415
    struct sigaction action;
    action.sa_handler = s_signal_handler; //信号处理函数
    action.sa_flags = 0;                  //对信号进行哪些特殊处理
    sigemptyset(&action.sa_mask);         //将信号屏蔽集清空,

    // 添加对SIGINT、SIGTERM信号的处理
    sigaction(SIGINT, &action, NULL);
    sigaction(SIGTERM, &action, NULL);
}

int main()
{
    // 初始化上下文
    void *context = zmq_ctx_new();

    // 创建绑定套接字
    void *socket = zmq_socket(context, ZMQ_REP);
    zmq_bind(socket, "tcp://*:5555");

    // 添加信号处理函数
    s_catch_signal();

    // 循环对消息进行处理
    while(1)
    {
        zmq_msg_t msg;
        zmq_msg_init(&msg);
        // recv会阻塞读, 当接收到信号时会返回-1停止阻塞, 我们不会其返回值做判断, 直接对s_interrupted对判断
        zmq_msg_recv(&msg, socket, 0);

        // 如果接收到信号, 那么s_interrupted就会变为1, 那么就退出循环, 清除退出程序
        if(s_interrupted)
        {
            printf("interrupt received, killing server...\n");
            break;
        }
    }

    // 关闭消息、销毁上下文
    zmq_close(socket);
    zmq_ctx_destroy(context);

    return 0;
}
  • 演示运行效果如下,我们在程序运行时按下“Ctrl+c”中断程序:

  • 文章一开始我们说过了系统自带接口在收到信号时errno会被设置为EINTR,自定义的接口会返回NULL。因此结合本演示案例和接口特性,下面是一个典型的信号处理代码片段:
s_catch_signals();
client = zmq_socket(...);
while(!s_interrupted)
{
    char *message = s_recv(client);
    if(!message)
        break;
}
zmq_close(client);

二、信号处理演示案例2

  • 下面的演示案例与上面的演示案例差不多,不过就是添加了一个管道而已。大致流程如下:
    • 在程序处理数据之前,为程序绑定信号处理函数
    • 程序创建了一个无名管道和一个用来处理数据的套接字,然后使用zmq_poll()轮询管道的读端和套接字
    • 在信号处理函数中,如果接收到信号,那么就向管道的写端发送一个字符表示有信号来了,此时zmq_poll()轮询到管道的读端有数据来,那么就退出程序

代码如下

// interrupt2.c
#include <stdio.h>
#include <zmq.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.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)
{
    //向管道的写端写入数据, 表示接收到信号了, 那么在zmq_poll()中轮询的管道的读端就会收到退出信息, 进而清除程序退出
    int rc = write(s_fd, S_NOTIFY_MSG, 1);
    // 如果此处写失败了, 那么管道的读端就不会知道有信号来了, 从而导致信号处理丢失, 因此此处我们需要自己退出程序, 但是丢失了对资源的关闭清除
    if(rc == -1)
    {
        write(STDOUT_FILENO, S_ERROR_MSG, sizeof(S_ERROR_MSG) - 1);
        exit(EXIT_FAILURE);
    }
}

// 初始化信号处理
void s_catch_signal(int fd)
{
    // 将管道的写端赋值给s_fd
    s_fd = fd;
    
    // 信号处理结构
    struct sigaction action;
    //信号处理函数
    action.sa_handler = s_signal_handler;
    // 标志,不需要设置为SA_RESTART(由此信号中断的系统调用自动重启动), 因为我们已经对各种函数的返回值进行了EINTR的判断, 如果函数返回EINTR我们一律不报错继续运行
    action.sa_flags = 0;                  
    sigemptyset(&action.sa_mask);

    // 添加对SIGINT和SIGTERM信号的处理
    sigaction(SIGINT, &action, NULL);
    sigaction(SIGTERM, &action, NULL);
}

int main()
{
    // 初始化上下文
    void *context = zmq_ctx_new();

    // 初始化绑定套接字
    void *socket = zmq_socket(context, ZMQ_REP);
    zmq_bind(socket, "tcp://*:5555");


    // 创建管道,
    // pipefds[0]是读端, pipefds[1]是写端. 向pipefds[1]中写入数据, 在pipefds[0]中会读取到
    int pipefds[2];
    int rc;
    rc = pipe(pipefds);
    if(rc == -1)
    {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    // 将管道的写端送入信号处理函数中, 如果有信号来就会向管道的写端写入数据
    s_catch_signal(pipefds[1]);
    
    // 将管道的读端设置为非阻塞的
    int flags = fcntl(pipefds[0], F_GETFL, 0);
    if(flags == -1)
    {
        perror("fcntl");
        exit(EXIT_FAILURE);
    }
    rc = fcntl(pipefds[0], F_SETFL, flags | O_NONBLOCK);
    if(rc == -1)
    {
        perror("fcntl");
        exit(EXIT_FAILURE);
    }

    // 将管道的读端和套接字加入zmq_poll中
    zmq_pollitem_t items[2] = {
        { NULL, pipefds[0], ZMQ_POLLIN, 0},
        { socket, 0, ZMQ_POLLIN, 0}
    };

    // 轮询数据
    while(1)
    {
        // zmq_poll为阻塞模式
        rc = zmq_poll(items, 2, -1);
        if(rc == -1)
        {
            if(errno == EINTR) { continue; } //如果是阻塞期间接收到了信号, 那么继续进行zmq_poll, 在下次一次zmq_poll的时候管道读端就会变为可读状态
            perror("zmq_poll");
            exit(EXIT_FAILURE);
        }
        else if(rc == 0)
        {
            continue;
        }
        else
        {
            // 如果管道的读端可读, 那么说明有信号来了, 因为在信号处理函数中会向管道的写端写入数据
            if(items[0].revents & ZMQ_POLLIN)
            {
                // 读取之后退出循环, 做清除工作
                char buff[1];
                read(items[0].fd, buff, 1); //此处读取一次, 将管道里的消息读取出来清除
                printf("Interrupt received, killing server...\n");
                break;
            }

            // 如果套接字可读
            if(items[1].revents & ZMQ_POLLIN)
            {
                char buff[255];
                // 使用非阻塞读取, 这样我们就不会因为阻塞而错过管道里的消息了
                rc = zmq_recv(items[1].socket, buff, sizeof(buff), ZMQ_DONTWAIT);
                if(rc = -1)
                {
                    // 如果zmq_recv()读取失败, 那么判断一下返回值
                    if(errno == EAGAIN) { continue; } // EAGIN表示zmq_recv()是在非阻塞模式下返回的且没有消息可读, 那么继续进行zmq_poll
                    if(errno == EINTR) { continue; } // 如果是信号打断的, 那么继续进行zmq_poll, 信号处理函数会对信号处理, 此处无须关心
                    perror("zmq_recv");
                    exit(EXIT_FAILURE);
                }

                printf("Recv: %s\n", buff);

                // 之后发送数据等等, 此处忽略
            }
        }
    }

    // 关闭套接字、销毁上下文
    printf("Cleaning up...\n");
    zmq_close(socket);
    zmq_ctx_destroy(context);
    
    return 0;
}
  • 演示运行效果如下,我们在程序运行时按下“Ctrl+c”中断程序:

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/106824228