Twisted Documentation: UDP Networking

# UDP Networking

## Overview

Unlike TCP, UDP has no notion of connections. A UDP socket can receive datagrams from any server on the network and send datagrams to any host on the network. In addition, datagrams may arrive in any order, never arrive at all, or be duplicated in transit.

Since there are no connections, we only use a single object, a protocol, for each UDP socket. We then use the reactor to connect this protocol to a UDP transport, using the `twisted.internet.interfaces.IReactorUDP` reactor API.

## DatagramProtocol

The class where you actually implement the protocol parsing and handling will usually be descended from `twisted.internet.protocol.DatagramProtocol` or from one of its convenience children. The `DatagramProtocol` class receives datagrams and can send them out over the network. Received datagrams include the address they were sent from. When sending datagrams the destination address must be specified.

Here is a simple example:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    from twisted.internet.protocol import DatagramProtocol
    from twisted.internet import reactor

    class Echo(DatagramProtocol):

        def datagramReceived(self, data, (host, port)):
            print "received %r from %s:%d" % (data, host, port)
            self.transport.write(data, (host, port))

    reactor.listenUDP(9999, Echo())
    reactor.run()

As you can see, the protocol is registered with the reactor. This means it may be persisted if it's added to an application, and thus it has `startProtocol` and `stopProtocol` methods that will get called when the protocol is connected and disconnected from a UDP socket.

The protocol's `transport` attribute will implement the `twisted.internet.interfaces.IUDPTransport` interface. Notice that the `host` argument should be an IP address, not a hostname. If you only have the hostname use `reactor.resolve()` to resolve the address (see `twisted.internet.interfaces.IReactorCore.resolve`).

## Connected UDP

A connected UDP socket is slightly different from a standard one - it can only send and receive datagrams to/from a single address, but this does not in any way imply a connection. Datagrams may still arrive in any order, and the port on the other side may have no one listening. The benefit of the connected UDP socket is that it it may provide notification of undelivered packages. This depends on many factors, almost all of which are out of the control of the application, but it still presents certain benefits which occasionally make it useful.

Unlike a regular UDP protocol, we do not need to specify where to send datagrams and are not told where they came from since they can only come from the address to which the socket is 'connected'.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    from twisted.internet.protocol import DatagramProtocol
    from twisted.internet import reactor

    class Helloer(DatagramProtocol):

        def startProtocol(self):
            host = "192.168.1.1"
            port = 1234

            self.transport.connect(host, port)
            print "now we can only send to host %s port %d" % (host, port)
            self.transport.write("hello") # no need for address

        def datagramReceived(self, data, (host, port)):
            print "received %r from %s:%d" % (data, host, port)

        # Possibly invoked if there is no server listening on the
        # address to which we are sending.
        def connectionRefused(self):
            print "No one listening"

    # 0 means any port, we don't care in this case
    reactor.listenUDP(0, Helloer())
    reactor.run()

Note that `connect()`, like `write()` will only accept IP addresses, not unresolved hostnames. To obtain the IP of a hostname use `reactor.resolve()`, e.g.:

    1
    2
    3
    4
    5
    6
    7
    8
    from twisted.internet import reactor

    def gotIP(ip):
        print "IP of 'example.com' is", ip
        reactor.callLater(3, reactor.stop)

    reactor.resolve('example.com').addCallback(gotIP)
    reactor.run()

Connecting to a new address after a previous connection or making a connected port unconnected are not currently supported, but likely will be in the future.

## Multicast UDP

A multicast UDP socket can send and receive datagrams from multiple clients. The interesting and useful feature of multicast is that a client can contact multiple servers with a single packet, without knowing the specific IP address of any of the hosts.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    from twisted.internet.protocol import DatagramProtocol
    from twisted.internet import reactor

    class MulticastServerUDP(DatagramProtocol):
        def startProtocol(self):
            print 'Started Listening'
            # Join a specific multicast group, which is the IP we will respond to
            self.transport.joinGroup('224.0.0.1')

        def datagramReceived(self, datagram, address):
            # The uniqueID check is to ensure we only service requests from
            # ourselves
            if datagram == 'UniqueID':
                print "Server Received:" + repr(datagram)
                self.transport.write("data", address)

    # Note that the join function is picky about having a unique object
    # on which to call join.  To avoid using startProtocol, the following is
    # sufficient:
    #reactor.listenMulticast(8005, MulticastServerUDP()).join('224.0.0.1')

    # Listen for multicast on 224.0.0.1:8005
    reactor.listenMulticast(8005, MulticastServerUDP())
    reactor.run()

Source listing - listings/udp/MulticastServer.py

The server protocol is very simple and closely resembles a normal `listenUDP` implementation. The main difference is that instead of `listenUDP`, `listenMulticast` is called with a specified port number. The server must also call `joinGroup` to specify on which multicast IP address it will service requests. Another item of interest is the contents of the datagram. Many different applications use multicast as a way of device discovery, which leads to an abundance of packets flying around. Checking the payload can ensure that we only service requests from our specific clients.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    from twisted.internet.protocol import DatagramProtocol
    from twisted.internet import reactor

    class MulticastClientUDP(DatagramProtocol):

        def datagramReceived(self, datagram, address):
                print "Received:" + repr(datagram)

    # Send multicast on 224.0.0.1:8005, on our dynamically allocated port
    port = reactor.listenUDP(0, MulticastClientUDP())
    port.write('UniqueID', ('224.0.0.1', 8005))
    reactor.run()

Source listing - listings/udp/MulticastClient.py

This is a mirror implementation of a standard UDP client. The only difference is that the destination IP is the multicast address. This datagram will be distributed to every server listening on `224.0.0.1` (a special address for all hosts on the local network segment, defined by RFC 1112) and port `8005`. Note that the client port is specified as `0`, as we have no need to keep track of what port the client is listening on.

## Acknowledgments

Thank you to all contributors to this document, including:

- Kyle Robertson, author of the explanation and examples of multicast

Index

Version: 11.1.0