[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
先來個開頭預設, 基本必要的, 之前文章有提過了
上面等會後續會解釋, 先往下看, 這邊宣告一個class並且繼承threading.Thread(這邊我叫他Tserver)
其實剩下的事情就很簡單了, 除了要實作叫thread要做的事情, 還有就是記得要先初始化一些參數, , 所以有兩個function要實作, 1是__init__ (也就是初始化, 文件規定一定要實作的其中之一), 2是run(讓每次呼叫thread, thread都要執行的東西進入點, 這不實作thread就不知道要做甚麼啦)
首先是初始化, 我在這邊, 只是要紀錄client端連過來的address, 以便我後續使用, 注意, threading.Thread.__init__(self)這是一定要加入的thread初始化條件,
再來是要叫thread做的事情, function一定要稱呼為run, 算是thread進入點, 之後要做哪些事情當然就看用途囉, 在進入這邊之前, 我這邊做一個很笨的範例, client端可以輸入jack:dp 就會將1元存入jack戶頭, 並且印出jack's money: 1, 如果輸入jack:wd 就會將jack戶頭取出1元, 沒錢的時候就會印出jack has no money!!
首先我用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去做後續的事情
這邊完整個程式碼跟簡單的執行結果
首先啟動Server端之後, 可以開兩個Client測試
1. Client A
上面就是一個很簡單繼承threading的一個範例, 之後還會有一篇講最後最方便的Python SocketServer
這裡開始講述一些比較複雜的運用
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: 22. 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
Check this basic ..Python socket
回覆刪除programming