本節主要是講解python3網路編程之socketserver,在上一節中我們講到了socket。由於socket無法支持多用戶和多併發,於是就有了socket server。 socket server最主要的作用就是實現併發處理。 socketserver中包含了兩種類: PS:一般情況下,所有 ...
本節主要是講解python3網路編程之socketserver,在上一節中我們講到了socket。由於socket無法支持多用戶和多併發,於是就有了socket server。
socket server最主要的作用就是實現併發處理。
socketserver中包含了兩種類:
- 服務類(server class):提供了許多方法:像綁定,監聽,運行等等(也就是建立連接的過程)
- 請求處理類(request handle class):專註於如何處理用戶所發送的數據(也就是事物邏輯)
PS:一般情況下,所有的服務,都是先建立連接,也就是建立一個服務類的實例,然後處理用戶的請求,也就是建立一個請求處理類的實例。
接下來就看一下這兩個類。
服務類
服務類有五種類型:
- BaseServer:不直接對外服務。
- TCPServer:針對TCP套接字流。
- UDPServer:針對UDP數據套接字。
- UnixStream:針對Unix套接字,不常用。
- UnixDatagramServer:針對Unix套接字,不常用。
他們之間的繼承關係如下:
+------------+ | BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+
服務類的方法:
class SocketServer.BaseServer:這是模塊中的所有伺服器對象的超類。它定義了介面,如下所述,但是大多數的方法不實現,在子類中進行細化。 BaseServer.fileno():返回伺服器監聽套接字的整數文件描述符。通常用來傳遞給select.select(), 以允許一個進程監視多個伺服器。 BaseServer.handle_request():處理單個請求。處理順序:get_request(), verify_request(), process_request()。如果用戶提供handle()方法拋出異常,將調用伺服器的handle_error()方法。如果self.timeout內沒有請求收到, 將調用handle_timeout()並返回handle_request()。 BaseServer.serve_forever(poll_interval=0.5): 處理請求,直到一個明確的shutdown()請求。每poll_interval秒輪詢一次shutdown。忽略self.timeout。如果你需要做周期性的任務,建議放置在其他線程。 BaseServer.shutdown():告訴serve_forever()迴圈停止並等待其停止。python2.6版本。 BaseServer.address_family: 地址家族,比如socket.AF_INET和socket.AF_UNIX。 BaseServer.RequestHandlerClass:用戶提供的請求處理類,這個類為每個請求創建實例。 BaseServer.server_address:伺服器偵聽的地址。格式根據協議家族地址的各不相同,請參閱socket模塊的文檔。 BaseServer.socketSocket:伺服器上偵聽傳入的請求socket對象的伺服器。 伺服器類支持下麵的類變數: BaseServer.allow_reuse_address:伺服器是否允許地址的重用。預設為false ,並且可在子類中更改。 BaseServer.request_queue_size 請求隊列的大小。如果單個請求需要很長的時間來處理,伺服器忙時請求被放置到隊列中,最多可以放request_queue_size個。一旦隊列已滿,來自客戶端的請求將得到 “Connection denied”錯誤。預設值通常為5 ,但可以被子類覆蓋。 BaseServer.socket_type:伺服器使用的套接字類型; socket.SOCK_STREAM和socket.SOCK_DGRAM等。 BaseServer.timeout:超時時間,以秒為單位,或 None表示沒有超時。如果handle_request()在timeout內沒有收到請求,將調用handle_timeout()。 下麵方法可以被子類重載,它們對伺服器對象的外部用戶沒有影響。 BaseServer.finish_request():實際處理RequestHandlerClass發起的請求並調用其handle()方法。 常用。 BaseServer.get_request():接受socket請求,並返回二元組包含要用於與客戶端通信的新socket對象,以及客戶端的地址。 BaseServer.handle_error(request, client_address):如果RequestHandlerClass的handle()方法拋出異常時調用。預設操作是列印traceback到標準輸出,並繼續處理其他請求。 BaseServer.handle_timeout():超時處理。預設對於forking伺服器是收集退出的子進程狀態,threading伺服器則什麼都不做。 BaseServer.process_request(request, client_address) :調用finish_request()創建RequestHandlerClass的實例。如果需要,此功能可以創建新的進程或線程來處理請求,ForkingMixIn和ThreadingMixIn類做到這點。常用。 BaseServer.server_activate():通過伺服器的構造函數來激活伺服器。預設的行為只是監聽伺服器套接字。可重載。 BaseServer.server_bind():通過伺服器的構造函數中調用綁定socket到所需的地址。可重載。 BaseServer.verify_request(request, client_address):返回一個布爾值,如果該值為True ,則該請求將被處理,反之請求將被拒絕。此功能可以重寫來實現對伺服器的訪問控制。預設的實現始終返回True。client_address可以限定客戶端,比如只處理指定ip區間的請求。 常用。View Code
這個幾個服務類都是同步處理請求的:一個請求沒處理完不能處理下一個請求。要想支持非同步模型,可以利用多繼承讓server類繼承ForkingMixIn 或 ThreadingMixIn mix-in classes。
ForkingMixIn利用多進程(分叉)實現非同步。
ThreadingMixIn利用多線程實現非同步。
請求處理類
要實現一項服務,還必須派生一個handler class請求處理類,並重寫父類的handle()方法。handle方法就是用來專門是處理請求的。該模塊是通過服務類和請求處理類組合來處理請求的。
socketserver模塊提供的請求處理類有BaseRequestHandler,以及它的派生類StreamRequestHandler和DatagramRequestHandler。從名字看出可以一個處理流式套接字,一個處理數據報套接字。
請求處理類有三種方法:
- setup()
- handle()
- finish()
setup()
Called before the handle()
method to perform any initialization actions required. The default implementation does nothing.
也就是在handle()之前被調用,主要的作用就是執行處理請求之前的初始化相關的各種工作。預設不會做任何事。(如果想要讓其做一些事的話,就要程式員在自己的請求處理器中覆蓋這個方法(因為一般自定義的請求處理器都要繼承python中提供的BaseRequestHandler,ps:下文會提到的),然後往裡面添加東西即可)
handle()
This function must do all the work required to service a request. The default implementation does nothing. Several instance attributes are available to it; the request is available as self.request
; the client address as self.client_address
; and the server instance as self.server
, in case it needs access to per-server information.
The type of self.request
is different for datagram or stream services. For stream services,self.request
is a socket object; for datagram services, self.request
is a pair of string and socket.
handle()的工作就是做那些所有與處理請求相關的工作。預設也不會做任何事。他有數個實例參數:self.request self.client_address self.server
finish()
Called after the handle()
method to perform any clean-up actions required. The default implementation does nothing. If setup()
raises an exception, this function will not be called.
在handle()方法之後會被調用,他的作用就是執行當處理完請求後的清理工作,預設不會做任何事
然後我們來看一下Handler的源碼:
class BaseRequestHandler: """Base class for request handler classes. This class is instantiated for each request to be handled. The constructor sets the instance variables request, client_address and server, and then calls the handle() method. To implement a specific service, all you need to do is to derive a class which defines a handle() method. The handle() method can find the request as self.request, the client address as self.client_address, and the server (in case it needs access to per-server information) as self.server. Since a separate instance is created for each request, the handle() method can define arbitrary other instance variariables. """ def __init__(self, request, client_address, server): self.request = request self.client_address = client_address self.server = server self.setup() try: self.handle() finally: self.finish() def setup(self): pass def handle(self): pass def finish(self): pass # The following two classes make it possible to use the same service # class for stream or datagram servers. # Each class sets up these instance variables: # - rfile: a file object from which receives the request is read # - wfile: a file object to which the reply is written # When the handle() method returns, wfile is flushed properly class StreamRequestHandler(BaseRequestHandler): """Define self.rfile and self.wfile for stream sockets.""" # Default buffer sizes for rfile, wfile. # We default rfile to buffered because otherwise it could be # really slow for large data (a getc() call per byte); we make # wfile unbuffered because (a) often after a write() we want to # read and we need to flush the line; (b) big writes to unbuffered # files are typically optimized by stdio even when big reads # aren't. rbufsize = -1 wbufsize = 0 # A timeout to apply to the request socket, if not None. timeout = None # Disable nagle algorithm for this socket, if True. # Use only when wbufsize != 0, to avoid small packets. disable_nagle_algorithm = False def setup(self): self.connection = self.request if self.timeout is not None: self.connection.settimeout(self.timeout) if self.disable_nagle_algorithm: self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) self.rfile = self.connection.makefile('rb', self.rbufsize) self.wfile = self.connection.makefile('wb', self.wbufsize) def finish(self): if not self.wfile.closed: try: self.wfile.flush() except socket.error: # An final socket error may have occurred here, such as # the local error ECONNABORTED. pass self.wfile.close() self.rfile.close() class DatagramRequestHandler(BaseRequestHandler): # XXX Regrettably, I cannot get this working on Linux; # s.recvfrom() doesn't return a meaningful client address. """Define self.rfile and self.wfile for datagram sockets.""" def setup(self): from io import BytesIO self.packet, self.socket = self.request self.rfile = BytesIO(self.packet) self.wfile = BytesIO() def finish(self): self.socket.sendto(self.wfile.getvalue(), self.client_address)Handler源碼
從源碼中可以看出,BaseRequestHandler中的setup()/handle()/finish()什麼內容都沒有定義,而他的兩個派生類StreamRequestHandler和DatagramRequestHandler則都重寫了setup()/finish()。
因此當我們需要自己編寫socketserver程式時,只需要合理選擇StreamRequestHandler和DatagramRequestHandler之中的一個作為父類,然後自定義一個請求處理類,併在其中重寫handle()方法即可。
創建一個socketserver的步驟:
- 自己創建一個請求處理類,並且這個類要繼承BaseRequestHandler,並且還要重寫父類裡面的handle()方法。
這個子類用來處理客戶端的請求
與客戶端所有的交互都是在handle()方法中重寫
- 實例化一個server(如TCPServer)類,並且講Server_IP和上一步創建的子類傳給這個實例化的類(此處是TCPServer)作為參數。
- 調用第二步實例化出來的對象的方法,這裡假定這個實例化出來的對象為server。
server.handle_request() # 只處理一個請求,處理完後退出
server.serve_forever() # 處理多個請求,永遠執行
- 調用close()方法關閉server。
代碼示例:
伺服器端:
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 server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) # Activate the server; this will keep running until you # interrupt the program with Ctrl-C server.serve_forever()伺服器端
客戶端:
import socket import sys HOST, PORT = "localhost", 9999 data = " ".join(sys.argv[1:]) # Create a socket (SOCK_STREAM means a TCP socket) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: # 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") finally: sock.close() print("Sent: {}".format(data)) print("Received: {}".format(received))客戶端
但你發現,上面的代碼,依然不能同時處理多個連接。
讓你的socketserver併發起來, 必須選擇使用以下一個多併發的類
class socketserver.ForkingTCPServer class socketserver.ForkingUDPServer class socketserver.ThreadingTCPServer class socketserver.ThreadingUDPServer
所以,只需要把下麵這一句
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
換成下麵這個,就可以多併發了,這樣,客戶端每連進一個來,伺服器端就會分配一個新的線程來處理這個客戶端的請求
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)