jeffbass / imagezmq

A set of Python classes that transport OpenCV images from one computer to another using PyZMQ messaging.
MIT License
1.01k stars 160 forks source link

client to server timeout #16

Open julio16101 opened 4 years ago

julio16101 commented 4 years ago

It is possible to set a waiting time on the client when sending video frames to the server: sender.send_jpg ("example", frame) where if the server for any reason is not available, the client will remain in the loop of connection to the server.

Could a connection attempt time be established as 5 seconds where if no connection can be established, another process is aborted or executed?

I have seen that it could be implemented using the select module.

jeffbass commented 4 years ago

Yes, you can set a timeout on the client when sending frames to the server. It is how my imagenode software works. I use a try / except block with a patience() function that times out after a preselected time. Here is the core of it:

                try:
                    with Patience(settings.patience):
                        text, image = node.send_q.popleft()
                        hub_reply = node.send_frame(text, image)
                except Patience.Timeout:  # if no timely response from hub
                    log.info('No imagehub reply for '
                        + str(int(settings.patience)) + ' seconds')
                    hub_reply = node.fix_comm_link()
                node.process_hub_reply(hub_reply)

The above code is in imagenode.py.

The Patience class is defined here.

In my own case in imagenode, I restart the client if the Patience() amount is exceeded, but you could do many other things instead.

julio16101 commented 4 years ago

Oh thanks, it works fine. Another question, is it possible to have two servers running on the same computer? you will see I want to execute certain processes but they are somewhat delayed and I have to apply them in different video streams, so to speed it up I would like to receive video from one client on one server and the video from another client on another server, but on the same computer.

I tried executing the video reception .py file twice but I get the error that one is already running on that port, I tried to change the port but I cannot specify which server to point to.

is this possible? have two servers listening on the same computer? and receive certain video segments in one and the other?

Thank you.

jeffbass commented 4 years ago

Yes, you can have multiple image receiving servers on the same computer. I do this a lot. You will have to run each server using a different port (I use 5555, 5556, 5557, but any unused port number will do). The client that is sending to each server must have its port number changed to match the port number of the server that it is sending to. You can, as always, have multiple clients sending to the same server, but all the clients must have the same port number as the server they are sending to. I have run as many as 3 servers on the same computer, receiving images from 8 clients each.

julio16101 commented 4 years ago

Thanks. adding, I have mounted a connection of this type in an azure virtual machine, the video transmission is carried out successfully, the bad thing is that few FPS are transmitted, around <8, increase the upload bandwidth and added a compression in the video frames and the result is the same, after verifications I saw that latency was a problem, so I moved the server to a closer position, where I have a latency of 80-90 ms, even so the speed of FPS delivery is one of the <8 that I mentioned, locally I even reach 40 FPS, I know that it is not the same local as on the Internet, but even so, I have an acceptable upload bandwidth and I do compression to speed up the delivery, in addition I have noticed that I do not consume all my upload bandwidth when sending the video.

Is it possible to make a configuration with respect to the tcp protocol to speed up this shipment or reduce the response time of the requests? In forums I have seen that some configurations can be applied but I am not sure about the real impact on transmissions of this type.

jeffbass commented 4 years ago

I don't know anything about changing configurations to speed up the tcp protocol. But I would love to find out if you learn anything! There are others who are using imagzmq who would love to learn more about this, I'm sure. If you do find out something, let me know and I will write it up and add a section to the documentation to explain it. Thanks, Jeff

jeffbass commented 4 years ago

I've added more discussion of your 2 questions above to the FAQ in the documentation. Closing this issue, but let me know if you have further questions.

saerhan commented 3 years ago

Is there a way to use try-with-except with imagezmq only (simple device to device image transfer)? I don't want to use imagenode and there is no quick way to utilize Patience and Settings without imagenode.py.

jeffbass commented 3 years ago

The short answer is no, there is no way to use a try-with-exceptwith imageZMQ only. The current version of imageZMQ does not have any timeout checking options in its API.

You can, however, add ZMQ options to your code that calls imageZMQ, including ZMQ timeout options:

# open ZMQ link using default REQ/REP messaging pattern
sender = imagezmq.ImageSender(connect_to=hub_address) 
sender.zmq_socket.setsockopt(zmq.LINGER, 0)  # prevents ZMQ hang on exit
sender.zmq_socket.setsockopt(zmq.RCVTIMEO, 2000 )  # will raise a ZMQError exception after 2 seconds
sender.zmq_socket.setsockopt(zmq.SNDTIMEO, 2000 )  # will raise a ZMQError exception after 2 seconds

# Then, in your code that sends messages, use a try-except block like this:

