twisted udp编程
概述
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.
和TCP不同,UDP没有连接。一个UDP套接字可以接受来自网络上的任何一台机器的报文,也可以发报文给任何一台主机。此外,报文可以以任意顺序到达,或者根本不到达(丢失),或者传输过程中发生重复。
既然没有连接,对于每一个UDP套机字,我们只需要使用一个协议的对象与之对应即可。然后可以用reactor来将协议对象绑定到UDP传输器(transport)上,使用 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:
我们的协议类,需要继承 twisted.internet.protocol.DatagramProtocol 类或者其子类。DatagramProtocol 类可以在网络上接受和发送报文。报文包括其发送地址。发送报文时必须指定接收地址。
下面是个简单的例子:
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.
如你所见,协议跟reactor一起注册。这意味着如果在程序中使用协议,它可以持久化,因而有一个startProtocol和stopProtocol 方法,分别在协议与套接字连接上和断开时调用。
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 (seetwisted.internet.interfaces.IReactorCore.resolve).
协议的transport属性实现了twisted.internet.interfaces.IUDPTransport接口。注意host参数必需是一个ip地址,不是主机名。如果只有主机名,可用reactor.resolve()得到地址(参考wisted.internet.interfaces.IReactorCore.resolve).
Adopted Datagram Ports
端口复用(提供基于socket的端口的来决定监听的端口的灵活性)
It is also possible to add an existing SOCK_DGRAM file descriptor to the reactor using the adoptDatagramPort API.
Here is a simple example:
可以将一个已经存在的SOCK_DRAM文件描述符添加到reactor,通过使用adoptDatagramPort API.
下面是一个简单的例子:
import socket 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)) portSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Make the port non-blocking and start it listening.确保端口是非阻塞的,然后才开始绑定 portSocket.setblocking(False) portSocket.bind(('127.0.0.1', 9999)) # Now pass the port file descriptor to the reactor port = reactor.adoptDatagramPort( portSocket.fileno(), socket.AF_INET, Echo()) # The portSocket should be cleaned up by the process that creates it. portSocket.close() reactor.run()
Note
- You must ensure that the socket is non-blocking before passing its file descriptor to adoptDatagramPort.
- adoptDatagramPort cannot (currently) detect the family of the adopted socket so you must ensure that you pass the correct socket family argument.
- The reactor will not shutdown the socket. It is the responsibility of the process that created the socket to shutdown and clean up the socket when it is no longer needed
注意:
- 必须确保套接字是非阻塞的,才能将它的文件描述符传给adoptDatagramPort.
- adoptDatagramPort(目前)不能检测采取的套接字的家族(family),因此务必确保falimy参数的正确赋值
- reactor不会关闭套接字。创建套接字的进程应当在不使用之的情况下关闭然后清除套接字。
Connected UDP
"连接型"的UDP
A connected UDP socket is slightly different from a standard one as it can only send and receive datagrams to/from a single address. However this does not in any way imply a connection as 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 may provide notification of undelivered packages. This depends on many factors (almost all of which are out of the control of the application) but 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’.
"连接型"的UDP套接字和常规的套接字有点不同,它只能发送报文到固定地址,或者从该固定地址接受报文。这不是说报文基于连接,因为报文还是可能以任意的顺序抵达,且有可能端口的另外一端没有任何套接字监听。”连接型“的UDP套接字的好处是,它可以提供通知---哪些包没有被传输。这取决于很多因素(知乎所有的因素都超出程序的控制),但仍然还是在某些情况下会比较有用。
和常规的UDP协议不同,不需要指定目标地址,也不需要知道源地址,因为他们都来自套接字”被绑定“的地址。
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; 0表示任何端口,这种情形下我们 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.:
注意到connect(),和write一样,只能接受ip地址,而不是主机名。通过主机名得到ip地址要使用reactor.resolve(),如下:
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
组播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.
组播允许协议使用一个包联系多个主机,不需要知道这些主机的具体ip地址。这个常规的,单路通讯的udp不同,常规的单路通讯的udp的每个报文都有一个唯一的ip地址,作为它的目的地址。
组播的报文被送到一些特殊的组播地址(ipv4里面从224.0.0.0到239.255.255.255),附有一个相应的端口。 要接受组播报文,你必须加入到那个指定的组地址里面去。
然后,任何udp套接字都可以发送到组播地址。
MulticastServer.py
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:设置TTL>1,以便跳过路由器,TTL定义最多跳几次 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: 使listenMultiple=True才能在同一台机器上运行客户端和服务器。 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.
使用udp,组播,客户端和服务器端在协议级别是没有差异的。上述服务器端代码比较简单,极似一个常规的 listenUDP协议的实现。主要区别在于,listenMulticast的调用中有端口。服务器端调用joinGroup加入到一个组播组。一个DatagramProtocol实例监听组播,加入到了可以接受组播报文的组中,但也可以接受直接发送给它的单路报文。上面的例子就发送了这样的一个单路广播的消息给它接受的消息的发送者。
MulticastClient.py
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 andsetLoopbackMode – see IMulticastTransport for more information.
注意到一个组播的套接字默认的TTL为1。 就是说,报文不会跨任何一个路由器转发,除非通过setTTL设置了一个更大值。组播的传输器(transport)还有其他的函数比如setOutgoingInterface,以及setLoopbackMode - 具体请参考IMulticastTransport。
Broadcast UDP
广播式UDP
Broadcast allows a different way of contacting several unknown hosts. Broadcasting via UDP sends a packet out to all hosts on the local network by sending to a magic broadcast address ("<broadcast>"). This broadcast is filtered by routers by default, and there are no “groups” like multicast, only different ports.
Broadcast is enabled by passing True to setBroadcastAllowed on the port. Checking the broadcast status can be done with getBroadcastAllowed on the port.
For a complete example of this feature, see udpbroadcast.py.
广播,提供了另外一种方式来联系一些未知的主机。通过UDP进行广播,将会把包发送给网络上的所有主机,只需要发送给一个神奇的广播地址("<broadcast>")。这个广播默认会被路由器过滤,没有类似于多路广播中的”组“的东东,只有不同的端口。
广播,只需要在端口上指定setBroadcastAllowed为True即可。检查端口上广播的状态可以通过getBroadcastAllowed来实现。
要知道完整的特性,请参考这里的例子 udpbroadcast.py.