A
So - you found out why there are higher-level protocols than TCP, such as HTTP and FTP for example: TCP handles the exchange of messages between two computers, with an established connection - but you don't care at all about the content of these messages.Any information further - including the length of the message, or final marker, is data that also has to be within the messages, in addition to the final content itself, in your case, the bytes with the images files. In your specific case, you want to share the same TCP connection to transit multiple files - or, create separate connections, and access distinct features, but with the same input socket - both situations require more control than just having a TCP clink and use recv there to read the bytes. The "HTTP" protocol for example was created to have the messages we know so much, which send messages of the type "GET", "POST", "PUT", and are answered by the server with the famous "Status Code". A raw socket has nothing of this- as you have noticed in the text that is in the first part of your question, you have to either use a higher-level protocol, or reinvent the wheel.So I have good news and bad news:Fortunately, instead of reinventing the wheel, you chose to use the library zerorpc that already does this: adds protocol layers around Socket, to allow the call of Python methods, and already formats and serializes the answer - that is, you are no longer concerned only with a "socket" - your doubt is of zerorpc. then it is to follow his documentation and see how to handle asynchronous calls: that is - the ability to process and respond a request without blocking the arrival of new requests. Hence the bad news - Python's zeroprc server does not create multiple workers automatically, using no method, and the existing documentation is pretty bad. The good part is that it manages multiple connections in the same socket, so, yes, once you can create the parallel workers, if your client is already making the calls in parallel, it will work (but if you need to parallelize the client too, you have more things).Well, in short - zerorpc uses a Python technology called gevent, which is lighter than threads, however this implies that you will have to use gevent to parallel your server, and not threads. Threads are already complicated, but you find plenty of example and documentation. Gevent uses something called "greenlet" instead of threads - has the same difficulties, but much less documentation and examples.Gevent documentation is here: http://www.gevent.org/api/ But in short, in every function of yours that is made available to be called remotely, you can create a new Greenlet pointing to the real function, and call your method .join - this will perform the function within the greenlet - equivalent to the threads of S.O than Python. uses - and can release the server to receive other connections (but - more about it the front), while doing its processing. When the call to .join return, you can return the attribute .value of greenlet - this will be the return value of the remote function.Here I have a simple client and server that only sleeps a second random fraction, first sequentially and then in parallel:import random, time
import gevent
import zerorpc
def sync_world(self, i):
print(f"starting {i}")
gevent.sleep(random.random())
print(f"finishing {i}")
return i
class Hello:
def world(self, i):
glet = gevent.spawn(sync_world, i)
glet.join()
return glet.value
s = zerorpc.Server(Hello())
s.bind("tcp://127.0.0.1:8877")
gevent.spawn(s.run)
gevent.wait()
And the customer - realize that customer calls have to pass the value async=True. This makes the call return a "future", instead of the actual value - then it is necessary to call the method get in each returned value to have the value processed on the server.import time
import zerorpc
c = zerorpc.Client("tcp://127.0.0.1:8877")
def timeit(func):
def wrapper(*args, **kw):
start = time.time()
result = func(*args, **kw)
print (f"\n{func.name} ran in {time.time() - start}s\n")
return result
return wrapper
@timeit
def query():
for i in range(10):
print(c.world(i))
@timeit
def parallel_query():
results = []
for i in range(10):
results.append(c.world(i, async=True))
for result in results:
print(result.get(block=True))
query()
parallel_query()
In other words - this client has a function that only calls the methods in sequence - it works, but this will expect each response to be ready on the server to send the next request- even the server being parallelized.
The second function adds the parameter async=True (do not confuse with the keyword async introduced in Python 3.5 - it's just the same idea, but implemented separately, using greenlets). (this code also has a decorator to print the time of each function - it, of course, is not necessary)Now - other bad news - realize that for my server to be parallel, I have to call the function greenlet.sleep. That is: I have to pass control, at some point, to the "gevent" loop, otherwise the function is not parallel, it runs to the end - without letting the server access new connections - then the remedy really is to call the gevent.sleep() (without a number - or with "0"), this makes the gevent check if there is another connection in the socket and start treating it. This is different from threads - in which the operating system itself changes to another thread automatically. More like asyncio most used in recent Python.And here comes the third bad news: the original greenlet, which called the gevent.sleep to give a chance to another call, will stand still waiting - if the time you take to back the request from the server side uses a lot of CPU or locks in IO (e.g. when saving the file on the disk) - this is not parallelized by gevent or Python - you will need a third technology (in addition to zerorpc and gevent) to run the task really in parallel. So, yes, you can still have a good way forward. One thing to try, as soon as you can parallel the server, even if you haven't noticed the benefits yet, is to call the gevent monkey-patch functions - they make some Python standard library functions co-operate with gevent automatically, and you may already respond to queries in parallel, depending on what each worker function you do: http://www.gevent.org/api/gevent.monkey.html (but I can't tell if zerorpc already makes that call).You wrote that you are doing "only a chat" - I understand this as a text chat - in this case, the parallel treatment of sockets only with the monkey-patch above is more than enough. But in your code you have functions to want to save in real time all frames of two video streams - this is much heavier, and you can't do much simpler. Alternative:Forget about zerorpc if you start to get too complicated and don't have better documentation on how to parallel it (I didn't think). Instead use the celery - it uses another process as "broker" - that is, a message manager, but does it transparently. Then instead of having a single server, you climb several workers consuming messages, and the remote method calls instead of talking to the direct workers, put the messages in the broker - for whom program is the same thing: your Python function here is remotely called the other process - but the whole part of parallelism and network is on Celery's account. - https://celery.org