I want to create a server that will handle a request / response scheme. The client of that server should be able to send multiple requests at one time (so REQ/REP does not fit). It should be like having a simple grpc server, a client is able send multiple requests and then wait on all responses.
I came into a few options. I saw that router-dealer can replace REP-REQ combo. This simple example I wrote does work. Here the client (dealer) sends 100 requests at the same time. (just as an example, in my real app I recv()
one request and send()
one response)
router.py
context = zmq.Context()socket = context.socket(zmq.ROUTER)port = 6000socket.bind(f"tcp://*:{port}")print(f"Listening on port :{port}")while True: for i in range(100): client_id = socket.recv() msg = socket.recv() print("Got ", msg) socket.send(client_id, zmq.SNDMORE) socket.send_string(f"Hello from router. Your message: {msg}")
dealer.py
port = 6000context = zmq.Context()socket = context.socket(zmq.DEALER)socket.connect(f"tcp://localhost:{port}")while True: for i in range(100): socket.send(f"hello from dealer1 {i}".encode()) time.sleep(1) msg = socket.recv() print("Got from server=", msg)
In a real app I need to know how to send / recv at the same time with the ROUTER socket.Say my app receives a message, send it to a background thread, then my app need to somehow send() the response. Not sure how to do that (maybe with poll()
?)
However, In the official guide https://zguide.zeromq.org/docs/chapter2/#sockets-and-patterns
I see this example:
import timeimport threadingimport zmqdef worker_routine(worker_url: str, context: zmq.Context = None):"""Worker routine""" context = context or zmq.Context.instance() # Socket to talk to dispatcher socket = context.socket(zmq.REP) socket.connect(worker_url) while True: string = socket.recv() print(f"Received request: [ {string} ]") # Do some 'work' time.sleep(1) # Send reply back to client socket.send(b"World")def main():"""Server routine""" url_worker = "inproc://workers" url_client = "tcp://*:5555" # Prepare our context and sockets context = zmq.Context.instance() # Socket to talk to clients clients = context.socket(zmq.ROUTER) clients.bind(url_client) # Socket to talk to workers workers = context.socket(zmq.DEALER) workers.bind(url_worker) # Launch pool of worker threads for i in range(5): thread = threading.Thread(target=worker_routine, args=(url_worker,)) thread.daemon = True thread.start() zmq.proxy(clients, workers) # We never get here but clean up anyhow clients.close() workers.close() context.term()if __name__ == "__main__": main()
Is the last implementation is more efficient than the other? i.e using one router socket to send / recv (probably with
zmq.poll()
), or using this pattern. Is there a cost involves with the inproc socket (serialization?), how does it works withzmq.proxy()
?what is a better way of the two to implement client - server like I need?
In the last implementation - can the server recv requests and send responses in parallel?
Performance wise, how the last implementation compares with having pub/sub socket on each side - i.e server will use sub socket to get a request and pub socket to return a response. Client will also have the same pair of sockets. That will require two ports which is less convenient. But this way the server can send / recv in parallel (which I'm not sure is possible with the first implementation).