創建一個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)
(bWed 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 "<stdin>", line 1, in <module>
TypeError: Cant convert IPv4Address object to str implicitly
>>> s.connect((str(a), 8080))
>>>

參考書目

《Python CookBook》作者:【美】 David Beazley, Brian K. Jones

Github地址:

yidao620c/python3-cookbook?

github.com圖標
推薦閱讀:

相關文章