I've setup a 1:1 connection between a linux client and a windows server using PyZMQ.
On the client side I use zmq.REQ
sockets and on the server side zmq.REP
sockets.
The server starts up a messageloop which receives requests from the client and subsequentially sends responses back to the client.
When the client sends a request, the server sends a response as soon as possible (fractions of a second).
Client (Stripped example, imagine try except surround method bodies):
def _connect_to_remote(self, remote: str) -> typing.Tuple[zmq.asyncio.Context, zmq.asyncio.Socket]:
# Set-up and connect Zero-MQ network socket,
# with the `remote` beeing of the form `remote = f"tcp://{remoteaddr}:{remote_port}"`
context = zmq.asyncio.Context()
socket = context.socket(zmq.REQ)
socket.connect(remote)
return context, socket
async def _send_msg(self, msg: dict):
# Send serialized msg over zmq network socket
serialized_msg = json.dumps(msg)
await self._socket.send_string(serialized_msg)
async def get_response(self, timeout_s: float = REQUEST_TIMEOUT_S):
# Receive response from server, raises on timeout
msg = await asyncio.wait_for(self._socket.recv(), timeout_s)
response = json.loads(msg)
return response
Server:
def _bind_socket(self, port: int) -> tuple[zmq.asyncio.Context, zmq.asyncio.Socket]:
# init zmq network socket and bind to port
context = zmq.asyncio.Context()
socket = context.socket(zmq.REP)
bind_addr = f"tcp://*:{port}"
socket.bind(bind_addr)
return context, socket
async def _rcv_msg(self):
# Receive request from client
# Use NOBLOCK to work around the problem that the recv must not be run into the blocking state
# before the client sends the first msg
message = await self._socket.recv(flags = zmq.NOBLOCK)
deserialized_msg = json.loads(message)
return deserialized_msg
async def _send_msg(self, msg: dict):
# send msg over zmq network socket
serialized_msg = json.dumps(asdict(msg))
await self._socket.send_string(serialized_msg)
async def _message_loop(self):
while True:
try:
message = await self._rcv_msg()
if message is not None:
"""
Do sth with the client request, here -> response
"""
# Send reply to client
await self._send_msg(response)
except asyncio.CancelledError as ex:
self._logger.exception("Cancelled messageloop")
break
except Exception as ex:
self._logger.exception("Unhandled exception in messageloop")
finally:
await asyncio.sleep(0.1)
Now, for testing purposes I've also set-up a testprogram which starts a client and a server both on the same linux development computer (via localhost). I can send requests and watch the response. The localhost testprogram shows that the response follows immediately upon the request.
However, when I start the server on the windows platform and the client on the linux platform, behaviour is different.
Both client and server are started in a console window in their virtual env from the source using python start_server.py
or python start_client.py
.
In contrast to before, the client was connected to the server instead of the localhost.
This time, the client testprogram shows that the response occasionally does not follow immediately upon the request. The client runs into timeout, then.
Looking further it shows that the server did not sent the response when it was expected.
However, I can enforce the response to be sent when I press enter in the terminal on the server side.
Then the client also receives that response immediately after I pressed enter. This shows it is not a connection problem and not a PyZMQ socket issue nor an issue with the testprogram.
It merely looks like the response waits in the network buffer until I press the enter key in the terminal. When the enter key is pressed, the buffer is flushed and the response is sent, which is quite weird and confusing, as I have programmed no keyboard interaction on the server side.
Have I missed something which messes up the communication between the linux client and the windows server?