python標準庫之SocketServer

socketserver簡化了網絡服務器的編寫。

  socketserver內包含了四個基於網絡服務的類:

1.TCPServer:使用TCP協議,提供在客戶端和服務端進行持續的流式數據通訊。

2.UDPServer:使用UDP數據包協議,這是一種不連續的數據包,在包的傳輸過程中可能出現數據包的到達順序不一致或者丟失的情況。

3.UnixStreamServer:繼承自TCPServer,使用了Unix domain socket在非Unix平臺下無法工作。

4.UnixDatagramServer繼承自UDPServer,使用了Unix domain socket在非Unix平臺下無法工作。

      所有的Server類都擁有相同的屬性和方法,不管它們使用的是何種協議!

  關於Unix domains socket可以參考下面兩本書。

W. Richard Stevens 《UNIX Network Programming》

Ralph Daviss 《Win32 Network Programming》


這四個類使用"同步"來處理請求。只有處理完所有請求後,纔可以開始處理新的請求!不適合使用在處理單個請求需要花費大量時間的場合。因爲需要花費大量的計算時間,或者這因爲它會返回大量的數據導致客戶端處理速度變得很慢。解決方法是創建單獨的進程或者線程處理每一個請求。在類內部的ForkingMixIn和ThreadingMixIn 組合可以支持"異步"的操作。


建立一個服務端需要以下幾步:

1.首先,需要建立一個從BaseRequestHandler類繼承的子類,並且重寫itshandle()方法,這個方法會處理每個進來請求。

2.再者,實例化一個server類的對象,傳遞給其服務器地址和在第一步繼承自BaseRequestHandler類的名稱,以便server對象按照重寫的方法來處理請求。

3.最後,調用server對象的handle_request()或者serve_forever()方法來處理單個或多個請求

爲了使用線程鏈而繼承自ThreadingMixIn的時候,必須明確的聲明線程在遇到突發情況下如何關閉。ThreadingMixIn類定義了一個屬性"daemon_threads",它表明了server需要等待線程的結束,如果你需要讓線程獨立於進程,你就應該改明確的設置這個屬性,它的默認值是False,意味着Python程序進程不會退出,除非所有使用ThreadingMixIn創建的線程已經都結束了。


服務器創建筆記:


以下是五個類的繼承關係圖,其中四個代表同步服務器中的類型:

+------------+
| BaseServer |
+------------+
      |
      v
+-----------+        +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+        +------------------+
      |
      v
+-----------+        +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+        +--------------------+
注意UnixDatagramServer繼承自UDPServer,而不是UnixStreamServer。一個IP地址和一個Unix流服務器的唯一區別就是他們的地址描述族不一樣。在Unix服務器類中的話其實它們是近乎重複的。

分佈(進程處理)和線程版本的每個類型的服務器可以使用ForkingMixIn和ThreadingMixIn混合類創建。例如一個線程UDP服務器創建類如下:

class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
類的混合是第一位的,因爲它會重寫定義在UDPServer類內的方法,而設置屬性的同時會改變server的基本運行機制。

爲了實現一個服務,首先要從BaseRequestHandler派生一個類並且重寫itshandle()方法。然後將其和一個server類綁定就可以運行其服務。這個類必須與datagram或者stream services不同,可以通過隱藏StreamRequestHandle 或者DatagramRequestHandle子類來實現。

當然,很多情況你必須自己思考,比如,一個服務進程中的內存狀態會隨着每個不同去請求而改變,你就沒必要使用分佈式服務。因爲子進程的變動不會傳遞到到主進程的初始狀態並且傳遞給其他的主進程。在這種情況下,你可以使用線程式服務,但是你可能需要使用線程鎖來保證共享數據的完整性。

另一方面,如果你建立了一個HTTP服務器,所有的數據都是存儲在外部,(比如,在文件系統上)如果使用同步server類的話,基本會讓服務器表現爲聾子一樣,什麼請求也聽不到。如果有一個客戶端緩慢接收其對服務器的請求的時候,其他客戶端的請求要等待相當長的一段時間。很明顯這裏使用線程server或者分佈server是很合適的。

