Python SocketServer 模組使用教學
Posted on Sep 13, 2016 in Python 程式設計 - 中階 by Amo Chen ‐ 4 min read
socketserver 是 Python 一個模組,主要用來簡化撰寫 Socket Server 的工作,因此在官網中也宣稱它是一個方便的 Socket Server Framework。
The SocketServer module simplifies the task of writing network servers.
使用前記得 import 。
import SocketServer
這個模組裡面類別化了 4 種基本的 Server 種類:
- TCPServer
- UDPServer
- UnixStreamServer
- UnixDatagramServer
後兩者無法在非 Unix 的平台上運作,若無特殊需求,只要使用 TCPServer, UDPServer 即可。
而這 4 種 Server 都是 synchronously
,簡單而言,就是每次只能處理一個要求(request),處理完一個要求之後,下一個要求才會被執行。當你所送出的每一個要求都需要很長的執行時間時,選擇使用 synchronously
是十分不適合的。如果要能夠同時處理數個要求,就需要使用 asynchronous
的方式,只要利用 socketserver 模組中定義的 ThreadingMixIn
類別或 ForkingMixIn
就可以讓 socketserver 也支援 asynchronous
。
Python socketserver Framework 基本 3 步驟如下(不含 asynchronous
):
撰寫一個子類別繼承 socketserver 模組的
BaseRequestHandler
類別,並覆寫父類別的handle()
方法,這個類別將會專門被用來處理每一個 client 送來的要求,在此可以暫時稱為Handler
實例化上述所提及的 4 種 Sever 類型的其中之一,並傳入參數 IP 位址,通訊埠(port)以及第 1 步驟所撰寫的
Handler
名稱,例如:server = SocketServer.TCPServer((127.0.0.1, 9999), MyTCPHandler)
呼叫第 2 步的實例(instance)的
serve_forever()
方法
接著,第一個範例示範一個類似終端機畫面,並且有 3 個指令 USER, PASS, EXIT 能夠使用的的 TCPServer 。
# -*- coding: utf-8 -*-
import re
import SocketServer
class MyTCPHandler(SocketServer.BaseRequestHandler):
def setup(self):
self.login_string = 'Hello guest!\r\n'
self.begin = True
self.exit = False
def handle(self):
while not self.exit:
if self.begin == True:
self.request.sendall(self.login_string)
self.begin = False
else:
self.request.sendall('$ ')
self.data = self.request.recv(1024).strip()
if self.data.startswith('USER '):
self.set_username(self.data)
elif self.data.startswith('PASS '):
self.set_password(self.data)
elif self.data.startswith('EXIT'):
self.request.sendall('Good bye!\r\n')
self.exit = True
else:
self.request.sendall('Available commands: USER, PASS, EXIT\r\n')
def set_username(self, username):
cols = re.split('\s', username)
print('USER is', cols[1])
def set_password(self, password):
cols = re.split('\s', password)
print('PASS is', cols[1])
if __name__ == '__main__':
binding = ('localhost', 9999)
server = SocketServer.TCPServer(binding, MyTCPHandler)
server.serve_forever()
上述是一個極其簡易的 TCPServer 範例,在啟動之後,本機的客戶端可使用指令 telnet localhost 9999
進行連線測試。
以下,讓我們一行一行說明範例 1 。
首先分別說明 MyTCPHandler 的 2 個主要方法 setup()
, handle()
,這 2 個方法是 RequestHandler
中所定義的 3 個方法之 2 ( 3 個方法分別是 setup()
, handle()
, finish()
,詳見 RequestHandler Objects 。
setup()
會在 handle()
執行前被呼叫,這個方法可以被用來初始化處理要求前所需要的變數,例如在本範例中的 setup()
方法中,就初始化了 handle()
所需要用到的 login_string
, begin
, exit
3 個變數(或稱屬性 attribute),不過預設的 setup()
是不進行任何動作,因此可以視需求決定是否需要覆寫 RequestHandler
的 setup()
方法。
handle()
則是用來處理要求的方法,是唯一必須被覆寫的方法。當客戶端(client side)連線時所送出的每一個要求,都會交由 handle()
處理。在範例中,我們用一個 while
迴圈,確保客戶端會保持連線狀態,並且能夠輸入一些指令與伺服端(server side)進行互動。此外,每一個要求都可以使用 self.request
這個屬性來獲得,因此可以看到範例使用 self.request.sendall()
回傳訊息給客戶端,並且使用 self.request.recv(1024)
取得客戶端傳送至伺服端的訊息。
p.s. 可使用self.client_address
取得客戶端的IP位址
p.s. 可使用self.server
獲得Server實例,進行伺服物件的操作,例如設定timeout
屬性等,詳見Server Objects
另外,沒有提到的是 finish()
方法,這個方法在 handle()
執行結束之後,會自動被呼叫(預設的 finish()
與 setup()
相同,都不會進行任何動作)。如果伺服端需要在處理完要求之後需要進行其他善後的動作,可以選擇撰寫在 finish()
方法中。
最後,範例中設定了 binding
變數將 IP 位址及通訊埠分別設定為 localhost 及 9999 後,實例化了 SocketServer.TCPServer
後,呼叫了 serve_forever()
等待客戶端的要求。
接下來第2個範例是支援 asynchronous
的 SocketServer 。
讓 socketserver 能夠支援 asynchronous
的主要做法,為除了範例 1 介紹的 RequestHandler 之外,須額外建立一類別同時繼承 SocketServer.ThreadingMixIn
及一開始提及的 4 種 SocketServer 之一即可(也不需覆寫任何父類別的方法),例如:
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
接著再使用上述建立的類別實例化伺服。 Python 官網提供了一個很好的範例可以參考,範例如下:
import socket
import threading
import SocketServer
class 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):
pass
def 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__":
binding = ("localhost", 9999)
server = ThreadedTCPServer(binding, 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()
在上述的範例中,可以清楚地看到多執行緒的 socketserver 是利用 threading.Thread(target=server.serve_forever)
來啟動的,接著為了測試執行緒的運作,範例模擬了 3 個客戶端連上伺服端的情況(即連續呼叫 3 次 client 函式)。
以上就是Python socketserver 模組的概要說明,如果有興趣的話,可以到官網閱讀 官方文件 。
參考資料: