Problem with writing fast UDP server

Jean-Paul Calderone exarkun at divmod.com
Thu Nov 20 22:52:24 EST 2008


On Fri, 21 Nov 2008 00:20:49 -0200, Gabriel Genellina <gagsl-py2 at yahoo.com.ar> wrote:
>En Thu, 20 Nov 2008 14:24:20 -0200, Krzysztof Retel 
><Krzysztof.Retel at googlemail.com> escribió:
>>On Nov 20, 4:00 pm, bieff... at gmail.com wrote:
>>>On 20 Nov, 16:03, Krzysztof Retel <Krzysztof.Re... at googlemail.com>
>>>wrote:
>>>
>>> > I am struggling writing fast UDP server. It has to handle around 10000
>>> > UDP packets per second. I started building that with non blocking
>>> > socket and threads. Unfortunately my approach does not work at all.
>>> > I wrote a simple case test: client and server. The client sends 2200
>>> > packets within 0.137447118759 secs. The tcpdump received 2189 packets,
>>> > which is not bad at all.
>>> > But the server only handles 700 -- 870 packets, when it is non-
>>> > blocking, and only 670 – 700 received with blocking sockets.
>>> > The client and the server are working within the same local network
>>> > and tcpdump shows pretty correct amount of packets received.
>>
>>I wonder if there is a kind of setting for socket to allow no delays?
>
>I've used this script to test sending UDP packets. I've not seen any 
>delays.
>
><code>
> [snip]
></code>
>
>Start the server before the client.
>

If you want to try this program out on POSIX, make sure you change the
time.clock() calls to time.time() calls instead, otherwise the results
aren't very meaningful.

I gave this a try on an AMD64 3200+ running a 32 bit Linux installation.
Here's the results I got on the server:

Packet count          91426
Total bytes         8228340 bytes
Total time              8.4 secs
Avg size / packet        90 bytes
Max size / packet        90 bytes
Max time / packet   41070.9 us
Min time / packet      79.9 us
Avg time / packet      92.3 us
Max speed            1100.4 Kbytes/sec
Min speed               2.1 Kbytes/sec
Avg speed             952.0 Kbytes/sec

And on the client:

Packet count          91426
Total bytes         8228340 bytes
Total time              8.4 secs
Avg size / packet        90 bytes
Max size / packet        90 bytes
Max time / packet   40936.0 us
Min time / packet      78.9 us
Avg time / packet      92.3 us
Max speed            1113.7 Kbytes/sec
Min speed               2.1 Kbytes/sec
Avg speed             952.1 Kbytes/sec

Both processes ran on the same machine and communicated over localhost.

For comparison, I tried running the client against a Twisted-based UDP
server.  Here are the results from that server:

Packet count          91426
Total bytes         8228340 bytes
Total time             11.8 secs
Avg size / packet        90 bytes
Max size / packet        90 bytes
Max time / packet   55393.9 us
Min time / packet       8.8 us
Avg time / packet     128.7 us
Max speed            9963.2 Kbytes/sec
Min speed               1.6 Kbytes/sec
Avg speed             682.7 Kbytes/sec

This seemed a bit low to me though, so I tried writing an alternate client
and re-ran the measurement.  Here are the new server results:

Packet count          91426
Total bytes         8228340 bytes
Total time              2.9 secs
Avg size / packet        90 bytes
Max size / packet        90 bytes
Max time / packet   38193.0 us
Min time / packet       8.8 us
Avg time / packet      32.2 us
Max speed            9963.2 Kbytes/sec
Min speed               2.3 Kbytes/sec
Avg speed            2726.7 Kbytes/sec

And then tried the new client against the original server, with these
results:

Packet count          91426
Total bytes         8228340 bytes
Total time              3.8 secs
Avg size / packet        90 bytes
Max size / packet        90 bytes
Max time / packet   23675.0 us
Min time / packet       6.9 us
Avg time / packet      41.7 us
Max speed           12711.7 Kbytes/sec
Min speed               3.7 Kbytes/sec
Avg speed            2109.0 Kbytes/sec

So it does seem that handling 10k datagrams per second should be no
problem, assuming comparable hardware, at least if whatever work you
have to do to process each one doesn't take more than about 24 25ths
of a millisecond (leaving you the remaining 1 part out of 25 of every
millisecond to receive a packet).

For reference, here's the Twisted UDP client code:

    from twisted.internet.protocol import DatagramProtocol
    from twisted.internet import reactor
    from twisted.internet.task import LoopingCall

    msg = 'xyxabc123' * 10

    class EchoClientDatagramProtocol(DatagramProtocol):

        def startProtocol(self):
            self.transport.connect('127.0.0.1', 8000)
            LoopingCall(self.sendDatagrams).start(0.00005)
    
        def sendDatagrams(self):
            for i in xrange(50):
                self.transport.write(msg)

    def main():
        protocol = EchoClientDatagramProtocol()
        t = reactor.listenUDP(0, protocol)
        reactor.run()

    if __name__ == '__main__':
        main()

And here's the Twisted UDP server code:

    from time import time as clock

    from twisted.internet.protocol import DatagramProtocol
    from twisted.internet import reactor

    class EchoUDP(DatagramProtocol):
        history = []
        t0 = clock()
        def datagramReceived(self, datagram, address):
            t1 = clock()
            self.history.append((len(datagram), t1 - self.t0))
            self.t0 = t1
            if len(self.history) == 91427:
                self.history.pop(0)
                show_stats(self.history)
                self.history = []

    def main():
        reactor.listenUDP(8000, EchoUDP())
        reactor.run()

    def show_stats(history):
        npackets = len(history)
        bytes_total = sum([item[0] for item in history])
        bytes_avg = float(bytes_total) / npackets
        bytes_max = max([item[0] for item in history])
        time_total = sum([item[1] for item in history])
        time_max   = max([item[1] for item in history])
        time_min   = min([item[1] for item in history])
        time_avg = float(time_total) / npackets
        speed_max = max([item[0]/item[1] for item in history if item[1]>0])
        speed_min = min([item[0]/item[1] for item in history if item[1]>0])
        speed_avg = float(bytes_total) / time_total
        print "Packet count       %8d" % npackets
        print "Total bytes        %8d bytes" % bytes_total
        print "Total time         %8.1f secs" % time_total
        print "Avg size / packet  %8d bytes" % bytes_avg
        print "Max size / packet  %8d bytes" % bytes_max
        print "Max time / packet  %8.1f us" % (time_max*1e6)
        print "Min time / packet  %8.1f us" % (time_min*1e6)
        print "Avg time / packet  %8.1f us" % (time_avg*1e6)
        print "Max speed          %8.1f Kbytes/sec" % (speed_max/1024)
        print "Min speed          %8.1f Kbytes/sec" % (speed_min/1024)
        print "Avg speed          %8.1f Kbytes/sec" % (speed_avg/1024)
        print

    if __name__ == '__main__':
        main()




More information about the Python-list mailing list