SocketServer簡化了網絡服務器的編寫。它有4個 類:TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer。這4個類是同步進行處理的,另 外通過ForkingMixIn和ThreadingMixIn類來支持異步。
創建服務器的步驟。首先,你必須創建一個請求處理類,它是BaseRequestHandler的子類並重載其handle()方法。其次,你必須 實例化一個服務器類,傳入服務器的地址和請求處理程序類。最後,調用handle_request()(一般是調用其他事件循環或者使用 select())或serve_forever()。
集成ThreadingMixIn類時需要處理異常關閉。daemon_threads指示服務器是否要等待線程終止,要是線程互相獨立,必須要設置爲True,默認是False。
無論用什麼網絡協議,服務器類有相同的外部方法和屬性。
該模塊在python3中已經更名爲socketserver。
服務器類型
5種類型:BaseServer,TCPServer,UnixStreamServer,UDPServer,UnixDatagramServer。 注意:BaseServer不直接對外服務。
服務器對象
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區間的請求。 常用。
請求處理器
處理器接收數據並決定如何操作。它負責在socket層之上實現協議(i.e., HTTP, XML-RPC, or AMQP),讀取數據,處理並寫反應。可以重載的方法如下:
setup(): 準備請求處理. 默認什麼都不做,StreamRequestHandler中會創建文件類似的對象以讀寫socket.
handle(): 處理請求。解析傳入的請求,處理數據,併發送響應。默認什麼都不做。常用變量:self.request,self.client_address,self.server。
finish(): 環境清理。默認什麼都不做,如果setup產生異常,不會執行finish。
通常只需要重載handle。self.request的類型和數據報或流的服務不同。對於流服務,self.request是socket 對象;對於數據報服務,self.request是字符串和socket 。可以在子類StreamRequestHandler或DatagramRequestHandler中重載,重寫setup()和finish() ,並提供self.rfile和self.wfile屬性。 self.rfile和self.wfile可以讀取或寫入,以獲得請求數據或將數據返回到客戶端。
Echo實例
TCPServer
TCPServer.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import SocketServerclass MyTCPHandler(SocketServer.BaseRequestHandler): """ The RequestHandler 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() |
另外一種方式是使用流,一次讀一行。
1 2 3 4 5 6 7 8 9 10 | 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()) |
客戶端:
1 2 3 4 5 6 7 8 | import socketimport sysHOST, PORT = "localhost" , 9999data = " " .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(data + "\n" ) # Receive data from the server and shut down received = sock.recv( 1024 ) finally : sock.close() print "Sent: {}" . format (data) print "Received: {}" . format (received) |
《The Python Standard Library by Example 2011》有更詳細的echo實例,參見11.3.5部分。 執行結果:
1 2 3 4 5 6 7 8 9 10 | # python TCPServer.py 127.0.0.1 wrote: hello world with TCP 127.0.0.1 wrote: python is nice # python TCPClient.py Sent: Received: # python TCPClient.py hello world with TCPSent: hello world with TCP Received: HELLO WORLD WITH TCP # python TCPClient.py python is niceSent: python is nice Received: PYTHON IS NICE |
UDPServer
UDPServer.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import SocketServerclass 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 server = SocketServer.UDPServer((HOST, PORT), MyUDPHandler) server.serve_forever() |
UDPClient.py
1 | import socketimport sysHOST, PORT = "localhost" , 9999data = " " .join(sys.argv[ 1 :]) # SOCK_DGRAM is the socket type to use for UDP socketssock = 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(data + "\n", (HOST, PORT))received = sock.recv(1024)print "Sent: {}".format(data)print "Received: {}".format(received) |
執行和UDP類似。
異步
ThreadingMixIn的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | import socketimport threadingimport SocketServerclass ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler): def handle( self ): data = self .request.recv( 1024 ) cur_thread = threading.current_thread() response = "{}: {}" . format (cur_thread.name, data) self .request.sendall(response) class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): passdef client(ip, port, message): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((ip, port)) try : sock.sendall(message) response = sock.recv( 1024 ) print "Received: {}" . format (response) finally : sock.close() if __name__ = = "__main__" : # Port 0 means to select an arbitrary unused port HOST, PORT = "localhost" , 0 server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler) 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() |
執行結果:
1 2 3 4 5 | $ 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的使用方法類似,只不過是用進程代替了線程。《The Python Standard Library by Example 2011》中有相關實例。