socketserver
— 網路伺服器框架¶
原始碼: Lib/socketserver.py
socketserver
模組簡化了編寫網路伺服器的任務。
可用性:非 WASI。
此模組在 WebAssembly 上不起作用或不可用。有關更多資訊,請參閱 WebAssembly 平臺。
有四種基本的具體伺服器類
- class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)¶
這使用網際網路TCP協議,它在客戶端和伺服器之間提供連續的資料流。如果 bind_and_activate 為 True,則建構函式會自動嘗試呼叫
server_bind()
和server_activate()
。其他引數將傳遞給BaseServer
基類。
- 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 伺服器之間的唯一區別是地址族。
- class socketserver.ForkingMixIn¶
- class socketserver.ThreadingMixIn¶
可以使用這些混合類建立每種型別的伺服器的分叉和執行緒版本。例如,
ThreadingUDPServer
的建立方式如下class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
混合類優先,因為它會覆蓋
UDPServer
中定義的方法。設定各種屬性也會改變底層伺服器機制的行為。ForkingMixIn
和下面提到的 Forking 類僅在支援fork()
的 POSIX 平臺上可用。- block_on_close¶
ForkingMixIn.server_close
會一直等到所有子程序完成,除非block_on_close
屬性為False
。ThreadingMixIn.server_close
會一直等到所有非守護執行緒完成,除非block_on_close
屬性為False
。
- max_children¶
指定
ForkingMixIn
同時處理請求的子程序數量。如果達到限制,新請求將等待一個子程序完成。
- daemon_threads¶
對於
ThreadingMixIn
,透過將ThreadingMixIn.daemon_threads
設定為True
來使用守護執行緒,這樣就不需要等待執行緒完成。
3.7 版本中已更改:
ForkingMixIn.server_close
和ThreadingMixIn.server_close
現在會等待所有子程序和非守護執行緒完成。添加了一個新的ForkingMixIn.block_on_close
類屬性,以選擇啟用 3.7 之前的行為。
- class socketserver.ForkingTCPServer¶
- class socketserver.ForkingUDPServer¶
- class socketserver.ThreadingTCPServer¶
- class socketserver.ThreadingUDPServer¶
- class socketserver.ForkingUnixStreamServer¶
- class socketserver.ForkingUnixDatagramServer¶
- class socketserver.ThreadingUnixStreamServer¶
- class socketserver.ThreadingUnixDatagramServer¶
這些類是使用混合類預定義的。
3.12 版本新增: ForkingUnixStreamServer
和 ForkingUnixDatagramServer
類已新增。
要實現服務,您必須從 BaseRequestHandler
派生一個類並重新定義其 handle()
方法。然後,您可以將其中一個伺服器類與您的請求處理程式類組合,以執行各種版本的服務。請求處理程式類對於資料報或流服務必須不同。這可以透過使用處理程式子類 StreamRequestHandler
或 DatagramRequestHandler
來隱藏。
當然,你仍然需要思考!例如,如果服務在記憶體中包含可能被不同請求修改的狀態,則使用分叉伺服器毫無意義,因為子程序中的修改永遠不會到達父程序中儲存並傳遞給每個子程序的初始狀態。在這種情況下,您可以使用執行緒伺服器,但您可能需要使用鎖來保護共享資料的完整性。
另一方面,如果您正在構建一個 HTTP 伺服器,其中所有資料都儲存在外部(例如,在檔案系統中),同步類將在處理一個請求時基本上使服務“失聰”——如果客戶端接收所請求的所有資料很慢,這可能會持續很長時間。這裡適合使用執行緒或分叉伺服器。
在某些情況下,可能適合同步處理請求的一部分,但根據請求資料在分叉的子程序中完成處理。這可以透過使用同步伺服器並在請求處理程式類 handle()
方法中執行顯式分叉來實現。
在不支援執行緒或 fork()
的環境中(或者這些方法對於服務來說過於昂貴或不合適)處理多個併發請求的另一種方法是維護一個顯式的部分完成請求表,並使用 selectors
來決定接下來處理哪個請求(或是否處理新的傳入請求)。這對於每個客戶端可能長時間連線的流服務尤其重要(如果不能使用執行緒或子程序)。
伺服器物件¶
- 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 秒輪詢一次關閉。忽略timeout
屬性。它還會呼叫service_actions()
,子類或混合類可以使用它來提供特定於給定服務的操作。例如,ForkingMixIn
類使用service_actions()
來清理殭屍子程序。3.3 版本中已更改:
service_actions
呼叫已新增到serve_forever
方法中。
- service_actions()¶
在
serve_forever()
迴圈中呼叫此方法。子類或混合類可以重寫此方法,以執行特定於給定服務的操作,例如清理操作。在 3.3 版本加入。
- shutdown()¶
告知
serve_forever()
迴圈停止並等待它完成。必須在serve_forever()
在另一個執行緒中執行時呼叫shutdown()
,否則會發生死鎖。
- server_close()¶
清理伺服器。可以重寫。
- address_family¶
伺服器套接字所屬的協議族。常見示例是
socket.AF_INET
、socket.AF_INET6
和socket.AF_UNIX
。如果要使用 IPv6 伺服器類,請使用類屬性address_family = AF_INET6
子類化此模組中的 TCP 或 UDP 伺服器類。
- RequestHandlerClass¶
使用者提供的請求處理程式類;為每個請求建立一個此類的例項。
- server_address¶
伺服器正在監聽的地址。地址的格式因協議族而異;有關詳細資訊,請參閱
socket
模組的文件。對於網際網路協議,這是一個包含地址字串和整數埠號的元組:例如('127.0.0.1', 80)
。
- socket¶
伺服器將監聽傳入請求的套接字物件。
伺服器類支援以下類變數
- request_queue_size¶
請求佇列的大小。如果處理單個請求需要很長時間,那麼在伺服器忙碌時到達的任何請求都會被放入佇列中,最多可容納
request_queue_size
個請求。一旦佇列滿了,來自客戶端的進一步請求將收到“連線被拒絕”錯誤。預設值通常為 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 元組。
- 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 版本中已更改: 添加了對上下文管理器協議的支援。退出上下文管理器等同於呼叫
server_close()
。
請求處理程式物件¶
- class socketserver.BaseRequestHandler¶
這是所有請求處理程式物件的超類。它定義了下面給出的介面。具體的請求處理程式子類必須定義一個新的
handle()
方法,並可以覆蓋其他任何方法。為每個請求建立一個子類的新例項。- handle()¶
此函式必須完成服務請求所需的所有工作。預設實現不執行任何操作。它可以使用幾個例項屬性;請求可用作
request
;客戶端地址可用作client_address
;伺服器例項可用作server
,以防需要訪問每個伺服器的資訊。資料報或流服務的
request
型別不同。對於流服務,request
是一個套接字物件;對於資料報服務,request
是一個字串和套接字的對。
- request¶
用於與客戶端通訊的 *新*
socket.socket
物件。
- client_address¶
由
BaseServer.get_request()
返回的客戶端地址。
- server¶
用於處理請求的
BaseServer
物件。
- class socketserver.StreamRequestHandler¶
- class socketserver.DatagramRequestHandler¶
這些
BaseRequestHandler
子類覆蓋了setup()
和finish()
方法,並提供了rfile
和wfile
屬性。- rfile¶
一個檔案物件,用於從中讀取請求。支援
io.BufferedIOBase
可讀介面。
- wfile¶
一個檔案物件,用於寫入回覆。支援
io.BufferedIOBase
可寫介面
3.6 版本中已更改:
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
pieces = [b'']
total = 0
while b'\n' not in pieces[-1] and total < 10_000:
pieces.append(self.request.recv(2000))
total += len(pieces[-1])
self.data = b''.join(pieces)
print(f"Received from {self.client_address[0]}:")
print(self.data.decode("utf-8"))
# just send back the same data, but upper-cased
self.request.sendall(self.data.upper())
# after we return, the socket will be closed.
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.
# We limit ourselves to 10000 bytes to avoid abuse by the sender.
self.data = self.rfile.readline(10000).rstrip()
print(f"{self.client_address[0]} wrote:")
print(self.data.decode("utf-8"))
# Likewise, self.wfile is a file-like object used to write back
# to the client
self.wfile.write(self.data.upper())
不同之處在於,第二個處理程式中的 readline()
呼叫會多次呼叫 recv()
,直到遇到換行符,而第一個處理程式必須使用 recv()
迴圈來累積資料,直到遇到換行符。如果它只使用單個 recv()
而沒有迴圈,它只會返回到目前為止從客戶端收到的資料。TCP 是基於流的:資料按照發送的順序到達,但客戶端 send()
或 sendall()
呼叫與伺服器上接收它所需的 recv()
呼叫次數之間沒有關聯。
這是客戶端側
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, "utf-8"))
sock.sendall(b"\n")
# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")
print("Sent: ", data)
print("Received:", 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(f"{self.client_address[0]} wrote:")
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: ", data)
print("Received:", 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 平臺上可用。