ZMQ DEALER - ROUTER Communication

后端 未结 2 1159
萌比男神i
萌比男神i 2020-12-09 23:39

I am currently working on a project that requires some communication over the network of a different data types from some entities of a distributed system and I am using ZMQ

相关标签:
2条回答
  • 2020-12-10 00:21

    The ( prematurely ) awarded answer does not meet defined properties.

    Distributed systems need to operate both smart and efficiently, as the agents are distributed and both error-analyses and deployed production-issues are extremely expensive to analyse / test / debug.

    Thus a copy/paste re-use of a problem-incompatible idea is not a way to achieve either the former, the less the latter.


    So, let's review the efficiency first:

    client-[A].send()-s a message, that O/P wanted to become server-side-[S].recv()-ed and re-broadcast to all other clients-[B,C,...], except the [A]-itself.

    The most resources-efficient approach to this is to properly configure the infrastructure tools for doing exactly this, without re-inventing wheel and/or using a fragile and performance-devastating scaffolding code(s).

    So:

    on the client-[*] side best use the below sketched primitive agent-concept. More complex setups, like using as clever event-handling facilities as Tkinter has evolved to have packed into the .mainloop() soft-real-time system, are better, yet it is not as easy to start design-battles on more than one front, so let's rather keep things simpler at this moment:

    zmq_VERSION      = zmq.zmq_version_info()
    anAgentsIDENTITY = whateverHashOrHumanReadableSTRING
    notMINE          = anAgentsIDENTITY
    
    if     zmq_VERSION[0] < 4:
               print "ZMQ{0:} ver < than expected, will exit".format( zmq_VERSION )
    aCTX = zmq.Context( 2 )                        # if performance boosting is needed
    
    #SUB ---------------------------------------------------------------------------
    aSUB = aCTX.socket( zmq.SUB )
    aSUB.setsockopt(    zmq.LINGER,          0 )   # protect your agent
    aSUB.setsockopt(    zmq.MAXMSGSIZE,      m )   # protect your agent from DoS
    aSUB.setsockopt(    zmq.AFFINITY,        1 )   # protect your server resources
    aSUB.setsockopt(    zmq.HEARTBEAT_IVL,   ivl ) #     set server helping Heartbeats
    aSUB.setsockopt(    zmq.HEARTBEAT_TTL,   ttl ) #     set server helping Heartbeats
    aSUB.setsockopt(    zmq.INVERT_MATCHING, 1 )   #   avoid server sending data back
    aSUB.setsockopt(    zmq.SUBSCRIBE,       notMINE )  #  NEVER .recv()-s  data back
    ...
    #SUB PERFORMANCE & RESOURCES TWEAKING DETAILS GO WAY BEYOND THE SCOPE OF THIS POST
    
    aSUB.connect(      "tcp://localhost:5557" )
    
    #PUSH --------------------------------------------------------------------------
    aPUSH = aCTX.socket( zmq.PUSH )
    ...
    #PUSH PERFORMANCE & RESOURCES TWEAKING DETAILS GO WAY BEYOND THE SCOPE OF THIS POST
    
    #main loop ---------------------------------------------------------------------
    pass; notSoftFLAG = True; anAgentSignsWithIdentityPREFIX = anAgentsIDENTITY
    while notSoftFLAG:
    
        if aReasonToSendSomethingToServer:
           aPUSH.send( anAgentSignsWithIdentityPREFIX
                     + ":::"
                     + aMsgPAYLOAD,
                       zmq.DONTWAIT
                       )                          # inspect ZMQError
           ...
           pass
    
        if aSUB.poll( 100 ):
           message = aSUB.recv( zmq.DONTWAIT )    #  NEVER .recv()-s own data back
           ...
           pass
    
    
        if aReasonToFlagLoopEXIT:
           notSoftFLAG = False
           ...
           pass
    
        if ...:
           ...
           pass
    
    #main loop ---------------------------------------------------------------------
    pass
    
    #########
    # ALWAYS:
    #          better using context-aware try:/except:/finally:
    
    aRetCODE = [ aSOCK.close() for aSOCK in ( aSUB, aPUSH, ) ]
    ...
    
    aCTX.term()
    #   .term()
    #########
    

    Server can avoid ALL hassles with any need for any ad-hoc handling:

    all being well tuned inside the ZeroMQ infrastructure:

    pass;  zmq_VERSION = zmq.zmq_version_info()
    if     zmq_VERSION[0] < 4:
               print "ZMQ{0:} ver < than expected, will exit".format( zmq_VERSION )
    
    aCTX = zmq.Context( 2 )                        # if performance boosting is needed
    
    #PUB ---------------------------------------------------------------------------
    aPUB = aCTX.socket( zmq.PUB )
    aPUB.setsockopt(    zmq.LINGER,          0 )   # protect your server
    aPUB.setsockopt(    zmq.MAXMSGSIZE,      m )   # protect your server from DoS
    aPUB.setsockopt(    zmq.AFFINITY,        3 )   # protect your server resources
    aPUB.setsockopt(    zmq.HEARTBEAT_IVL,   ivl ) #     server L3-helper Heartbeats
    aPUB.setsockopt(    zmq.HEARTBEAT_TTL,   ttl ) #     server L3-helper Heartbeats
    aPUB.setsockopt(    zmq.INVERT_MATCHING, 1 )   #   avoid server sending data back
    aPUB.setsockopt(    zmq.IMMEDIATE,       1 )   # avoid Queueing for dead-ends
    aPUB.setsockopt(    zmq.TOS,             tos ) # allow for L3-router TOS-policies
    ...
    #PUB PERFORMANCE & RESOURCES TWEAKING DETAILS GO WAY BEYOND THE SCOPE OF THIS POST
    aPUB.bind(   "tcp://*:5557" )                  # expose AccessPoint on tcp://
    
    #PULL --------------------------------------------------------------------------
    aPULL = aCTX.socket( zmq.PULL )
    aPULL.setsockopt(    zmq.LINGER,          0 )  # protect your server
    aPULL.setsockopt(    zmq.MAXMSGSIZE,      m )  # protect your server from DoS
    aPULL.setsockopt(    zmq.AFFINITY,        3 )  # protect your server resources
    aPULL.setsockopt(    zmq.HEARTBEAT_IVL,   ivl )#     server L3-helper Heartbeats
    aPULL.setsockopt(    zmq.HEARTBEAT_TTL,   ttl )#     server L3-helper Heartbeats
    ...
    #PULL PERFORMANCE & RESOURCES TWEAKING DETAILS GO WAY BEYOND THE SCOPE OF THIS POST
    aPULL.bind(   "tcp://*:5558" )                 # expose AccessPoint on tcp://
    ...
    
    #main loop ---------------------------------------------------------------------
    pass; notSoftFLAG = True
    while notSoftFLAG:
        NOP_SLEEP = 10                            #  set a 10 [ms] sleep in case NOP
        if aPULL.poll( 0 ):                       #  NEVER block/wait
           aMSG = aPULL.recv( zmq.DONTWAIT )      #  NEVER .recv()-s own data back
           #CPY = zmq_msg_copy( &aMSG );          // WARNING ABOUT NATIVE C-API
           #                                      // HANDLING, NEED .COPY()
           #                                      //           NEED .CLOSE()
           aPUB.send( aMSG,   zmq.DONTWAIT )      #  re-PUB-lish to all others but sender
           ...< process aMSG payload on server-side, if needed >...
    
           NOP_SLEEP = 0                          # !NOP, avoid 10[ms] NOP-loop sleep
           pass
    
        if aReasonToFlagLoopEXIT:
           notSoftFLAG = False
           ...
           NOP_SLEEP = 0
           pass
    
        if ...:
           ...
           pass
    
        sleep( NOP_SLEEP )                        # a soft-real-time controlled sleep on NOP
    #main loop ---------------------------------------------------------------------
    pass
    
    #########
    # ALWAYS:
    #          better using context-aware try:/except:/finally:
    
    aRetCODE = [ aSOCK.close() for aSOCK in ( aPUB, aPULL, ) ]
    ...
    
    aCTX.term()
    #   .term()
    #########
    
    0 讨论(0)
  • 2020-12-10 00:25

    You could have all clients send a "I am here" message at start-up. The central server could then store all the IDs, c.f. the initial communication between worker and router in here: http://zguide.zeromq.org/page:all#A-Load-Balancing-Message-Broker. The server would send out any received message to all currently known clients. You should add some heart beating in order to detect disconnected clients, c.f. http://zguide.zeromq.org/page:all#Heartbeating.

    However, ZeroMQ already comes with such a communication pattern: PUBSUB. In essence every client would have a DEALER and a SUB socket connected to the servers ROUTER and PUB sockets. The server simply sends out any received message via the PUB socket to all clients. If this would be a problem for the originating client, you can include the client ID in the message so that each client can filter out messages with their own ID. See also this example from the guide http://zguide.zeromq.org/page:all#Getting-an-Out-of-Band-Snapshot

    Another interesting pattern would be Republishing Updates from Clients:

    Here PUSH--PULL is used to send the updates to the server. This makes sense if there is no need for a reply message from the server. If you do not need the state request from that example, you can leave out the ROUTER--DEALER part. Here a sample implementation using Python for brevity. The server listens to the PULL socket and sends out everything via the PUB socket:

    import zmq
    
    def main():
        # context and sockets
        ctx = zmq.Context()
        publisher = ctx.socket(zmq.PUB)
        publisher.bind("tcp://*:5557")
        collector = ctx.socket(zmq.PULL)
        collector.bind("tcp://*:5558")
    
        while True:
            message = collector.recv()
            print "I: publishing update %s" % message
            publisher.send(message)
    
    if __name__ == '__main__':
        main()
    

    The client listens to the PUB socket for some time. If a message is received it is logged. If the timeout is reached, a message is generated with a 1 in 10 chance:

    import random
    import time
    
    import zmq
    
    def main():
    
        # Prepare our context and subscriber
        ctx = zmq.Context()
        subscriber = ctx.socket(zmq.SUB)
        subscriber.setsockopt(zmq.SUBSCRIBE, '')
        subscriber.connect("tcp://localhost:5557")
        publisher = ctx.socket(zmq.PUSH)
        publisher.connect("tcp://localhost:5558")
    
        random.seed(time.time())
        while True:
            if subscriber.poll(100) & zmq.POLLIN:
                message = subscriber.recv()
                print "I: received message %s" % message
            else:
                rand = random.randint(1, 100)
                if rand < 10:
                    publisher.send("%d" % rand)
                    print "I: sending message %d" % rand
    
    if __name__ == '__main__':
        main()
    
    0 讨论(0)
提交回复
热议问题