socketserver
--- 用于網(wǎng)絡(luò )服務(wù)器的框架?
源代碼: Lib/socketserver.py
socketserver
模塊簡(jiǎn)化了編寫(xiě)網(wǎng)絡(luò )服務(wù)器的任務(wù)。
該模塊具有四個(gè)基礎實(shí)體服務(wù)器類(lèi):
- class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)?
該類(lèi)使用互聯(lián)網(wǎng) TCP 協(xié)議,它可以提供客戶(hù)端與服務(wù)器之間的連續數據流。 如果 bind_and_activate 為真值,該類(lèi)的構造器會(huì )自動(dòng)嘗試發(fā)起調用
server_bind()
和server_activate()
。 其他形參會(huì )被傳遞給BaseServer
基類(lèi)。
- class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)?
該類(lèi)使用數據包,即一系列離散的信息分包,它們可能會(huì )無(wú)序地到達或在傳輸中丟失。 該類(lèi)的形參與
TCPServer
的相同。
- class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)?
- class socketserver.UnixDatagramServer(server_address, RequestHandlerClass, bind_and_activate=True)?
這兩個(gè)更常用的類(lèi)與 TCP 和 UDP 類(lèi)相似,但使用 Unix 域套接字;它們在非 Unix 系統平臺上不可用。 它們的形參與
TCPServer
的相同。
這四個(gè)類(lèi)會(huì ) 同步地 處理請求;每個(gè)請求必須完成才能開(kāi)始下一個(gè)請求。 這就不適用于每個(gè)請求要耗費很長(cháng)時(shí)間來(lái)完成的情況,或者因為它需要大量的計算,又或者它返回了大量的數據而客戶(hù)端處理起來(lái)很緩慢。 解決方案是創(chuàng )建單獨的進(jìn)程或線(xiàn)程來(lái)處理每個(gè)請求;ForkingMixIn
和 ThreadingMixIn
混合類(lèi)可以被用于支持異步行為。
創(chuàng )建一個(gè)服務(wù)器需要分幾個(gè)步驟進(jìn)行。 首先,你必須通過(guò)子類(lèi)化 BaseRequestHandler
類(lèi)并重載其 handle()
方法來(lái)創(chuàng )建一個(gè)請求處理句柄類(lèi);這個(gè)方法將處理傳入的請求。 其次,你必須實(shí)例化某個(gè)服務(wù)器類(lèi),將服務(wù)器地址和請求處理句柄類(lèi)傳給它。 建議在 with
語(yǔ)句中使用該服務(wù)器。 然后再調用服務(wù)器對象的 handle_request()
或 serve_forever()
方法來(lái)處理一個(gè)或多個(gè)請求。 最后,調用 server_close()
來(lái)關(guān)閉套接字(除非你使用了 with
語(yǔ)句)。
當從 ThreadingMixIn
繼承線(xiàn)程連接行為時(shí),你應當顯式地聲明你希望在突然關(guān)機時(shí)你的線(xiàn)程采取何種行為。 ThreadingMixIn
類(lèi)定義了一個(gè)屬性 daemon_threads,它指明服務(wù)器是否應當等待線(xiàn)程終止。 如果你希望線(xiàn)程能自主行動(dòng)你應當顯式地設置這個(gè)旗標;默認值為 False
,表示 Python 將不會(huì )在 ThreadingMixIn
所創(chuàng )建的所有線(xiàn)程都退出之前退出。
服務(wù)器類(lèi)具有同樣的外部方法和屬性,無(wú)論它們使用哪種網(wǎng)絡(luò )協(xié)議。
服務(wù)器創(chuàng )建的說(shuō)明?
在繼承圖中有五個(gè)類(lèi),其中四個(gè)代表四種類(lèi)型的同步服務(wù)器:
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
請注意 UnixDatagramServer
派生自 UDPServer
,而不是 UnixStreamServer
--- IP 和 Unix 流服務(wù)器的唯一區別是地址族,它會(huì )在兩種 Unix 服務(wù)器類(lèi)中簡(jiǎn)單地重復。
- class socketserver.ForkingMixIn?
- class socketserver.ThreadingMixIn?
每種服務(wù)器類(lèi)型的分叉和線(xiàn)程版本都可以使用這些混合類(lèi)來(lái)創(chuàng )建。 例如,
ThreadingUDPServer
的創(chuàng )建方式如下:class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
混合類(lèi)先出現,因為它重載了
UDPServer
中定義的一個(gè)方法。 設置各種屬性也會(huì )改變下層服務(wù)器機制的行為。ForkingMixIn
和下文提及的分叉類(lèi)僅在支持fork()
的 POSIX 系統平臺上可用。socketserver.ForkingMixIn.server_close()
會(huì )等待直到所有子進(jìn)程完成,除非socketserver.ForkingMixIn.block_on_close
屬性為假值。socketserver.ThreadingMixIn.server_close()
會(huì )等待直到所有非守護類(lèi)線(xiàn)程完成,除非socketserver.ThreadingMixIn.block_on_close
屬性為假值。 請將ThreadingMixIn.daemon_threads
設為True
來(lái)使用守護類(lèi)線(xiàn)程以便不等待線(xiàn)完成。在 3.7 版更改:
socketserver.ForkingMixIn.server_close()
和socketserver.ThreadingMixIn.server_close()
現在會(huì )等待直到所有子進(jìn)程和非守護類(lèi)線(xiàn)程完成。 請新增一個(gè)socketserver.ForkingMixIn.block_on_close
類(lèi)屬性來(lái)選擇 3.7 版之前的行為。
- class socketserver.ForkingTCPServer?
- class socketserver.ForkingUDPServer?
- class socketserver.ThreadingTCPServer?
- class socketserver.ThreadingUDPServer?
這些類(lèi)都是使用混合類(lèi)來(lái)預定義的。
要實(shí)現一個(gè)服務(wù),你必須從 BaseRequestHandler
派生一個(gè)類(lèi)并重定義其 handle()
方法。 然后你可以通過(guò)組合某種服務(wù)器類(lèi)型與你的請求處理句柄類(lèi)來(lái)運行各種版本的服務(wù)。 請求處理句柄類(lèi)對于數據報和流服務(wù)必須是不相同的。 這可以通過(guò)使用處理句柄子類(lèi) StreamRequestHandler
或 DatagramRequestHandler
來(lái)隱藏。
當然,你仍然需要動(dòng)點(diǎn)腦筋! 舉例來(lái)說(shuō),如果服務(wù)包含可能被不同請求所修改的內存狀態(tài)則使用分叉服務(wù)器是沒(méi)有意義的,因為在子進(jìn)程中的修改將永遠不會(huì )觸及保存在父進(jìn)程中的初始狀態(tài)并傳遞到各個(gè)子進(jìn)程。 在這種情況下,你可以使用線(xiàn)程服務(wù)器,但你可能必須使用鎖來(lái)保護共享數據的一致性。
另一方面,如果你是在編寫(xiě)一個(gè)所有數據保存在外部(例如文件系統)的 HTTP 服務(wù)器,同步類(lèi)實(shí)際上將在正在處理某個(gè)請求的時(shí)候“失聰” -- 如果某個(gè)客戶(hù)端在接收它所請求的所有數據時(shí)很緩慢這可能會(huì )是非常長(cháng)的時(shí)間。 這時(shí)線(xiàn)程或分叉服務(wù)器會(huì )更為適用。
在某些情況下,合適的做法是同步地處理請求的一部分,但根據請求數據在分叉的子進(jìn)程中完成處理。 這可以通過(guò)使用一個(gè)同步服務(wù)器并在請求處理句柄類(lèi) handle()
中進(jìn)行顯式分叉來(lái)實(shí)現。
還有一種可以在既不支持線(xiàn)程也不支持 fork()
的環(huán)境(或者對于本服務(wù)來(lái)說(shuō)這兩者開(kāi)銷(xiāo)過(guò)大或是不適用)中處理多個(gè)同時(shí)請求的方式是維護一個(gè)顯式的部分完成的請求表并使用 selectors
來(lái)決定接下來(lái)要處理哪個(gè)請求(或者是否要處理一個(gè)新傳入的請求)。 這對于流服務(wù)來(lái)說(shuō)特別重要,因為每個(gè)客戶(hù)端可能會(huì )連接很長(cháng)的時(shí)間(如果不能使用線(xiàn)程或子進(jìn)程)。 請參閱 asyncore
來(lái)了解另一種管理方式。
Server 對象?
- class socketserver.BaseServer(server_address, RequestHandlerClass)?
這是本模塊中所有 Server 對象的超類(lèi)。 它定義了下文給出的接口,但沒(méi)有實(shí)現大部分的方法,它們應在子類(lèi)中實(shí)現。 兩個(gè)形參存儲在對應的
server_address
和RequestHandlerClass
屬性中。- handle_request()?
處理單個(gè)請求。 此函數會(huì )依次調用下列方法:
get_request()
,verify_request()
和process_request()
。 如果用戶(hù)提供的處理句柄類(lèi)的handle()
方法引發(fā)了異常,則將調用服務(wù)器的handle_error()
方法。 如果在timeout
秒內未接收到請求,將會(huì )調用handle_timeout()
并將返回handle_request()
。
- serve_forever(poll_interval=0.5)?
對請求進(jìn)行處理直至收到顯式的
shutdown()
請求。 每隔 poll_interval 秒對 shutdown 進(jìn)行輪詢(xún)。 忽略timeout
屬性。 它還會(huì )調用service_actions()
,這可被子類(lèi)或混合類(lèi)用來(lái)提供某個(gè)給定服務(wù)的專(zhuān)屬操作。 例如,ForkingMixIn
類(lèi)使用service_actions()
來(lái)清理僵尸子進(jìn)程。在 3.3 版更改: 將
service_actions
調用添加到serve_forever
方法。
- service_actions()?
此方法會(huì )在 the
serve_forever()
循環(huán)中被調用。 此方法可被子類(lèi)或混合類(lèi)所重載以執行某個(gè)給定服務(wù)的專(zhuān)屬操作,例如清理操作。3.3 新版功能.
- shutdown()?
通知
serve_forever()
循環(huán)停止并等待它完成。shutdown()
必須在serve_forever()
運行于不同線(xiàn)程時(shí)被調用否則它將發(fā)生死鎖。
- server_close()?
清理服務(wù)器。 此方法可被重載。
- address_family?
服務(wù)器套接字所屬的協(xié)議族。 常見(jiàn)的例子有
socket.AF_INET
和socket.AF_UNIX
。
- RequestHandlerClass?
用戶(hù)提供的請求處理句柄類(lèi);將為每個(gè)請求創(chuàng )建該類(lèi)的實(shí)例。
- server_address?
服務(wù)器所監聽(tīng)的地址。 地址的格式因具體協(xié)議族而不同;請參閱
socket
模塊的文檔了解詳情。 對于互聯(lián)網(wǎng)協(xié)議,這將是一個(gè)元組,其中包含一個(gè)表示地址的字符串,和一個(gè)表示端口號的整數,例如:('127.0.0.1', 80)
。
- socket?
將由服務(wù)器用于監聽(tīng)入站請求的套接字對象。
服務(wù)器類(lèi)支持下列類(lèi)變量:
- request_queue_size?
請求隊列的長(cháng)度。 如果處理單個(gè)請求要花費很長(cháng)的時(shí)間,則當服務(wù)器正忙時(shí)到達的任何請求都會(huì )被加入隊列,最多加入
request_queue_size
個(gè)請求。 一旦隊列被加滿(mǎn),來(lái)自客戶(hù)端的更多請求將收到 "Connection denied" 錯誤。 默認值為 5,但可在子類(lèi)中重載。
- socket_type?
服務(wù)器使用的套接字類(lèi)型;常見(jiàn)的有
socket.SOCK_STREAM
和socket.SOCK_DGRAM
這兩個(gè)值。
- timeout?
超時(shí)限制,以秒數表示,或者如果不限制超時(shí)則為
None
。 如果在超時(shí)限制期間沒(méi)有收到handle_request()
,則會(huì )調用handle_timeout()
方法。
有多個(gè)服務(wù)器方法可被服務(wù)器基類(lèi)的子類(lèi)例如
TCPServer
所重載;這些方法對服務(wù)器對象的外部用戶(hù)來(lái)說(shuō)并無(wú)用處。- finish_request(request, client_address)?
通過(guò)實(shí)例化
RequestHandlerClass
并調用其handle()
方法來(lái)實(shí)際處理請求。
- get_request()?
必須接受來(lái)自套接字的請求,并返回一個(gè) 2 元組,其中包含用來(lái)與客戶(hù)端通信的 new 套接字對象,以及客戶(hù)端的地址。
- handle_error(request, client_address)?
此函數會(huì )在
RequestHandlerClass
實(shí)例的handle()
方法引發(fā)異常時(shí)被調用。 默認行為是將回溯信息打印到標準錯誤并繼續處理其他請求。在 3.6 版更改: 現在只針對派生自
Exception
類(lèi)的異常調用此方法。
- handle_timeout()?
此函數會(huì )在
timeout
屬性被設為None
以外的值并且在超出時(shí)限之后仍未收到請求時(shí)被調用。 分叉服務(wù)器的默認行為是收集任何已退出的子進(jìn)程狀態(tài),而在線(xiàn)程服務(wù)器中此方法則不做任何操作。
- process_request(request, client_address)?
調用
finish_request()
來(lái)創(chuàng )建RequestHandlerClass
的實(shí)例。 如果需要,此函數可創(chuàng )建一個(gè)新的進(jìn)程或線(xiàn)程來(lái)處理請求;ForkingMixIn
和ThreadingMixIn
類(lèi)能完成此任務(wù)。
- server_activate()?
由服務(wù)器的構造器調用以激活服務(wù)器。 TCP 服務(wù)器的默認行為只是在服務(wù)器的套接字上發(fā)起調用
listen()
。 可以被重載。
- server_bind()?
由服務(wù)器的構造器調用以將套接字綁定到所需的地址。 可以被重載。
- verify_request(request, client_address)?
必須返回一個(gè)布爾值;如果值為
True
,請求將被處理。 而如果值為False
,請求將被拒絕。 此函數可被重載以實(shí)現服務(wù)器的訪(fǎng)問(wèn)控制。 默認實(shí)現總是返回True
。
在 3.6 版更改: 添加了對 context manager 協(xié)議的支持。 退出上下文管理器與調用
server_close()
等效。
請求處理句柄對象?
- class socketserver.BaseRequestHandler?
這是所有請求處理句柄對象的超類(lèi)。 它定義了下文列出的接口。 一個(gè)實(shí)體請求處理句柄子類(lèi)必須定義新的
handle()
方法,并可重載任何其他方法。 對于每個(gè)請求都會(huì )創(chuàng )建一個(gè)新的子類(lèi)的實(shí)例。- handle()?
此函數必須執行為請求提供服務(wù)所需的全部操作。 默認實(shí)現不執行任何操作。 它有幾個(gè)可用的實(shí)例屬性;請求為
self.request
;客戶(hù)端地址為self.client_address
;服務(wù)器實(shí)例為self.server
,如果它需要訪(fǎng)問(wèn)特定服務(wù)器信息的話(huà)。針對數據報或流服務(wù)的
self.request
類(lèi)型是不同的。 對于流服務(wù),self.request
是一個(gè)套接字對象;對于數據報服務(wù),self.request
是一對字符串與套接字。
- class socketserver.StreamRequestHandler?
- class socketserver.DatagramRequestHandler?
BaseRequestHandler
子類(lèi)重載了setup()
和finish()
方法,并提供了self.rfile
和self.wfile
屬性。self.rfile
和self.wfile
屬性可以被分別讀取或寫(xiě)入,以獲取請求數據或將數據返回給客戶(hù)端。這兩個(gè)類(lèi)的
rfile
屬性都支持io.BufferedIOBase
可讀接口,并且DatagramRequestHandler.wfile
還支持io.BufferedIOBase
可寫(xiě)接口。在 3.6 版更改:
StreamRequestHandler.wfile
也支持io.BufferedIOBase
可寫(xiě)接口。
例子?
socketserver.TCPServer
示例?
以下是服務(wù)端:
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
"""
The request handler class for our server.
It is instantiated once per connection to the server, and must
override the handle() method to implement communication to the
client.
"""
def handle(self):
# self.request is the TCP socket connected to the client
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# just send back the same data, but upper-cased
self.request.sendall(self.data.upper())
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# Create the server, binding to localhost on port 9999
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()
一個(gè)使用流(通過(guò)提供標準文件接口來(lái)簡(jiǎn)化通信的文件類(lèi)對象)的替代請求處理句柄類(lèi):
class MyTCPHandler(socketserver.StreamRequestHandler):
def handle(self):
# self.rfile is a file-like object created by the handler;
# we can now use e.g. readline() instead of raw recv() calls
self.data = self.rfile.readline().strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# Likewise, self.wfile is a file-like object used to write back
# to the client
self.wfile.write(self.data.upper())
區別在于第二個(gè)處理句柄的 readline()
調用將多次調用 recv()
直至遇到一個(gè)換行符,而第一個(gè)處理句柄的單次 recv()
調用只是返回在一次 sendall()
調用中由客戶(hù)端發(fā)送的內容。
以下是客戶(hù)端:
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Connect to server and send data
sock.connect((HOST, PORT))
sock.sendall(bytes(data + "\n", "utf-8"))
# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")
print("Sent: {}".format(data))
print("Received: {}".format(received))
這個(gè)示例程序的輸出應該是像這樣的:
服務(wù)器:
$ python TCPServer.py
127.0.0.1 wrote:
b'hello world with TCP'
127.0.0.1 wrote:
b'python is nice'
客戶(hù)端:
$ python TCPClient.py hello world with TCP
Sent: hello world with TCP
Received: HELLO WORLD WITH TCP
$ python TCPClient.py python is nice
Sent: python is nice
Received: PYTHON IS NICE
socketserver.UDPServer
示例?
以下是服務(wù)端:
import socketserver
class MyUDPHandler(socketserver.BaseRequestHandler):
"""
This class works similar to the TCP handler class, except that
self.request consists of a pair of data and client socket, and since
there is no connection the client address must be given explicitly
when sending data back via sendto().
"""
def handle(self):
data = self.request[0].strip()
socket = self.request[1]
print("{} wrote:".format(self.client_address[0]))
print(data)
socket.sendto(data.upper(), self.client_address)
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
server.serve_forever()
以下是客戶(hù)端:
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# SOCK_DGRAM is the socket type to use for UDP sockets
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# As you can see, there is no connect() call; UDP has no connections.
# Instead, data is directly sent to the recipient via sendto().
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")
print("Sent: {}".format(data))
print("Received: {}".format(received))
這個(gè)示例程序的輸出應該是與 TCP 服務(wù)器示例相一致的。
異步混合類(lèi)?
要構建異步處理句柄,請使用 ThreadingMixIn
和 ForkingMixIn
類(lèi)。
ThreadingMixIn
類(lèi)的示例:
import socket
import threading
import socketserver
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
data = str(self.request.recv(1024), 'ascii')
cur_thread = threading.current_thread()
response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
self.request.sendall(response)
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
def client(ip, port, message):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((ip, port))
sock.sendall(bytes(message, 'ascii'))
response = str(sock.recv(1024), 'ascii')
print("Received: {}".format(response))
if __name__ == "__main__":
# Port 0 means to select an arbitrary unused port
HOST, PORT = "localhost", 0
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
with server:
ip, port = server.server_address
# Start a thread with the server -- that thread will then start one
# more thread for each request
server_thread = threading.Thread(target=server.serve_forever)
# Exit the server thread when the main thread terminates
server_thread.daemon = True
server_thread.start()
print("Server loop running in thread:", server_thread.name)
client(ip, port, "Hello World 1")
client(ip, port, "Hello World 2")
client(ip, port, "Hello World 3")
server.shutdown()
這個(gè)示例程序的輸出應該是像這樣的:
$ python ThreadedTCPServer.py
Server loop running in thread: Thread-1
Received: Thread-2: Hello World 1
Received: Thread-3: Hello World 2
Received: Thread-4: Hello World 3
ForkingMixIn
類(lèi)的使用方式是相同的,區別在于服務(wù)器將為每個(gè)請求產(chǎn)生一個(gè)新的進(jìn)程。 僅在支持 fork()
的 POSIX 系統平臺上可用。