在某些情況下,處理一個合適的同步請求的一部分,但是其請求處理完成是在子分佈服務器上完成的。這可以通過使用一個同步服務器請求處理類handle()方法做一個明確的分佈實現

另外一種情況:在一個不支持線程和分佈式處理的環境下處理多個並行請求的情況下(或是其成本太高 或者其場合不合適),保持一個顯式的部分請求完成表,並且使用select()去判斷哪個請求應該在下一步被執行(是否處理一個新傳入的請求)。這是一種相當重要的方法可以保持流示服務器上的客戶端可以連接很長一段時間(如果線程或者子進程不可使用)。可以參考asyncore用其他的方法管理他們


Server 對象:


class socketserver.BaseServer
#這是一個集合所有Server對象的超類,它只定義了接口方法,具體實現在子類內完成!

	BaseServer.fileno() 	"""當服務器已經處於listening狀態下此方法返回一個描述socket的整數描述符號。
		這個方法通常傳遞給select.select(),以便允許在同一個進程中多個server對socket的監聽。"""

	BaseServer.handle_request() 	"""在處理單個請求的情況下
		此方法會按順序調用一下方法:get_request(), verify_request(), process_request()。
		如果用戶提供的處理請求類中的handle()方法印發了一個異常,
		那麼Server對象的handle_error()方法將會被調用.
		如果在self.timeout設置的時間內沒有請求到達將會調用handle_timeout()方法並且handle_request()會返回.""" 

	BaseServer.serve_forever(poll_interval=0.5) 	"""處理請求除非遇到明確的 shutdown() 請求,。
		忽略self.timeout設定的時間並且間隔poll_interval秒檢測是否需要關閉連接. 
		通常也會調用service_actions().用來實現子類方法或者是混合類,從而提供特殊的服務行爲
		比如,  ForkingMixIn類使用service_actions()去清除殭屍子進程 。
		在3.3版本中的改變:增加service_actions()調用serve_forever()"""

	BaseServer.service_actions()    """這個方法在serve_forever()循環中被調用. 
		這個方法可以被子類重寫和混合類重寫用於執行特別的服務.
		比如清理動作。此方法只在3.3中擁有 """

	BaseServer.shutdown()	#告訴serve_forever()循環停止並且等待其完成 

	BaseServer.address_family  #Server套接字使用的地址協議族,通常來說他們是socket.AF_INET 和 socket.AF_UNIX

	BaseServer.RequestHandlerClass  #由使用者提供的請求處理類,這個請求處理類的對象將會處理每個請求。

	BaseServer.server_address  """這個地址是表示server在監聽哪個地址,地址格式和地址的協議族相關聯;
		想要獲得更多其信息可以查看 socket模塊文檔,對於IP協議來說,
		這個元組包含一個字符串地址 和一個整數的端口號,比如('127.0.0.1',80)"""

	BaseServer.socket  # 監聽來自客戶端請求的socket對象

#---------------所有 server 類支持以下類變量-----------------------:

	BaseServer.allow_reuse_address #是否允許server重新使用地址,默認爲Flase,可以在子類改變其默認策略!

	BaseServer.request_queue_size """請求隊列的大小,如果單個請求處理了很長時間,其他的所有請求會放置在隊列中,
		如果隊列已經滿了的話,新的客戶端請求會得到一個“Connection denied”錯誤,
		默認值爲5,此大小可在子類中修改!"""

	BaseServer.socket_type   #server使用的socket類型通常是:socket.SOCK_STREAM和socket.SOCK_DGRAM。

	BaseServer.timeout #超時間隔,單位爲秒,如果在超時間隔內handle_request()沒有收到新的請求,handle_timeout()方法將會被調用!

