How to send both image(ndarray) and string data in single ZMQ send request
Asked Answered
G

1

6

For sending string data, following codes works :

context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:5667")
socket.send_string("my string data")

For sending image(ndarray) following code works :

def send_array(socket, img, flags=0, copy=True, track=False):
    """send a numpy array with metadata"""
    md = dict(
        dtype = str(img.dtype),
        shape = img.shape,
    )
    socket.send_json(md, flags|zmq.SNDMORE)
    return socket.send(img, flags, copy=copy, track=track)

context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:5667")
send_array(socket, my_ndarray_image )

But I do need to send both the string message along with the image file. Is there any way to append the message in the same request ?

Any ideas are welcomed ! Thanks

Genseric answered 29/10, 2018 at 15:46 Comment(3)
What do you mean of "request"? Why don't you add a key, val to you dictionary for sending your image and your string then parse it in SUB side?Belen
@BenyaminJafari By single "request", I meant single message queue request. Whats the need ? ---> As i mentioned, in my application I need to send an image & an associated string message along with it. If I send them in 2 separate requests. Great ! works good for single user instance. But, say there are 2 users who simultaneously run my code. If both requests are done at same time, there's no way to know which Image & String are together. Possible wrong combinations ( IMAGE1 + STRING2 ) instead of correct ( IMAGE1 + STRING1 ). I know, its has gone complicated. Feel free to query back.Genseric
@BenyaminJafari Secondly, adding image & string in key, value doesn't works out, I've already tried. It can't be encoded together at the sender end itself. So, no point I can parse it at SUBSCRIBER sideGenseric
M
8

I guess you are looking for multipart messages, which allow you to compose a message out of several frames. The Python implementation pyzmq already provides us with a nice wrapper for multipart messages. Here is an example for the server sending a multipart message:

context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:5555")

time.sleep(0.2)  # wait for socket to be properly bound

socket.send_multipart([b"first part", b"second part"])

And the client receiving a multipart message:

context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.setsockopt(zmq.SUBSCRIBE, b"")
socket.connect("tcp://localhost:5555")

print(socket.recv_multipart())

Please consider the following when working with multipart messages:

  • All the message parts will only ever be sent after you send the final part.
  • The receiver will always either receive all message parts or none at all.

In your specific example, you are already composing a multipart messages in the send_array function by using the flag zmq.SNDMORE. We can extend your example by also adding the string data with the zmq.SNDMORE flag. Here is the server side:

def send_array_and_str(socket, img, string, flags=0):
    md = dict(dtype = str(img.dtype), shape=img.shape)

    socket.send_string(string, flags | zmq.SNDMORE)
    socket.send_json(md, flags | zmq.SNDMORE)
    return socket.send(img, flags)

context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:5667")
time.sleep(0.2)

my_ndarray = np.array([1, 2, 3])
my_string = "Hello World"
send_array_and_str(socket, my_ndarray, my_string)

And the client code receiving the message:

def recv_array_and_str(socket, flags=0, copy=True, track=False):
    string = socket.recv_string(flags=flags)
    md = socket.recv_json(flags=flags)
    msg = socket.recv(flags=flags, copy=copy, track=track)

    img = np.frombuffer(bytes(memoryview(msg)), dtype=md['dtype'])
    return string, img.reshape(md['shape'])

context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.setsockopt(zmq.SUBSCRIBE, b"")
socket.connect("tcp://localhost:5667")

print(recv_array_and_str(socket))

This code is based on the example Serializing messages with PyZMQ and adapted to work with Python 3. For Python 2, consider using buffer(msg) instead of bytes(memoryview(msg)).

Minh answered 21/11, 2018 at 19:29 Comment(2)
Do you mean was "The receiver will ..." instead of "The sender will always either receive all message parts or none at all."?Belen
@BenyaminJafari exactly, thanks for pointing out this error! :)Minh

© 2022 - 2024 — McMap. All rights reserved.