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 種類:

  1. TCPServer
  2. UDPServer
  3. UnixStreamServer
  4. UnixDatagramServer

後兩者無法在非 Unix 的平台上運作,若無特殊需求,只要使用 TCPServer, UDPServer 即可。

而這 4 種 Server 都是 synchronously,簡單而言,就是每次只能處理一個要求(request),處理完一個要求之後,下一個要求才會被執行。當你所送出的每一個要求都需要很長的執行時間時,選擇使用 synchronously 是十分不適合的。如果要能夠同時處理數個要求,就需要使用 asynchronous 的方式,只要利用 socketserver 模組中定義的 ThreadingMixIn 類別或 ForkingMixIn 就可以讓 socketserver 也支援 asynchronous

Python socketserver Framework 基本 3 步驟如下(不含 asynchronous ):

  1. 撰寫一個子類別繼承 socketserver 模組的 BaseRequestHandler 類別,並覆寫父類別的 handle() 方法,這個類別將會專門被用來處理每一個 client 送來的要求,在此可以暫時稱為 Handler

  2. 實例化上述所提及的 4 種 Sever 類型的其中之一,並傳入參數 IP 位址,通訊埠(port)以及第 1 步驟所撰寫的 Handler 名稱,例如:

    server = SocketServer.TCPServer((127.0.0.1, 9999), MyTCPHandler)

  3. 呼叫第 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() 是不進行任何動作,因此可以視需求決定是否需要覆寫 RequestHandlersetup() 方法。

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 模組的概要說明,如果有興趣的話,可以到官網閱讀 官方文件

參考資料:

http://docs.python.org/3/library/socketserver.html

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

追蹤新知

看完這篇文章了嗎?還意猶未盡的話,追蹤粉絲專頁吧!

我們每天至少分享 1 篇文章/新聞或者實用的軟體/工具,讓你輕鬆增廣見聞提升專業能力!如果你喜歡我們的文章,或是想了解更多特定主題的教學,歡迎到我們的粉絲專頁按讚、留言讓我們知道。你的鼓勵,是我們的原力!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!