#--------------以下server方法是可以被子類重寫的,比如TCPServer;對於外部的使用者來說 這些server對象的方法都是沒有用的!--------------------

	BaseServer.finish_request() #實際處理請求通過實例化RequestHandlerClass並且調用handle()方法

	BaseServer.get_request() #必須在socket得到請求後返回一個二元組包含:客戶端連接的新socket對象,和客戶端的地址。

	BaseServer.handle_error(request, client_address) """此方法將在RequestHandlerClass的handle()方法引發了一個異常後調用,默認動作是輸出錯誤異常,
		並且繼續處理下一步的請求!"""

	BaseServer.handle_timeout()"""在一段時間內沒有新的請求的話將會執行此方法,在分佈式進程server中它會收集所有已經退出的子進程狀態。在線程server中,它什麼也不做!"""
	
	BaseServer.process_request(request, client_address) """如果需要的話會執行finsh_request()方法,來實例化RequestHandlerClass,
		這個方法可以建立一個新的處理線程去處理請求。可以使用ForkingMixIn 和 ThreadingMixIn 類來處理此請求。"""

	BaseServer.server_activate()#在server的構造函數內調用此方法,默認的行爲是監聽server的socket。可以被重寫!

	BaseServer.server_bind() #在server的構造函數內調用此方法,將socket和網絡地址綁定。可以被重寫!
	
	BaseServer.verify_request(request, client_address) """返回值爲Boolean值,如果爲True,這個請求將會被處理,如果爲False這個請求將會被拒絕。
		重寫這個方法可以實現“訪問控制”默認的返回值爲True"""

RequestHandler 對象!

#-------------此處理請求類必須定義新的handle()方法,並且可以重寫以下方法。一個新的對象實例會爲每個請求進行處理!---------------

	RequestHandler.finish() """此方法將在handle()方法被調用後用來執行必要的清理動作,默認實現是什麼也不做,如果setup()方法引發了一個異常,此方法將不會被調用。"""

	RequestHandler.handle()  """此方法必須爲所有請求完成其要求的服務。默認的實現是什麼也不做。允許其中有多個server對象。
        	萬一需要訪問每個server的信息,那麼其請求可以通過self.request獲得;客戶端的地址self.client_address;server的對象self.server。
 		self.request的類型在datagram(數據包)和streamservices(流式服務)下是不同的。
		在StreamServices下self.request類型是一個socket對象,在datagram下self.request是以字符串和socket的組合。
		然而這個方法可以通過重寫其子類StreamRequestHandler 或者 DatagramRequestHandler 類的setup() 和 finish()方法來達到隱藏的目的。
		並且通過self.rfile和self.wfile屬性的讀寫來達到獲取客戶端的請求數據或者返回給客戶端數據。"""

	RequestHandler.setup()  #此方法在handle()方法前調用,用來做一些初始化的工作。默認的實現是什麼也不做!

示例:TCPServer

服務端:

import socketserver


class 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()
另一種特別的方法,利用流(類似文件對象的方法來簡化通訊)

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())
兩種方法不同之處在於:第二種方法將多次調用recv()方法直到遇上一個換行符號。而第一種是隻調用一次recv()方法,其返回的數據是在客戶端執行sendall()方法中的所有數據
客戶端:

import socket
import sys


HOST, PORT = "localhost", 9999
data = "hello Python "


# 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))

首先運行服務端,然後運行客戶端(在命令行模式下)

將會看到類似的結果

服務端:

127.0.0.1 wrote:
b'hello Python'

客戶端:

Sent:     hello Python 
Received: HELLO PYTHON

示例: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
    server = socketserver.UDPServer((HOST, PORT), MyUDPHandler)
    server.serve_forever()

客戶端:

import socket
import sys

HOST, PORT = "localhost", 9999
data = "hello Python"

# 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):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((ip, port))
    try:
        sock.sendall(bytes(message, 'ascii'))
        response = str(sock.recv(1024), 'ascii')
        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()

結果應該類似這樣:

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
原文地址:http://docs.python.org/3/library/socketserver.html


















發佈了21 篇原創文章 · 獲贊 3 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章