pythonsocketsudpsocketserver

Python UDP socketserver returning empty message


I have a UDP socketserver program that I use to demonstrate how UDP works (code for the server and client are below). I run this on a server, then have the client.py program send a message and receive a reply. I am unfortunately running into an issue that seems to only occur on campus Wifi. On campus wifi, the client does not receive a response.

Troubleshooting with Wireshark shows the issue. For some reason the UDP server is responding with two UDP messages - one empty, and one containing the response message. These messages are recorded in Wireshark as coming in approximately 0.000002 seconds apart. On a wired network, the one with the response consistently comes first, and on Wifi, the empty message consistently comes first. Since the client is waiting for a single messages response, when the empty message returns, the client prints and exits, and the actual response is never seen.

I know I could write the client to listen for both messages and print out whichever one has the data, but I would rather try to figure out what's going on. Why is the socketserver responding with two messages in the first place, and how can I get it to only send one? OR at least to send the data first.

server.py:

import socketserver


class MyUDPRequestHandler(socketserver.DatagramRequestHandler):
    def handle(self):

        data = self.request[0].strip()
        socket = self.request[1]
        # just send back the same data, but lower-cased
        socket.sendto(data.lower(), self.client_address)


if __name__ == "__main__":
    with socketserver.UDPServer(("0.0.0.0", 9091), MyUDPRequestHandler) as server:
        server.serve_forever()

client.py:

import socket

HOST, PORT = "localhost", 9091
message = "NOW I AM SHOUTING"  # The UDP server will lowercase the message

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

sock.sendto(bytes(message + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")

print("Sent:     {}".format(message))
print("Received: {}".format(received))

Solution

  • I've repeated the problem and it's socketserver. Notice the definition of DatagramRequestHandler below:

    class DatagramRequestHandler(BaseRequestHandler):
    
        """Define self.rfile and self.wfile for datagram sockets."""
    
        def setup(self):
            from io import BytesIO
            self.packet, self.socket = self.request
            self.rfile = BytesIO(self.packet)
            self.wfile = BytesIO()
    
        def finish(self):
            self.socket.sendto(self.wfile.getvalue(), self.client_address)
    

    The packet is put into a buffer as rfile and should be read from there, then written back to the wfile buffer. finish sends the packet. The handler shouldn't call sendto itself:

    import socketserver
    
    class MyUDPRequestHandler(socketserver.DatagramRequestHandler):
        def handle(self):
            data = self.rfile.read()
            self.wfile.write(data.strip().lower())
    
    if __name__ == "__main__":
        with socketserver.UDPServer(("0.0.0.0", 9091), MyUDPRequestHandler) as server:
            server.serve_forever()
    

    But just using a simple socket as the server works fine too:

    import socket
    
    s = socket.socket(type=socket.SOCK_DGRAM)
    s.bind(('', 9091))
    while True:
        data, client = s.recvfrom(2048)
        s.sendto(data.strip().lower(), client)
    

    Note that UDP packets are not guaranteed to be delivered or delivered in the same order, so the original code's issue with the two packets changing order isn't surprising.