while True:
    try:
        hub_reply = sender.send_jpg(text, jpg_buffer)  # or use sender.send_image()
    except:  # or specifically list zmq.ZMQError, zmq.ContextTerminated & perhaps others
        # code to recover from timeout would go here
        # you could close and restart the sender or otherwise restart the program        

(You would need to set all 3 socket options above to catch possible timeouts. I have not fully tested the above code yet, but it seems to work OK with some quick tests).

I know of one imageZMQ user @youngsoul who forked imageZMQ and modified the ImageSender class to raise a ZMQ exception for a timeout on sending or receiving. You can see the code here. I mention @youngsoul's code in the Helpful Forks section of the imageZMQ README.

If you use @youngsoul's code, you would need to include a try-with-except block in your own code that checks for the exception being raised. You can see an example of how @youngsoul did that here.

Here are some additional alternative approaches that have worked for me and for other ZMQ users:

  1. In my own most recent version of imagenode, I have used a method of saving a timestamp before and after each call to sender.send_jpg(text, jpg_buffer) in lines 326-347 of imagenode. I then check for an excessive amount of time between the timestamps in lines 271-302 of method REP_watcher() in the same block of code.
  2. Another alternative is to use ZMQ PUB/SUB, but you need to be careful with timing. A user of imageZMQ, @philipp-schmidt, provided an example. The checking is in the subscriber code rather than the publisher code.

I may add some option to imageZMQ for timeout checking in the future, but for now I am doing timeout checking in the code that uses imageZMQ rather than in imageZMQ itself.

jeffbass commented 3 years ago

Hi @saerhan, Let me know if you use one of the alternatives I mention in my comment above. Whatever works for you may help others. I am re-opening this issue so others can more easily see the issue and see my answer above. Thanks, Jeff

saerhan commented 3 years ago

Sure, Thanks for the help. I combined parts of your suggestion with @youngsoul code and it works just fine. It can wait without an error and if times passed it creates a new sender and try sending images again :


sender =imagezmq.ImageSender(connect_to="tcp://{}:5555".format(server_ip))
sender.zmq_socket.setsockopt(zmq.LINGER, 0)  # prevents ZMQ hang on exit
sender.zmq_socket.setsockopt(zmq.RCVTIMEO, 100 )  # will raise a ZMQError exception after 0.1 seconds
sender.zmq_socket.setsockopt(zmq.SNDTIMEO, 100 )  # will raise a ZMQError exception after 0.1 seconds

....

from contextlib import contextmanager
@contextmanager
def timeout(time):
    # register a function to raise a TimeoutError on the signal
    signal.signal(signal.SIGALRM, raise_timeout)
    # schedule the signal to be sent after 'time'
    signal.alarm(time)

    try:
        yield
    finally:
        # unregister the signal so it wont be triggered if the timtout is not reached
        signal.signal(signal.SIGALRM, signal.SIG_IGN)

def raise_timeout(signum, frame):
    raise TimeoutError

.....

try:
    with timeout(1):
        try:
            if compressed:
                ret, img_enc = cv2.imencode('.jpg', frame, [int(cv2.IMWRITE_JPEG_QUALITY), 100])
                data = sender.send_jpg(m, img_enc)
            else:
                data = sender.send_image(m, cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY))
        except Exception as exc:
            print("send_image exception")
            print("Exception msg: {}".format(exc))
            time.sleep(4)  # something happened, force a timeout
except TimeoutError:
    print("Sending timeout.. reconnect to server")
    sender = imagezmq.ImageSender(connect_to="tcp://{}:5555".format(server_ip))
    sender.zmq_socket.setsockopt(zmq.LINGER, 0)  # prevents ZMQ hang on exit
    sender.zmq_socket.setsockopt(zmq.RCVTIMEO, 100 )  # will raise a ZMQError exception after 0.1 seconds
    sender.zmq_socket.setsockopt(zmq.SNDTIMEO, 100 )  # will raise a ZMQError exception after 0.1 seconds
jeffbass commented 3 years ago

Thanks, your solution looks like a great one. Thanks for sharing it. One minor suggestion. Before starting a new ImageSender, it may prevent errors if you first close the sender that is already open. So, in your except TimeoutError code, you may want to add this close statement:

except TimeoutError:
    print("Sending timeout.. reconnect to server")
    sender.close()  # this closes the currently open sender instance; I have found it prevents occasional errors
    sender = imagezmq.ImageSender(connect_to="tcp://{}:5555".format(server_ip))
    sender.zmq_socket.setsockopt(zmq.LINGER, 0)  # prevents ZMQ hang on exit
    sender.zmq_socket.setsockopt(zmq.RCVTIMEO, 100 )  # will raise a ZMQError exception after 0.1 seconds
    sender.zmq_socket.setsockopt(zmq.SNDTIMEO, 100 )  # will raise a ZMQError exception after 0.1 seconds