Python之Socket通信與線程
想寫一個能讓兩個不同的主機間能進行通訊的軟體??想仿造一個類似app store一樣的軟體???
如果你打算開發和網路有關的程序, 比如聊天軟體,那麼你絕對繞不過網路編程這一關,而socket通信就是網路編程的一個重點。
1.Socket的定義
網路上的兩個程序通過一個雙向的通信連接實現數據的交換,這個連接的一端稱為一個socket。(引自百度百科)Emmmm... 想了解理論部分的同學我建議你們到其他文章中查閱相關內容,咱們重點說說實戰方面吧
2一個簡單的Socket通信例子
# -*-coding:utf-8 -*-# Server端# Server.pyimport socketHOST = 127.0.0.1PORT = 33333ADDR = (HOST,PORT)# AF_INET 表示連接使用ipv4地址族 SOCK_STREAM表示用流式套接字tcpSerSock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)tcpSerSock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)# 將套接字綁定該地址tcpSerSock.bind(ADDR)# 參數1表示阻塞模式 0表示非阻塞模式 默認為阻塞模式tcpSerSock.setblocking(1)# 開始監聽TCP傳入連接。參數指定在拒絕連接之前,操作系統可以掛起的最大連接數量。tcpSerSock.listen(5)print "Waiting connect..."# tcpCliSock 是該鏈接的套接字,addr表示對方的地址tcpCliSock, addr = tcpSerSock.accept()# 設置超時時間tcpCliSock.settimeout(20.0)print ...connected from, addr# recv(param)用於接收對方發送的數據 param為緩衝區大小data = tcpCliSock.recv(1024)print datatcpCliSock.sendall("here is server")# 關閉套接字tcpCliSock.close()
# -*- coding: utf-8 -*-# client端# Client.pyimport socketaddress = (127.0.0.1, 33333)s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)s.connect(address)s.sendall("here is client")data = s.recv(1024)print datas.close()
運行結果
listen(x)方法用於監聽TCP傳入連接。參數指定在拒絕連接之前,操作系統可以掛起的最大連接數量。假設a主機connect伺服器,伺服器會與a建立連接進行socket通信。如果在a與伺服器斷開連接前b主機也connect伺服器,那麼伺服器會掛起b連接,直到a與伺服器斷開連接後,伺服器才會與b進行連接。而listen方法的參數x正是表示伺服器最多可以掛起多少個連接數的。
想了解更多socket模塊的方法詳細使用方法及參數的朋友們可以看看這篇文章(非知乎連接,用瀏覽器打開觀看更佳)
但是實際應用中經常需要伺服器同時與多個客戶端進行連接呀,那應該怎麼辦呢????解決方案可以開啟多線程或者是多進程。多進程的消耗是比多線程大的,但對於當今伺服器(基於Linux)的配置來說,這些消耗基本可以忽略不計。
我們今天先討論多線程伺服器。
3.1多線程
多線程(multithreading),是指從軟體或者硬體上實現多個線程並發執行的技術。具有多線程能力的計算機因有硬體支持而能夠在同一時間執行多於一個線程,進而提升整體處理性能。
通過一個簡單例子了解多線程
# -*- coding:utf-8 -*-# simpleThread_1.pyimport threadingimport timedef process(): n = 0 while(n<5): print str(n)+": the threading %s is running"%threading.current_thread().name n = n + 1 time.sleep(1)# 創建新線程 target為線程要執行的方法 name為進程的名字,name參數可以省略,其實名字基本無多大用處t1 = threading.Thread(target=process,name="One")t2 = threading.Thread(target=process,name="Two")t1.start()t2.start()
那麼線程t1和t2將能同時運行
如果在t1.start()和t2.start()中間加多個:t1.join() 那麼線程t2將在t1運行結束後才會開始運行
3.2多線程伺服器
#-*- coding:utf-8 -*-# 多線程伺服器 # threads_Server.pyimport socketimport selectimport threadingimport timedef process(tcpCliSock,addr): print "connect from "+str(addr) pattern_data = tcpCliSock.recv(1024) print data tcpCliSock.sendall("here is server") def run(): server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(("127.0.0.1",33333)) server.listen(5) while True: r,w,e = select.select([server,],[],[],1) # enumerate()分別列舉出list r中的序號和內容 for i,server in enumerate(r): conn,addr = server.accept() t = threading.Thread(target=process,args=(conn,addr)) t.start()if __name__=="__main__": run()
當然,其實多線程伺服器更普遍的應用場景在於長連接。
那麼那麼那麼,我們又遇到了一個問題。客戶端可能會忽然斷開連接,然而伺服器卻沒有發現,那伺服器該多傷心呀,被劈腿了還不知道[逃]。多線程伺服器面對這麼多個客戶端的連接,那麼它怎麼知道客戶端是否還與伺服器保持連接呢?於是我們又推出了一個神器:心跳包機制
心跳包可以定時向對方發送一個數據包,通過發送情況判斷該TCP的連接情況。
關於心跳包機制有很多種設計方法,比如:
- client定時向server發送心跳包,server收到心跳包後向client發送個包給予回應,這樣雙方都能知道對方是否在線。
- server定時向client發送心跳包,如果發送失敗則表示client已斷開連接,否則表示client還在連接中。這樣就屬於server的單方面監測了
設計方案不止以上兩種,具體情況具體分析!!!記住記住!!
那麼我們接下來就來看看加上了心跳包的多線程伺服器長啥樣~
#-*- coding:utf-8 -*-# 多線程伺服器(心跳包版) # hreatBeat_Server.pyimport socketimport selectimport threadingimport time# 心跳包線程 def hreatBeat(conn): sum = 0 # 無回應次數 while True: time.sleep(10) if sum<3: try: conn.sendall("hreatBeat") sum=0 except socket.error: sum = sum + 1 continue else: conn.close() breakdef process(tcpCliSock,addr): print "connect from "+str(addr) pattern_data = tcpCliSock.recv(1024) print data tcpCliSock.sendall("here is server") # 創建心跳包線程 # 須記住,創建新線程時 args參數如果只有一個的話一定要加個逗號!! thr = threading.Thread(target=hreatBeat, args=(tcpCliSock,)) thr.start() # thr.is_alive() 用於監測進程thr是否還存在(即client是否還在連接中) while thr.is_alive(): print "do everything you like here" def run(): server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(("127.0.0.1",33333)) server.listen(5) while True: r,w,e = select.select([server,],[],[],1) # enumerate()分別列舉出list r中的序號和內容 for i,server in enumerate(r): conn,addr = server.accept() t = threading.Thread(target=process,args=(conn,addr)) t.start()if __name__=="__main__": run()
4.總結
Socket通信入門很簡單,但深入可不容易。想進步就多學習,多練習!相信以上的知識,足夠你搭建出一個成功伺服器的雛形了。作為程序猿,需求擺在第一位,一切都要結合具體需求進行分析。
那麼...歡迎關注我的公眾號*#@% ? 不存在的
愛你們
推薦閱讀:
※什麼?Python3.X不能輸出中文?原來是編輯器geany的鍋?!
※百家爭鳴,誰是王者?
※C語言基礎:函數參數與返回值