ZeroMQ:09---基础篇之(IO多路复用:zmq_poll())

一、IO多路复用:zmq_poll()

int zmq_poll (zmq_pollitem_t *items, int nitems, long timeout);
  • API参考文档:http://api.zeromq.org/4-3:zmq-poll
  • 功能:实现I/O多路复用
  • 参数:
    • items:是一种zmq_pollitem_t结构的数组
    • nitems:参数items数组的大小
    • timeout:等待时间。可取的值如下:
      • >0:等待指定的毫秒。指定的毫秒内items中有事件就绪则返回,否则等待到指定的毫秒超时返回
      • 0:不等待,判断items后直接返回
      • -1:永久阻塞等待。直到item上有事件返回
  • 返回值:
    • 成功:
      • >0:返回items中就绪的事件的数量
      • 0:timeout>0时表示zmq_poll()超时返回;timeout=0时表示没有事件就绪
    • 失败:返回-1,并将errno设置为以下定义的值之一:
      • ETERM:至少一个条目数组的成员是指一个套接字ØMQ上下文相关的终止
      • EFAULT:items参数无效(NULL)
      • EINTR:在发生任何事件之前,操作被信号中断了
  • 相关描述:
    • 对于items中的每个项目,zmq_poll()函数会轮询每个zmq_pollitem_t结构中的socket或fd,判断是否有事件返回
    • 如果zmq_pollitem_t同时设置了socket和fd,则zmq_poll()只socket而忽略fd
    • 对于items中的每个项目,zmq_poll()会首先清除revents成员,然后设置每个项目的revents成员对应的事件位标志来表示发生的事 

zmq_pollitem_t结构

typedef struct
{
    void *socket;  //要轮询的ØMQ套接字
    int   fd;      //或者,要轮询的本机文件句柄
    short events;  //要轮询的事件
    short revents; //轮询后返回的事件
} zmq_pollitem_t;
  • socket:要轮询的ØMQ套接字
  • fd:要轮询的本机文件句柄
  • events:要轮询的事件。可取的值如下,可以多个值进行|运算:
    • ZMQ_POLLIN(可读):
      • 对于ØMQ套接字(socket),可以从该套接字上接收到一条消息并且不会阻塞
      • 对于标准套接字(fd),这相当于poll()系统调用的POLLIN标志,通常意味着可以从fd上至少读取一个字节的数据而不阻塞
    • ZMQ_POLLOUT(可写):
      • 对于ØMQ套接字(socket),可以从向该套接字发送一条消息并且不会阻塞
      • 对于标准套接字(fd),这相当于poll()系统调用的POLLOUT标记,意味着至少有一个字节的数据可以在不阻塞的情况下写入fd
    • ZMQ_POLLERR(错误发生):
      • 对于标准套接字(fd),这个标志通过zmq_poll()传递给基础的poll()系统调用,通常意味着在fd指定的套接字上存在某种错误条件
      • 对于ØMQ套接字(socket),这个标志没有任何影响,不会因为ØMQ套接字发生这个事件而导致zmq_poll()返回
    • ZMQ_POLLPRI(紧急数据):
      • 对于ØMQ套接字(socket),这个标志没有任何影响
      • 对于标准套接字(fd),意味着有紧急数据到来。有关更多信息请参考POLLPRI标志。对于文件描述符,请参考您的用例:例如通过POLLPRI事件来发出GPIO中断信号。此标志在Windows上无效
  • revents:当zmq_poll()轮询返回之后,这个成员会自动被设置,表示该事件的就绪状态。可以设置的值与events相同

演示案例(伪代码)

zmq_pollitem_t items [2];

items[0].socket = socket;
items[0].events = ZMQ_POLLIN;

items[1].socket = NULL;
items[1].fd = fd;
items[1].events = ZMQ_POLLIN;

int rc = zmq_poll (items, 2, -1);
assert (rc >= 0); 

二、演示案例

  • 在前面的文章中我们演示过两个演示案例
    • 发布订阅模式:https://blog.csdn.net/qq_41453285/article/details/106005939
    • 流水线模式:https://blog.csdn.net/qq_41453285/article/details/106009184
  • 下面的两个程序完成相同的功能:即作为发布订阅演示案例中的“天气状态更新的订阅者”,又作为流水线演示案例中的“并行任务的工人”。其中第一个演示案例不使用zmq_poll(),第二个演示案例使用zmq_poll()

