在上一章中咱們遺留了下面幾個問題:linux
1.異步通訊,也是在不斷的輪詢排隊處理中,若是採用服務器端多線程處理呢?算法
2.多線程與異步操做的異同編程
3.若是A - 服務器 -B該如何實現?(包含如下內容:windows
a.半雙工通訊服務器
b.全雙工通訊多線程
c.多用戶全雙工通訊併發
d.使用多線程實現多用戶全雙工通訊異步
e.多用戶、多房間、全雙工通訊socket
)tcp
針對上面的幾個問題:我以爲是頗有必要好好探索一下的。那麼首先從多線程與異步操做的異同:
一.多線程與異步操做的異同
1.進程:
每一個進程都擁有本身的地址空間、內存、數據棧以及其它用於跟蹤執行的輔助數據。須要採用ipc的方式共享信息。
2.線程:
輕量級進程,在同一個進程下執行的,共享相同的上下文。能夠將它們認爲是在一個主進程或者「主線程」中並行運行的一些迷你進程,須要操做系統投入CPU資源來運行和調度。線程包含:開始、執行順序和結束三部分。有一個指令指針,用於紀錄當前運行的上下文,當其餘線程運行時,它能夠被搶佔(中斷)和臨時掛起(睡眠),這種作法叫作讓步。最重要的是一個進程中的各個線程與主線程共享同一片數據空間,所以相比於獨立的進程而言,線程間的信息共享和通訊更加容易。這種共享風險就是,若是多個線程訪問同一片數據,因爲訪問的順序不一樣,結果可能不一致。這種狀況一般稱爲競態條件(race condition)。有些線程可能還有阻塞狀態,因此必須處理來避免cpu的時間過多的分配到這些貪婪線程。
3.多線程:
以前有看到過知乎上的一個比喻,以爲特別好,在這裏引用下:
單進程單線程:一我的在一個桌子上吃飯
單進程多線程:一羣人在一個桌子上吃飯
多進程單線程:多我的在各自的桌子上吃飯
多線程容易引起的問題就是,一羣人在一個桌子上吃飯的時候,若是同時有多我的要夾同一個菜,就容易一我的剛伸筷子,菜已經被別人夾走。因此必須一人夾一筷子。這就是資源衝突。
在windows上,建立進程會消耗會很大,因此鼓勵你們在windows平臺下進行單進程多線程操做。
在linux 上,建立進程的消耗很小,因此鼓勵你們在各自的桌子上吃飯,可是這樣就存在不一樣桌子互相講話很困難。因此須要你們學習處理進程間的通訊。
當在多核cpu上,每一個cpu運行一個進程,有各自的進程資源,因此在cpu核心上切換無需考慮上下文。可是若是每一個核心運行一個線程,每一個線程須要共享資源,因此必須將一個核心上的資源拷貝到另外一個核心才能繼續運行,因此會耗費大量的開銷。所以在多核服務器端編程,要習慣多進程而非多線程。
4.異步操做
所謂異步,打個比方,就是若是一大羣人都想你聽他說話,那麼你就給他們每人一分鐘的時間說,你們輪流說,沒說完的待會兒輪到時再繼續說。也就是一個時間片的方法。異步處理基於兩個前提。第一個前提是支持併發,固然這是基本前提。這裏的併發並不必定要是並行,也就是說容許邏輯上異步,實現上串行;第二個前提是 支持回調(callback),由於併發的、異步的處理不會阻塞當前正在被執行的流程,因此「任務完成後」要執行的步驟應該寫在回調中,絕大多數回調是經過函數來實現。
異步操做的優缺點:
由於異步操做無須額外的線程負擔,而且使用回調的方式進行處理,在設計良好的狀況下,處理函數能夠沒必要使用共享變量(即便沒法徹底不用,最起碼能夠減小共享變量的數量),減小了死鎖的可能。固然異步操做也並不是完美無暇。編寫異步操做的複雜程度較高,程序主要使用回調方式進行處理,與普通人的思惟方式有些初入,並且難以調試。
多線程的優缺點:
多線程的優勢很明顯,線程中的處理程序依然是順序執行,符合普通人的思惟習慣,因此編程簡單。可是多線程的缺點也一樣明顯,線程的使用(濫用)會給系統帶來上下文切換的額外負擔。而且線程間的共享變量可能形成死鎖的出現。
針對他們是適用範圍,若是針對大數據量的算法及圖形處理,建議使用多線程進行操做。
2、實戰,下面咱們來實現下如下幾個功能:
1.半雙工通訊
半雙工通訊,就是隻有一我的能打字,而另外一個參與者在獲得輸入消息提示以前必須等待消息。而且,一旦發送者發送一了一條消息,在它可以再次發送消息以前,必須等待對方的回覆。再上一章中,其實咱們已經實現了,修改下部分代碼,來更加完善:
服務器端:
# coding:utf-8 from socket import * from time import ctime HOST = '' PORT = 21567 BUFSIZE = 1024 ADDR = (HOST, PORT) tcpSerSock = socket(AF_INET, SOCK_STREAM) # 分配TCP服務器套接字 tcpSerSock.bind(ADDR) # 將套接字綁定到服務器地址 tcpSerSock.listen(5) # 開啓TCP監聽器,接受鏈接數6個,超過鏈接數拒絕鏈接!只與第一個鏈接客戶端通訊. try: while True: print 'waiting for connection...' # 無限循環中,等待客戶端的鏈接 tcpCliSock, addr = tcpSerSock.accept() print '...connected from:', addr try: while True: data = tcpCliSock.recv(BUFSIZE) # 接收客戶端數據 if not data: # 若是接收的消息是空白數據,這覺得着客戶端已經退出,跳出循環,關閉當前的客戶端鏈接,繼續等待另外一個客戶端鏈接 break else: print ' Client To Ser [%s] %s' % (ctime(), data) while True: serRecData = raw_input('> ') if not serRecData: continue else: tcpCliSock.send(serRecData) # 接受到客戶端消息不爲空,服務端輸入信息返回給客戶端。 print 'waiting for Client...' break finally: tcpCliSock.close() finally: tcpSerSock.close() # 永遠不會執行,只是提醒你們,能夠用這種方式,關閉服務器套接字,退出服務
客戶端:
# coding:utf-8 from socket import * from time import ctime HOST = 'localhost' PORT = 21567 BUFSIZE = 1024 ADDR = (HOST, PORT) tcpCliSock = socket(AF_INET, SOCK_STREAM) # 分配TCP套接字 tcpCliSock.connect(ADDR) # 經過主機信息鏈接服務器 try: while True: data = raw_input('> ') # 輸入信息 if not data: # 若是輸入爲空,則跳出循環體,關閉客戶端鏈接 break tcpCliSock.send(data) # 輸入不爲空,發送信息。 print 'waiting for Ser...' data = tcpCliSock.recv(BUFSIZE) if not data: break print ' Ser To Client [%s] %s' % (ctime(), data) finally: tcpCliSock.close()
服務器端顯示:
客戶端顯示:
2.全雙工通訊
3.多用戶全雙工通訊
4.使用多線程實現多用戶全雙工通訊
5.多用戶、多房間、全雙工通訊