[Python] Threading Socket Server

上一篇介紹了怎麼簡單的建立一個daemon server

這裡開始講述一些比較複雜的運用

thread或許很好用, 但是python也提供了一個threading module來幫助大家更方便使用thread

python threading: http://docs.python.org/2/library/threading.html

因為要做daemon server, 我們必須為每一個thread都建立一套機制流程, 而可能會有怕race condition, 或者是會有不同的client有不同的需求, 需要特別寫很多function來去加強功能需求, threading module可以很容易的讓你達到這件事情


threading module本身使用方法比較特別, 因為他本身已經有很多能力, 我們要好好用他的話最好是繼承threading class, 然後在幾個要實作的function上實作就好, 不熟悉的人可以想成, 我幫你把主要架構寫好了, 你只要把相對應的function填上即可

注意: 這是針對多thread的角度下使用, 並非平行CPU的角度下使用, 官方有建議如果讓多CPU最大效能, 請參考http://docs.python.org/2/library/multiprocessing.html#module-multiprocessing

先來個開頭預設, 基本必要的, 之前文章有提過了
import socket, threading

HOST = ''
PORT = 54321 

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #reuse tcp
sock.bind((HOST, PORT))
sock.listen(4)
people = {}
lock = threading.Lock()

上面等會後續會解釋, 先往下看, 這邊宣告一個class並且繼承threading.Thread(這邊我叫他Tserver)
class TServer(threading.Thread)

其實剩下的事情就很簡單了, 除了要實作叫thread要做的事情, 還有就是記得要先初始化一些參數, , 所以有兩個function要實作, 1是__init__ (也就是初始化, 文件規定一定要實作的其中之一), 2是run(讓每次呼叫thread, thread都要執行的東西進入點, 這不實作thread就不知道要做甚麼啦)

首先是初始化, 我在這邊, 只是要紀錄client端連過來的address, 以便我後續使用, 注意, threading.Thread.__init__(self)這是一定要加入的thread初始化條件,
def __init__(self, socket, adr):
    threading.Thread.__init__(self)
    self.socket = socket
    self.address= adr

再來是要叫thread做的事情, function一定要稱呼為run, 算是thread進入點, 之後要做哪些事情當然就看用途囉, 在進入這邊之前, 我這邊做一個很笨的範例, client端可以輸入jack:dp 就會將1元存入jack戶頭, 並且印出jack's money: 1, 如果輸入jack:wd 就會將jack戶頭取出1元, 沒錢的時候就會印出jack  has no money!!
    def run(self):
        global people
        print 'Client %s:%s connected.' % self.address
        while True:
            try:
                data = self.socket.recv(1024)
                if not data:
                    break
                cont = data.split(":")
                if len(cont) > 1:
                    lock.acquire()  #lock 區塊開始
                    if (people.has_key(cont[0]) == False):
                        people[cont[0]] = 0 
                    if "dp" in cont[1]:
                        people[cont[0]] += 1
                    elif "wd" in cont[1]:
                        if people[cont[0]] > 0:
                            people[cont[0]] -= 1
                    if people[cont[0]] == 0:
                        self.socket.send(cont[0] + " has no money!!\r\n")
                    else:
                        self.socket.send(cont[0] + "'s money: " + str(people[cont[0]]) + "\r\n")
                    lock.release() #lock 區塊結束
            except socket.timeout:
                break
        self.socket.close()
        print 'Client %s:%s disconnected.' % self.address
這邊有一個叫做lock.acquire()跟lock.release(), 因為在最一開頭初始的時候, 我有多設定兩個東西
people = {}
lock = threading.Lock()

首先我用people來紀錄每個人名跟多少錢, 預設就會是0元, 只要有輸入dp就會存, wd就會取, 那問題來了, 因為我用了一個global關鍵字, people變數會變成是每個thread都看得到, 所以沒處理好就會發生這個thread寫了一筆紀錄, 我另外一個thread在同時間又寫了另外一個紀錄, 然後造成衝突. 想成沒寫好的提款機, 兩個人用同張卡片在兩個不同的提款機同時提款, 戶頭只有一千元, 結果你跟他居然都可以領到一千而且都領到, 那就是很嚴重的同步問題

當然同步其實背後有很多可以探討的技術, 想要深讀的可以去看Operation System的同步處理問題

如果會發生資源衝突的時候, 可以用threading.lock來幫你解決

1. lock.acquire(), 這行背後其實做了很多事情, 但是簡單講就是你跟系統說我接下來的lock區塊只能單一thread運作, 然後問有沒有人正在用, 沒人的話他要進去, 也就是說, 如果當下有thread在運作, 其他thread就會被卡在這行

2. lock.release(), 擁有lock區塊權力的thread的可以做完事情之後執行這行, 他就會把權他就會把權力讓出來讓出來, 給下一個thread使用

那現在有趣的來了, 我有了class而且繼承了threading, 那接下來要怎麼用? 很簡單, 官方文件上寫明了, 只要你用你的class去呼叫start(), 他就會開始運行, 下面這意思是, 我先用我建立的socket接收client結果, 然後開一thread並且把client訊息傳進去, 讓這個thead去做後續的事情
if __name__ == "__main__":
    while True:
        (client, adr) = sock.accept()
        TServer(client, adr).start()

這邊完整個程式碼跟簡單的執行結果
import socket, threading

HOST = ''
PORT = 54321 

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #reuse tcp
sock.bind((HOST, PORT))
sock.listen(4)
people = {}
lock = threading.Lock()

class TServer(threading.Thread):
    def __init__(self, socket, adr):
        threading.Thread.__init__(self)
        self.socket = socket
        self.address= adr 

    def run(self):
        global people
        print 'Client %s:%s connected.' % self.address
        while True:
            try:
                data = self.socket.recv(1024)
                if not data:
                    break
                cont = data.split(":")
                if len(cont) > 1:
                    lock.acquire()
                    if (people.has_key(cont[0]) == False):
                        people[cont[0]] = 0 
                    if "dp" in cont[1]:
                        people[cont[0]] += 1
                    elif "wd" in cont[1]:
                        if people[cont[0]] > 0:
                            people[cont[0]] -= 1
                    if people[cont[0]] == 0:
                        self.socket.send(cont[0] + " has no money!!\r\n")
                    else:
                        self.socket.send(cont[0] + "'s money: " + str(people[cont[0]]) + "\r\n")
                    lock.release()
            except socket.timeout:
                break
        self.socket.close()
        print 'Client %s:%s disconnected.' % self.address

if __name__ == "__main__":
    while True:
        (client, adr) = sock.accept()
        TServer(client, adr).start()

首先啟動Server端之後, 可以開兩個Client測試

1. Client A
hhtu@hhtu:~$ telnet 127.0.0.1 54321
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
jack:dp
jack's money: 1
jack:dp
jack's money: 2
jack:dp
jack's money: 3
jack:wd
jack's money: 2
2. Client B
hhtu@hhtu:~$ telnet 127.0.0.1 54321
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
jack:wd
jack's money: 1
jack:wd
jack has no money!!

上面就是一個很簡單繼承threading的一個範例, 之後還會有一篇講最後最方便的Python SocketServer

留言

張貼留言

這個網誌中的熱門文章

[Linux] Linux下查詢硬體記憶體資訊 Memory Information

[Other] Chrome 重新整理所有開啟頁面

[Python] Simple Socket Server