不使用zmq_poll()实现

//msreader.c
#include 
#include 
#include 

static void s_sleep (int msecs);

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

    // 2.连接到任务发生器
    void *receiver = zmq_socket(context, ZMQ_PULL);
    zmq_connect(receiver, "tcp://localhost:5557");

    // 3.连接到天气服务器
    void *subscriber = zmq_socket(context, ZMQ_SUB);
    zmq_connect(subscriber, "tcp://localhost:5556");
    zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, "10001", 6);


    // 4.处理来自两个套接字的消息, 先处理任务发生器, 再处理天气服务器
    while(1)
    {
        // 5.先处理任务发生器的
        int rc;
        for(rc = 0; !rc; )
        {
            zmq_msg_t task;
            zmq_msg_init(&task);
            if((rc = zmq_msg_recv(&task, receiver, ZMQ_DONTWAIT)) != -1)
            {
                //...处理任务
            }
            zmq_msg_close(&task);
        }

        // 6.再处理天气服务器的
        for(rc = 0; !rc; )
        {
            zmq_msg_t update;
            zmq_msg_init(&update);
            if((rc = zmq_msg_recv(&update, subscriber, ZMQ_DONTWAIT)) != -1)
            {
                //...处理天气状态更新
            }
            zmq_msg_close(&update);
        }

        s_sleep(1);
    }

    // 7.关闭套接字, 销毁上下文
    zmq_close(receiver);
    zmq_close(subscriber);
    zmq_ctx_term(context);
    
    return 0;
}

static void s_sleep (int msecs)
{
#if (defined (WIN32))
    Sleep (msecs);
#else
    struct timespec t;
    t.tv_sec  =  msecs / 1000;
    t.tv_nsec = (msecs % 1000) * 1000000;
    nanosleep (&t, NULL);
#endif
}
  • 编译运行如下,没问题:

使用zmq_poll()实现

//mspoller.c
#include 
#include 
#include 

static void s_sleep (int msecs);

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

    // 2.连接到任务发生器
    void *receiver = zmq_socket(context, ZMQ_PULL);
    zmq_connect(receiver, "tcp://localhost:5557");

    // 3.连接到天气服务器
    void *subscriber = zmq_socket(context, ZMQ_SUB);
    zmq_connect(subscriber, "tcp://localhost:5556");
    zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, "10001", 6);

    // 4.初始化轮询集
    zmq_pollitem_t items[] = {
        // 参数分别为: 轮询的套接字、轮询的本机文件句柄、要轮询的时间、轮询后返回的事件
        { receiver, 0, ZMQ_POLLIN, 0},
        { subscriber, 0, ZMQ_POLLIN, 0}
    };

    // 5.处理来自两个套接字的消息
    while(1)
    {
        zmq_msg_t message;
        // 6.轮询items, 最后一个参数为-1, 因此zmq_poll()会无限期阻塞等待事件
        zmq_poll(items, 2, -1);

        // 7.如果receiver套接字可读, 那么处理receiver套接字
        if(items[0].revents & ZMQ_POLLIN)
        {
            zmq_msg_t(&message);
            zmq_msg_recv(&message, receiver, 0);
            
            //处理任务
            
            zmq_msg_close(&message);
        }

        // 8.如果subscriber套接字可读, 那么处理subscriber套接字
        if(items[1].revents & ZMQ_POLLIN)
        {
            zmq_msg_t(&message);
            zmq_msg_recv(&message, subscriber, 0);
            
            //处理天气更新状态
            
            zmq_msg_close(&message);
        }
    }

    // 7.关闭套接字, 销毁上下文
    zmq_close(receiver);
    zmq_close(subscriber);
    zmq_ctx_term(context);
    
    return 0;
}

static void s_sleep (int msecs)
{
#if (defined (WIN32))
    Sleep (msecs);
#else
    struct timespec t;
    t.tv_sec  =  msecs / 1000;
    t.tv_nsec = (msecs % 1000) * 1000000;
    nanosleep (&t, NULL);
#endif
}
  • 编译运行如下,没问题:

ZeroMQ:09---基础篇之(IO多路复用:zmq_poll())_第1张图片

你可能感兴趣的:(ZeroMQ,IO多路复用,zmq_poll)