创建一个UDP服务器
假设我们想实现一个采用UDP协议同客户端进行通信的服务器,同TCP一样,我们可以利用socketsever库也很容易创建出UDP服务器。比如下面这个简单的时间服务器程序:
from socketserver import BaseRequestHandler, UDPServer
import time
class TimeHandler(BaseRequestHandler):
def handle(self):
print('Got connection from', self.client_address)
# Get message and client socket
msg, sock = self.request
resp = time.ctime()
sock.sendto(resp.encode('ascii'), self.client_address)
if __name__ == '__main__':
serv = UDPServer(('', 20000), TimeHandler)
serv.serve_forever()
像上一节一样,这里需要定义一个特殊的处理类,其中要实现一个handle()方法来处理客户端的连接,这里的request书信就是一个元组,包含了这个服务器收到的数据报以及代表了底层的socket对象。client_address包含的是客户端的地址
要调试这个服务器程序,先运行上面的脚本,然后另外开一个Python进程并向服务端程序发送消息:
>>> from socket import socket, AF_INET, SOCK_DGRAM
>>> s = socket(AF_INET, SOCK_DGRAM)
>>> s.sendto(b'', ('localhost', 20000))
0
>>> s.recvfrom(8192)
(b'Wed Aug 15 20:35:08 2012', ('127.0.0.1', 20000))
>>>
一个典型的UDP服务器接收到达的数据报(消息)和客户端地址。如果服务器需要做应答, 它要给客户端回发一个数据报。对于数据报的传送, 你应该使用socket的 sendto() 和 recvfrom() 方法。 尽管传统的 send() 和 recv() 也可以达到同样的效果, 但是前面的两个方法对于UDP连接而言更普遍。
由于没有底层的连接,UPD服务器相对于TCP服务器来讲实现起来更加简单。 不过,UDP天生是不可靠的(因为通信没有建立连接,消息可能丢失)。 因此需要由你自己来决定该怎样处理丢失消息的情况。这个已经不在本书讨论范围内了, 不过通常来说,如果可靠性对于你程序很重要,你需要借助于序列号、重试、超时以及一些其他方法来保证。 UDP通常被用在那些对于可靠传输要求不是很高的场合。例如,在实时应用如多媒体流以及游戏领域, 无需返回恢复丢失的数据包(程序只需简单的忽略它并继续向前运行)。
UDPServer 类是单线程的,也就是说一次只能为一个客户端连接服务。 实际使用中,这个无论是对于UDP还是TCP都不是什么大问题。 如果你想要并发操作,可以实例化一个 ForkingUDPServer 或 ThreadingUDPServer 对象:
from socketserver import ThreadingUDPServer
if __name__ == '__main__':
serv = ThreadingUDPServer(('',20000), TimeHandler)
serv.serve_forever()
直接使用socket来实现一个UDP服务器也不难,下面是一个例子:
from socket import socket, AF_INET, SOCK_DGRAM
import time
def time_server(address):
sock = socket(AF_INET, SOCK_DGRAM)
sock.bind(address)
while True:
msg, addr = sock.recvfrom(8192)
print('Got message from', addr)
resp = time.ctime()
sock.sendto(resp.encode('ascii'), addr)
if __name__ == '__main__':
time_server(('', 20000))
从CIDR地址中生成IP地址的范围
假设我们有一个类似于"123.45.67.89/27"这样的CIDR网络地址,我们向生成由该地址可表示的全部IP地址范围。对于这种情况来说,我们可以利用ipaddress模块来处理这样的计算:
>>> import ipaddress
>>> net = ipaddress.ip_network('123.45.67.64/27')
>>> net
IPv4Network('123.45.67.64/27')
>>> for a in net:
... print(a)
...
123.45.67.64
123.45.67.65
123.45.67.66
123.45.67.67
123.45.67.68
...
123.45.67.95
>>>
>>> net6 = ipaddress.ip_network('12:3456:78:90ab:cd:ef01:23:30/125')
>>> net6
IPv6Network('12:3456:78:90ab:cd:ef01:23:30/125')
>>> for a in net6:
... print(a)
...
12:3456:78:90ab:cd:ef01:23:30
12:3456:78:90ab:cd:ef01:23:31
12:3456:78:90ab:cd:ef01:23:32
12:3456:78:90ab:cd:ef01:23:33
12:3456:78:90ab:cd:ef01:23:34
12:3456:78:90ab:cd:ef01:23:35
12:3456:78:90ab:cd:ef01:23:36
12:3456:78:90ab:cd:ef01:23:37
>>>
network对象同样也支持像数组一样的索引操作:
>>> net.num_addresses
32
>>> net[0]
IPv4Address('123.45.67.64')
>>> net[1]
IPv4Address('123.45.67.65')
>>> net[-1]
IPv4Address('123.45.67.95')
>>> net[-2]
IPv4Address('123.45.67.94')
>>>
并且还可以执行检查成员归属的操作:
>>> a = ipaddress.ip_address('123.45.67.69')
>>> a in net
True
>>> b = ipaddress.ip_address('123.45.67.123')
>>> b in net
False
>>>
IP地址加上网络号可以用来指定一个IP接口:
>>> inet = ipaddress.ip_interface('123.45.67.73/27')
>>> inet.network
IPv4Network('123.45.67.64/27')
>>> inet.ip
IPv4Address('123.45.67.73')
>>>
ipaddress 模块有很多类可以表示IP地址、网络和接口。 当你需要操作网络地址(比如解析、打印、验证等)的时候会很有用。
要注意的是,ipaddress 模块跟其他一些和网络相关的模块比如 socket 库交集很少。 所以,你不能使用 IPv4Address 的实例来代替一个地址字符串,你首先得显式的使用 str() 转换它。例如:
>>> a = ipaddress.ip_address('127.0.0.1')
>>> from socket import socket, AF_INET, SOCK_STREAM
>>> s = socket(AF_INET, SOCK_STREAM)
>>> s.connect((a, 8080))
Traceback (most recent call last):
File "", line 1, in
TypeError: Can't convert 'IPv4Address' object to str implicitly
>>> s.connect((str(a), 8080))
>>>
参考书目:
《Python CookBook》作者:【美】 David Beazley, Brian K. Jones
Github地址:yidao620c/python3-cookbookgithub.com