socketserver --- 用于網絡服務器的框架?
源代碼: Lib/socketserver.py
socketserver 模塊簡化了編寫網絡服務器的任務。
該模塊具有四個基礎實體服務器類:
-
class
socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)? This uses the Internet TCP protocol, which provides for continuous streams of data between the client and server. If bind_and_activate is true, the constructor automatically attempts to invoke
server_bind()andserver_activate(). The other parameters are passed to theBaseServerbase class.
-
class
socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)? 該類使用數據包,即一系列離散的信息分包,它們可能會無序地到達或在傳輸中丟失。 該類的形參與
TCPServer的相同。
-
class
socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)? -
class
socketserver.UnixDatagramServer(server_address, RequestHandlerClass, bind_and_activate=True)? 這兩個更常用的類與 TCP 和 UDP 類相似,但使用 Unix 域套接字;它們在非 Unix 系統平臺上不可用。 它們的形參與
TCPServer的相同。
這四個類會 同步地 處理請求;每個請求必須完成才能開始下一個請求。 這就不適用于每個請求要耗費很長時間來完成的情況,或者因為它需要大量的計算,又或者它返回了大量的數據而客戶端處理起來很緩慢。 解決方案是創建單獨的進程或線程來處理每個請求;ForkingMixIn 和 ThreadingMixIn 混合類可以被用于支持異步行為。
創建一個服務器需要分幾個步驟進行。 首先,你必須通過子類化 BaseRequestHandler 類并重載其 handle() 方法來創建一個請求處理句柄類;這個方法將處理傳入的請求。 其次,你必須實例化某個服務器類,將服務器地址和請求處理句柄類傳給它。 建議在 with 語句中使用該服務器。 然后再調用服務器對象的 handle_request() 或 serve_forever() 方法來處理一個或多個請求。 最后,調用 server_close() 來關閉套接字(除非你使用了 with 語句)。
當從 ThreadingMixIn 繼承線程連接行為時,你應當顯式地聲明你希望在突然關機時你的線程采取何種行為。 ThreadingMixIn 類定義了一個屬性 daemon_threads,它指明服務器是否應當等待線程終止。 如果你希望線程能自主行動你應當顯式地設置這個旗標;默認值為 False,表示 Python 將不會在 ThreadingMixIn 所創建的所有線程都退出之前退出。
服務器類具有同樣的外部方法和屬性,無論它們使用哪種網絡協議。
服務器創建的說明?
在繼承圖中有五個類,其中四個代表四種類型的同步服務器:
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
請注意 UnixDatagramServer 派生自 UDPServer,而不是 UnixStreamServer --- IP 和 Unix 流服務器的唯一區別是地址族,它會在兩種 Unix 服務器類中簡單地重復。
-
class
socketserver.ForkingMixIn? -
class
socketserver.ThreadingMixIn? 每種服務器類型的分叉和線程版本都可以使用這些混合類來創建。 例如,
ThreadingUDPServer的創建方式如下:class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
混合類先出現,因為它重載了
UDPServer中定義的一個方法。 設置各種屬性也會改變下層服務器機制的行為。ForkingMixIn和下文提及的分叉類僅在支持fork()的 POSIX 系統平臺上可用。socketserver.ForkingMixIn.server_close()會等待直到所有子進程完成,除非socketserver.ForkingMixIn.block_on_close屬性為假值。socketserver.ThreadingMixIn.server_close()會等待直到所有非守護類線程完成,除非socketserver.ThreadingMixIn.block_on_close屬性為假值。 請將ThreadingMixIn.daemon_threads設為True來使用守護類線程以便不等待線完成。在 3.7 版更改:
socketserver.ForkingMixIn.server_close()和socketserver.ThreadingMixIn.server_close()現在會等待直到所有子進程和非守護類線程完成。 請新增一個socketserver.ForkingMixIn.block_on_close類屬性來選擇 3.7 版之前的行為。
-
class
socketserver.ForkingTCPServer? -
class
socketserver.ForkingUDPServer? -
class
socketserver.ThreadingTCPServer? -
class
socketserver.ThreadingUDPServer? 這些類都是使用混合類來預定義的。
要實現一個服務,你必須從 BaseRequestHandler 派生一個類并重定義其 handle() 方法。 然后你可以通過組合某種服務器類型與你的請求處理句柄類來運行各種版本的服務。 請求處理句柄類對于數據報和流服務必須是不相同的。 這可以通過使用處理句柄子類 StreamRequestHandler 或 DatagramRequestHandler 來隱藏。
當然,你仍然需要動點腦筋! 舉例來說,如果服務包含可能被不同請求所修改的內存狀態則使用分叉服務器是沒有意義的,因為在子進程中的修改將永遠不會觸及保存在父進程中的初始狀態并傳遞到各個子進程。 在這種情況下,你可以使用線程服務器,但你可能必須使用鎖來保護共享數據的一致性。
另一方面,如果你是在編寫一個所有數據保存在外部(例如文件系統)的 HTTP 服務器,同步類實際上將在正在處理某個請求的時候“失聰” -- 如果某個客戶端在接收它所請求的所有數據時很緩慢這可能會是非常長的時間。 這時線程或分叉服務器會更為適用。
在某些情況下,合適的做法是同步地處理請求的一部分,但根據請求數據在分叉的子進程中完成處理。 這可以通過使用一個同步服務器并在請求處理句柄類 handle() 中進行顯式分叉來實現。
還有一種可以在既不支持線程也不支持 fork() 的環境(或者對于本服務來說這兩者開銷過大或是不適用)中處理多個同時請求的方式是維護一個顯式的部分完成的請求表并使用 selectors 來決定接下來要處理哪個請求(或者是否要處理一個新傳入的請求)。 這對于流服務來說特別重要,因為每個客戶端可能會連接很長的時間(如果不能使用線程或子進程)。 請參閱 asyncore 來了解另一種管理方式。
Server 對象?
-
class
socketserver.BaseServer(server_address, RequestHandlerClass)? 這是本模塊中所有 Server 對象的超類。 它定義了下文給出的接口,但沒有實現大部分的方法,它們應在子類中實現。 兩個形參存儲在對應的
server_address和RequestHandlerClass屬性中。-
handle_request()? 處理單個請求。 此函數會依次調用下列方法:
get_request(),verify_request()和process_request()。 如果用戶提供的處理句柄類的handle()方法引發了異常,則將調用服務器的handle_error()方法。 如果在timeout秒內未接收到請求,將會調用handle_timeout()并將返回handle_request()。
-
serve_forever(poll_interval=0.5)? 對請求進行處理直至收到顯式的
shutdown()請求。 每隔 poll_interval 秒對 shutdown 進行輪詢。 忽略timeout屬性。 它還會調用service_actions(),這可被子類或混合類用來提供某個給定服務的專屬操作。 例如,ForkingMixIn類使用service_actions()來清理僵尸子進程。在 3.3 版更改: 將
service_actions調用添加到serve_forever方法。
-
service_actions()? 此方法會在 the
serve_forever()循環中被調用。 此方法可被子類或混合類所重載以執行某個給定服務的專屬操作,例如清理操作。3.3 新版功能.
-
shutdown()? 通知
serve_forever()循環停止并等待它完成。shutdown()必須在serve_forever()運行于不同線程時被調用否則它將發生死鎖。
-
server_close()? 清理服務器。 可以被重載。
-
address_family? 服務器套接字所屬的協議族。 常見的例子有
socket.AF_INET和socket.AF_UNIX。
-
RequestHandlerClass? 用戶提供的請求處理句柄類;將為每個請求創建該類的實例。
-
server_address? The address on which the server is listening. The format of addresses varies depending on the protocol family; see the documentation for the
socketmodule for details. For Internet protocols, this is a tuple containing a string giving the address, and an integer port number:('127.0.0.1', 80), for example.
-
socket? 將由服務器用于監聽入站請求的套接字對象。
服務器類支持下列類變量:
-
request_queue_size? 請求隊列的長度。 如果處理單個請求要花費很長的時間,則當服務器正忙時到達的任何請求都會被加入隊列,最多加入
request_queue_size個請求。 一旦隊列被加滿,來自客戶端的更多請求將收到 "Connection denied" 錯誤。 默認值為 5,但可在子類中重載。
-
socket_type? 服務器使用的套接字類型;常見的有
socket.SOCK_STREAM和socket.SOCK_DGRAM這兩個值。
-
timeout? 超時限制,以秒數表示,或者如果不限制超時則為
None。 如果在超時限制期間沒有收到handle_request(),則會調用handle_timeout()方法。
有多個服務器方法可被服務器基類的子類例如
TCPServer所重載;這些方法對服務器對象的外部用戶來說并無用處。-
finish_request(request, client_address)? 通過實例化
RequestHandlerClass并調用其handle()方法來實際處理請求。
-
get_request()? 必須接受來自套接字的請求,并返回一個 2 元組,其中包含用來與客戶端通信的 new 套接字對象,以及客戶端的地址。
-
handle_error(request, client_address)? 此函數會在
RequestHandlerClass實例的handle()方法引發異常時被調用。 默認行為是將回溯信息打印到標準錯誤并繼續處理其他請求。在 3.6 版更改: 現在只針對派生自
Exception類的異常調用此方法。
-
handle_timeout()? 此函數會在
timeout屬性被設為None以外的值并且在超出時限之后仍未收到請求時被調用。 分叉服務器的默認行為是收集任何已退出的子進程狀態,而在線程服務器中此方法則不做任何操作。
-
process_request(request, client_address)? 調用
finish_request()來創建RequestHandlerClass的實例。 如果需要,此函數可創建一個新的進程或線程來處理請求;ForkingMixIn和ThreadingMixIn類能完成此任務。
-
server_bind()? 由服務器的構造器調用以將套接字綁定到所需的地址。 可以被重載。
-
verify_request(request, client_address)? 必須返回一個布爾值;如果值為
True,請求將被處理。 而如果值為False,請求將被拒絕。 此函數可被重載以實現服務器的訪問控制。 默認實現總是返回True。
在 3.6 版更改: 添加了對 context manager 協議的支持。 退出上下文管理器與調用
server_close()等效。-
請求處理句柄對象?
-
class
socketserver.BaseRequestHandler? 這是所有請求處理句柄對象的超類。 它定義了下文列出的接口。 一個實體請求處理句柄子類必須定義新的
handle()方法,并可重載任何其他方法。 對于每個請求都會創建一個新的子類的實例。-
handle()? 此函數必須執行為請求提供服務所需的全部操作。 默認實現不執行任何操作。 它有幾個可用的實例屬性;請求為
self.request;客戶端地址為self.client_address;服務器實例為self.server,如果它需要訪問特定服務器信息的話。針對數據報或流服務的
self.request類型是不同的。 對于流服務,self.request是一個套接字對象;對于數據報服務,self.request是一對字符串與套接字。
-
-
class
socketserver.StreamRequestHandler? -
class
socketserver.DatagramRequestHandler? BaseRequestHandler子類重載了setup()和finish()方法,并提供了self.rfile和self.wfile屬性。self.rfile和self.wfile屬性可以被分別讀取或寫入,以獲取請求數據或將數據返回給客戶端。這兩個類的
rfile屬性都支持io.BufferedIOBase可讀接口,并且DatagramRequestHandler.wfile還支持io.BufferedIOBase可寫接口。在 3.6 版更改:
StreamRequestHandler.wfile也支持io.BufferedIOBase可寫接口。
例子?
socketserver.TCPServer 示例?
以下是服務端:
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()
一個使用流(通過提供標準文件接口來簡化通信的文件類對象)的替代請求處理句柄類:
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())
區別在于第二個處理句柄的 readline() 調用將多次調用 recv() 直至遇到一個換行符,而第一個處理句柄的單次 recv() 調用只是返回在一次 sendall() 調用中由客戶端發送的內容。
以下是客戶端:
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))
這個示例程序的輸出應該是像這樣的:
服務器:
$ python TCPServer.py
127.0.0.1 wrote:
b'hello world with TCP'
127.0.0.1 wrote:
b'python is nice'
客戶端:
$ 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 示例?
以下是服務端:
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()
以下是客戶端:
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))
這個示例程序的輸出應該是與 TCP 服務器示例相一致的。
異步混合類?
要構建異步處理句柄,請使用 ThreadingMixIn 和 ForkingMixIn 類。
ThreadingMixIn 類的示例:
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()
這個示例程序的輸出應該是像這樣的:
$ 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 類的使用方式是相同的,區別在于服務器將為每個請求產生一個新的進程。 僅在支持 fork() 的 POSIX 系統平臺上可用。
