• ZeroMQ_07 请求-应答代理


    下面让我们在请求-应答模式中编写一个小型的消息队列代理装置。

    在Hello World客户/服务模型中,一个客户端和一个服务端进行通信。但在真实环境中,我们会需要让多个客户端和多个服务端进行通信。关键问题在于,服务端应该是无状态的,所有的状态都应该包含在一次请求中,或者存放其它介质中,如数据库。

    我们有两种方式来连接多个客户端和多个服务端。第一种是让客户端直接和多个服务端进行连接。客户端套接字可以连接至多个服务端套接字,它所发送的请求会通过负载均衡的方式分发给服务端。比如说,有一个客户端连接了三个服务端,A、B、C,客户端产生了R1、R2、R3、R4四个请求,那么,R1和R4会由服务A处理,R2由B处理,R3由C处理:

    这种设计的好处在于可以方便地添加客户端,但若要添加服务端,那就得修改每个客户端的配置。如果你有100个客户端,需要添加三个服务端,那么这些客户端都需要重新进行配置,让其知道新服务端的存在。

    这种方式肯定不是我们想要的。一个网络结构中如果有太多固化的模块就越不容易扩展。因此,我们需要有一个模块位于客户端和服务端之间,将所有的知识都汇聚到这个网络拓扑结构中。理想状态下,我们可以任意地增减客户端或是服务端,不需要更改任何组件的配置。

    下面就让我们编写这样一个组件。这个代理会绑定到两个端点,前端端点供客户端连接,后端端点供服务端连接。它会使用zmq_poll()来轮询这两个套接字,接收消息并进行转发。装置中不会有队列的存在,因为ZMQ已经自动在套接字中完成了。

    在使用REQ和REP套接字时,其请求-应答的会话是严格同步。客户端发送请求,服务端接收请求并发送应答,由客户端接收。如果客户端或服务端中的一个发生问题(如连续两次发送请求),程序就会报错。

    但是,我们的代理装置必须要是非阻塞式的,虽然可以使用zmq_poll()同时处理两个套接字,但这里显然不能使用REP和REQ套接字。

    幸运的是,我们有DEALER和ROUTER套接字可以胜任这项工作,进行非阻塞的消息收发。DEALER过去被称为XREQ,ROUTER被称为XREP,但新的代码中应尽量使用DEALER/ROUTER这种名称。在第三章中你会看到如何用DEALER和ROUTER套接字构建不同类型的请求-应答模式。

    下面就让我们看看DEALER和ROUTER套接字是怎样在装置中工作的。

    下方的简图描述了一个请求-应答模式,REQ和ROUTER通信,DEALER再和REP通信。ROUTER和DEALER之间我们则需要进行消息转发:

     请求-应答代理会将两个套接字分别绑定到前端和后端,供客户端和服务端套接字连接。在使用该装置之前,还需要对客户端和服务端的代码进行调整。

    Client:

    #include "../zhelpers.h"
    
    int main (void) 
    {
        void *context = zmq_ctx_new ();
    
        //  Socket to talk to server
        void *requester = zmq_socket (context, ZMQ_REQ);
        zmq_connect (requester, "tcp://localhost:5559");
    
        int request_nbr;
        for (request_nbr = 0; request_nbr != 10; request_nbr++) {
            s_send (requester, "Hello");
            char *string = s_recv (requester);
            printf ("Received reply %d [%s]
    ", request_nbr, string);
            free (string);
        }
        zmq_close (requester);
        zmq_ctx_destroy (context);
        return 0;
    }

    Server:

    #include "../zhelpers.h"
    #include <unistd.h>
    
    int main (void) 
    {
        void *context = zmq_ctx_new ();
    
        //  Socket to talk to clients
        void *responder = zmq_socket (context, ZMQ_REP);
        zmq_connect (responder, "tcp://localhost:5560");
    
        while (1) {
            //  Wait for next request from client
            char *string = s_recv (responder);
            printf ("Received request: [%s]
    ", string);
            free (string);
    
            //  Do some 'work'
            sleep (1);
    
            //  Send reply back to client
            s_send (responder, "World");
        }
        //  We never get here, but clean up anyhow
        zmq_close (responder);
        zmq_ctx_destroy (context);
        return 0;
    }

    Broker

    #include "../zhelpers.h"
    
    int main (void) 
    {
        //  Prepare our context and sockets
        void *context = zmq_ctx_new ();
        void *frontend = zmq_socket (context, ZMQ_ROUTER);
        void *backend  = zmq_socket (context, ZMQ_DEALER);
        zmq_bind (frontend, "tcp://*:5559");
        zmq_bind (backend,  "tcp://*:5560");
    
        //  Initialize poll set
        zmq_pollitem_t items [] = {
            { frontend, 0, ZMQ_POLLIN, 0 },
            { backend,  0, ZMQ_POLLIN, 0 }
        };
        //  Switch messages between sockets
        while (1) {
            zmq_msg_t message;
            zmq_poll (items, 2, -1);
            if (items [0].revents & ZMQ_POLLIN) {
                while (1) {
                    //  Process all parts of the message
                    zmq_msg_init (&message);
                    zmq_msg_recv (&message, frontend, 0);
                    int more = zmq_msg_more (&message);
                    zmq_msg_send (&message, backend, more? ZMQ_SNDMORE: 0);
                    zmq_msg_close (&message);
                    if (!more)
                        break;      //  Last message part
                }
            }
            if (items [1].revents & ZMQ_POLLIN) {
                while (1) {
                    //  Process all parts of the message
                    zmq_msg_init (&message);
                    zmq_msg_recv (&message, backend, 0);
                    int more = zmq_msg_more (&message);
                    zmq_msg_send (&message, frontend, more? ZMQ_SNDMORE: 0);
                    zmq_msg_close (&message);
                    if (!more)
                        break;      //  Last message part
                }
            }
        }
        //  We never get here, but clean up anyhow
        zmq_close (frontend);
        zmq_close (backend);
        zmq_ctx_destroy (context);
        return 0;
    }

    out:

    // client
    Received reply 0 [World]
    Received reply 1 [World]
    Received reply 2 [World]
    Received reply 3 [World]
    Received reply 4 [World]
    Received reply 5 [World]
    Received reply 6 [World]
    Received reply 7 [World]
    Received reply 8 [World]
    Received reply 9 [World]
    
    // server
    Received request: [Hello]
    Received request: [Hello]
    Received request: [Hello]
    Received request: [Hello]
    Received request: [Hello]
    Received request: [Hello]
    Received request: [Hello]
    Received request: [Hello]
    Received request: [Hello]
    Received request: [Hello]

    使用请求-应答代理可以让你的C/S网络结构更易于扩展:客户端不知道服务端的存在,服务端不知道客户端的存在。网络中唯一稳定的组件是中间的代理装置:

     这里运行多个客户端看一下效果,同时改一下客户端服务端代码:

    // client
    for (request_nbr = 0; request_nbr != 10; request_nbr++) {
            char strDst[256] = {0};
            snprintf(strDst,256,"Hello %d",request_nbr);
            s_send (requester, strDst);
            char *string = s_recv (requester);
            printf ("Received reply %d [%s]
    ", request_nbr, string);
            free (string);
        }
    
    // server
    
     while (1) {
            //  Wait for next request from client
            char *string = s_recv (responder);
            printf ("Received request: [%s]
    ", string);
            
    
            //  Do some 'work'
            sleep (1);
    
            //  Send reply back to client
            s_send (responder, string);
    
            free (string);
        }

    out:

    // client 1
    Received reply 0 [Hello 0]
    Received reply 1 [Hello 1]
    Received reply 2 [Hello 2]
    Received reply 3 [Hello 3]
    Received reply 4 [Hello 4]
    Received reply 5 [Hello 5]
    Received reply 6 [Hello 6]
    Received reply 7 [Hello 7]
    Received reply 8 [Hello 8]
    Received reply 9 [Hello 9]
    
    // client 2
    Received reply 0 [Hello 0]
    Received reply 1 [Hello 1]
    Received reply 2 [Hello 2]
    Received reply 3 [Hello 3]
    Received reply 4 [Hello 4]
    Received reply 5 [Hello 5]
    Received reply 6 [Hello 6]
    Received reply 7 [Hello 7]
    Received reply 8 [Hello 8]
    Received reply 9 [Hello 9]
    
    // server
    Received request: [Hello 0]
    Received request: [Hello 1]
    Received request: [Hello 2]
    Received request: [Hello 0]
    Received request: [Hello 3]
    Received request: [Hello 1]
    Received request: [Hello 4]
    Received request: [Hello 2]
    Received request: [Hello 5]
    Received request: [Hello 3]
    Received request: [Hello 6]
    Received request: [Hello 4]
    Received request: [Hello 7]
    Received request: [Hello 5]
    Received request: [Hello 8]
    Received request: [Hello 6]
    Received request: [Hello 9]
    Received request: [Hello 7]
    Received request: [Hello 8]
    Received request: [Hello 9]

    可以看到客户端可以工作的很好,zmq帮我们正确的代理了消息。

  • 相关阅读:
    有7g和2g的砝码各一个,怎样称可以3次把140g东西分为50g和90g???????
    中缀到后缀(一个例子)
    动态代理模式的使用
    代理模式用来初始化的延迟下载
    ReentrantLock Condition 实现消费者生产者问题
    Two Sum
    [leetcode]重建二叉树(先序和终须) 中序遍和后续
    (转载)旋转数组查找 最简洁方法 总结
    [不明觉厉] 下一个排列
    codeforces -- 283A
  • 原文地址:https://www.cnblogs.com/vczf/p/12852286.html
Copyright © 2020-2023  润新知