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
Multicast allows a process to contact multiple hosts with a single packet, without knowing the specific IP address of any of the hosts. This is in contrast to normal, or unicast, UDP, where each datagram has a single IP as its destination. Multicast datagrams are sent to special multicast group addresses (in the IPv4 range 224.0.0.0 to 239.255.255.255), along with a corresponding port. In order to receive multicast datagrams, you must join that specific group address. However, any UDP socket can send to multicast addresses.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
from twisted.internet.protocol import DatagramProtocol from twisted.internet import reactor class MulticastPingPong(DatagramProtocol): def startProtocol(self): """ Called after protocol has started listening. """ # Set the TTL>1 so multicast will cross router hops: self.transport.setTTL(5) # Join a specific multicast group: self.transport.joinGroup("228.0.0.5") def datagramReceived(self, datagram, address): print "Datagram %s received from %s" % (repr(datagram), repr(address)) if datagram == "Client: Ping": # Rather than replying to the group multicast address, we send the # reply directly (unicast) to the originating port: self.transport.write("Server: Pong", address) # We use listenMultiple=True so that we can run MulticastServer.py and # MulticastClient.py on same machine: reactor.listenMulticast(8005, MulticastPingPong(), listenMultiple=True) reactor.run()
As with UDP, with multicast there is no server/client differentiation
at the protocol level. Our server example is very simple and closely
resembles a normal listenUDP
protocol implementation. The main difference is that instead
of listenUDP
, listenMulticast
is called with the port number. The server calls joinGroup
to
join a multicast group. A DatagramProtocol
that is listening with multicast and has joined a group can receive
multicast datagrams, but also unicast datagrams sent directly to its
address. The server in the example above sends such a unicast message in
reply to the multicast message it receives from the client.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
from twisted.internet.protocol import DatagramProtocol from twisted.internet import reactor class MulticastPingClient(DatagramProtocol): def startProtocol(self): # Join the multicast address, so we can receive replies: self.transport.joinGroup("228.0.0.5") # Send to 228.0.0.5:8005 - all listeners on the multicast address # (including us) will receive this message. self.transport.write('Client: Ping', ("228.0.0.5", 8005)) def datagramReceived(self, datagram, address): print "Datagram %s received from %s" % (repr(datagram), repr(address)) reactor.listenMulticast(8005, MulticastPingClient(), listenMultiple=True) reactor.run()
Note that a multicast socket will have a default TTL (time to live) of
1. That is, datagrams won't traverse more than one router hop, unless a
higher TTL is set with
setTTL
. Other
functionality provided by the multicast transport
includes setOutgoingInterface
and setLoopbackMode
-- see IMulticastTransport
for more
information.