一、socketserver模塊簡介 socketserver模塊簡化了網路編程,模塊下有五個服務類:BaseServer、TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer 。這五個類的關係如下: + + | BaseServer | + ...
一、socketserver模塊簡介
socketserver模塊簡化了網路編程,模塊下有五個服務類:BaseServer、TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer 。這五個類的關係如下:
+------------+ | BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer |
1、四個基本的同步伺服器類簡介:
class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True):TCP數據流伺服器
class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True):UDP數據報伺服器
class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True):僅限於Unix系統的,Unix套接字流
class socketserver.UnixDatagramServer(server_address, RequestHandlerClass, bind_and_activate=True):僅限於Unix系統的,Unix數據報
他們的參數意義相同,如下:
server_address:伺服器的地址,他應該是一個元組包含地址和埠如:('192.168.1.1',9000),
RequestHandlerClass:我們自定義的類,類中必須重寫handle()方法。用於處理所有socket請求。
bind_and_activate:如果為True,將自動調用server_bind()和server_activate()。
這四個類運行時只能處理一個請求,也就是一個服務端只能對應一個客戶端,這對於我們將來要編寫的FTP伺服器可能不適用,因為我們希望一個服務能處理多個客戶端,下麵我們來看socketserver為我們提供的兩個處理非同步的類。
2、非同步伺服器類簡介
class socketserver.ForkingMixIn:啟用多進程
class socketserver.ThreadingMixIn:啟用多線程
創建非同步服務的方法非常簡單,下麵創建一個簡單的TCP框架為例:
import socketserver class MyTCPServer(socketserver.BaseRequestHandler): # 自定義TCP服務類,必須繼承socketserver.BaseRequestHandler def handle(self): # 重寫此類, ''' 我們要處理的任務 :return: ''' self.fun() # 執行我們的任務 def fun(self): print('Hello World') class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): # 類名隨便起,必須要繼承這兩個類 pass if __name__ == "__main__": ip_port = ("127.0.0.1", 9000) # TCP伺服器的地址和埠 with socketserver.ThreadingTCPServer(ip_port, MyTCPServer) as server: server.serve_forever() # 開啟TCP服務
這樣我們就簡單的創建了一個非同步TCP伺服器框架,其實ThreadingTCPServer這個類我們不用自己創建,因為socketserver已經為我們創建好了,如下:
class ForkingUDPServer(ForkingMixIn, UDPServer): pass # 多進程UDP伺服器 class ForkingTCPServer(ForkingMixIn, TCPServer): pass # 多進程TCP伺服器 class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass # 多線程UDP伺服器 class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass # 多線程TCP伺服器
如果覺得socketserver提供的這四個類不是你想要的,那麼你就可以像上面那樣自己定義,上面都是服務類,通過服務類實例化對象,但是目前還不知道對象擁有哪些方法,因為這些服務類都是繼承的BaseServer類,所以方法都在BaseServer類中,有些方法只是定義了介面,在子類中實現的。
3、服務對象常用方法
class socketserver.BaseServer(server_address, RequestHandlerClass):
fileno():返回伺服器正在監聽的套接字的文件描述符(int類型的數字)。此函數最常傳遞給選擇器,以允許監視同一進程中的多個伺服器。
handle_request():處理單個請求。此函數按順序調用以下方法:get_request()、verify_request()和process_request()。如果handler類的用戶提供的handle()方法引發異常,則將調用伺服器的handle_error()方法。如果在超時秒內未收到請求,將調用handle_timeout(),並返回handle_request()。
serve_forever(poll_interval=0.5):定時任務,通常是在一個線程中,每poll_interval秒輪詢一次,直到調用shutdown停止。
service_actions():該函數被serve_forever定時函數重覆調用,這個方法我們可以繼承BaseServer,然後重寫此方法。
shutdown():此方法用於停止serve_forever()定時任務。
socket:socket對象。
socket_type:socket套接字類型,TCP,UDP等。
allow_reuse_address:伺服器是否允許地址的重用。預設為false ,並且可在子類中更改。
address_family:設置socket套接字家族。
server_address:值是一個元組,socket伺服器地址和監聽的埠。
server_activate():伺服器將處於監聽狀態,該函數可被重寫,其實他的內部就是self.socket.listen(self.request_queue_size)。
server_bind():將socket綁定到地址上,可以被重寫。
get_request():此方法的前提是必須接收到來自套接字的請求,返回一個元組(與客戶端通信的新套接字對象,客戶端地址)。其實該方法就是將self.socket.accept()的結果返回。
server_close():關閉服務(關閉socket),此方法可被重寫。
RequestHandlerClass:值是類名,這個類是我們定義的用於創建實例處理我們的請求,如上面TCP非同步框架中的MyTCPServer,這個類就是RequestHandlerClass的值。
request_queue_size:請求隊列的大小。如果處理單個請求需要很長時間,在伺服器繁忙時會將請求放到隊列中,當請求數達到request_queue_size的值時。來自客戶端的進一步請求將得到一個“拒絕連接”錯誤。預設值通常是5,但是這個值可以被子類覆蓋。
finish_request(request, client_address):此方法會實例化RequestHandlerClass並調用它的handle()方法來實際處理請求。
process_request(request,client_address):調用finish_request()來創建RequestHandlerClass的一個實例。我們可以自己創建線程池或進程池來調用這個函數,實現伺服器處理多個請求的問題,ForkingMixIn和ThreadingMixIn類就是這樣做的。
handle_error(request,client_address):如果RequestHandlerClass實例的handle()方法引發異常,則調用此函數。預設操作是將回溯列印到標準錯誤,並繼續處理其他請求。在版本3.6中更改:現在僅調用從Exception類派生的異常。
timeout:超時時間(以秒為單位),如果是None,會一直阻塞。如果設置了timeout,handle_request()在超時期間沒有收到傳入請求,則調用handle_timeout()方法。
handle_timeout():當timeout屬性被設置為None以外的值,並且超時周期已經過去而沒有收到任何請求時,將調用此函數。多進程 伺服器的預設操作是收集已退出的任何子進程的狀態,而線上程伺服器中,此方法不執行任何操作。
verify_request(request,client_address):返回一個布爾值;如果值為真,請求將被處理,如果值為假,請求將被拒絕。可以重寫此函數以實現伺服器的訪問控制。預設實現只是一句return True。
上面這些都是服務對象的方法,下麵來介紹處理socket請求類BaseRequestHandler。
4、客戶端請求處理類BaseRequestHandler
class socketserver.BaseRequestHandler:
這是所有socket請求處理程式的基類。它只定義了介面,而沒有實現,如果想要使用介面,我們首先繼承BaseRequestHandler,然後在子類中重寫這些方法。每個socket請求處理程式子類必須重寫handle()方法,因為該方法是用於處理所有socket請求。該類的方法如下:
setup():在handle()方法之前調用,執行初始化操作。 預設不執行任何操作,我們可以重寫此方法來實現程式的初始化。
handle():所有socket請求任務都是在這個函數內部完成的,我們在子類中必須重寫此方法,並處理socket請求,因為預設基類中的handle()的實現不執行任何操作。
finish():在handle()方法之後調用以執行清理操作。預設實現不執行任何操作。如果setup()引發異常,則不會調用此函數。
雖然上面的介面都只是定義而沒有實現,但是它的實例屬性還是很有用的;
self.request;客戶端和服務端的連接對象,用於發送數據,接收數據。
self.client_address:socket客戶端地址 。
self.server:socket服務端信息。
class socketserver.StreamRequestHandler
class socketserver.DatagramRequestHandler
這兩個類是socketserver繼承BaseRequestHandler後重寫了setup(),finish(),實現了對讀,寫緩衝區的設置,有興趣的可以看看源碼。
5、官方示例:
TCP同步服務示例:
# 服務端 import socketserver class MyTCPHandler(socketserver.BaseRequestHandler): # 自定義類,繼承BaseRequestHandler,處理socket請求 def handle(self): # socket客戶端請求 self.data = self.request.recv(1024).strip() # 接收socket客戶端發來的數據 print("{} wrote:".format(self.client_address[0])) print(self.data) self.request.sendall(self.data.upper()) # 將數據大寫後發給客戶端 ''' class MyTCPHandler(socketserver.StreamRequestHandler): # 自定義類,功能與上面的一樣,只不過是繼承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()) ''' if __name__ == "__main__": HOST, PORT = "localhost", 9999 with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server: server.serve_forever() # 啟用TCP伺服器 # 列印內容如下 127.0.0.1 wrote: b'Hello World' # 客戶端 import socket import sys HOST, PORT = "localhost", 9999 data = "Hello World" # 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)) # 列印內容如下 Sent: Hello World Received: HELLO WORLD
UDP服務示例:
# 服務端 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() # 列印內容如下 127.0.0.1 wrote: b'Hello World' # 客戶端 import socket import sys HOST, PORT = "localhost", 9999 data = "Hello World" # 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)) # 列印內容如下 Sent: Hello World Received: HELLO WORLD
TCP服務非同步示例:
import socket import threading import socketserver class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler): # 自定義socket請求處理類 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): ''' socket客戶端 :param ip: 服務段的IP地址 :param port: 服務端的埠 :param message: 給服務端發送的消息 :return: ''' 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__": HOST, PORT = "localhost", 0 # 埠是0隨機獲取一個未被使用的埠 server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler) with server: ip, port = server.server_address # 獲取服務端的IP地址和埠號 server_thread = threading.Thread(target=server.serve_forever) # 創建線程對象 server_thread.daemon = True # 守護線程 server_thread.start() # 開啟線程,線上程中開啟TCP伺服器 print("Server loop running in thread:", server_thread.name) # 模擬三個socket客戶端連接TCP伺服器 client(ip, port, "Hello World 1") client(ip, port, "Hello World 2") client(ip, port, "Hello World 3") server.shutdown() # 列印內容如下: 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
參考文檔:https://docs.python.org/3/library/socketserver.html?highlight=socketserver#module-socketserver