一、單進程服務器
1. 完成一個簡單的TCP服務器
![](http://image109.360doc.com/DownloadImg/2018/05/2814/134228940_2_20180528020552300)
2. 總結
同一時刻只能為一個客戶進行服務,不能同時為多個客戶服務
類似于找一個“明星”簽字一樣,客戶需要耐心等待才可以獲取到服務
當服務器為一個客戶端服務時,而另外的客戶端發(fā)起了connect,只要服務器listen的隊列有空閑的位置,就會為這個新客戶端進行連接,并且客戶端可以發(fā)送數(shù)據(jù),但當服務器為這個新客戶端服務時,可能一次性把所有數(shù)據(jù)接收完畢
當recv接收數(shù)據(jù)時,返回值為空,即沒有返回數(shù)據(jù),那么意味著客戶端已經(jīng)調用了close關閉了;因此服務器通過判斷recv接收數(shù)據(jù)是否為空 來判斷客戶端是否已經(jīng)下線
二、多進程服務器
1. 多進程服務器
![](http://image109.360doc.com/DownloadImg/2018/05/2814/134228940_3_20180528020552410)
![](http://image109.360doc.com/DownloadImg/2018/05/2814/134228940_4_20180528020552566)
2. 總結
三、多線程服務器
![](http://image109.360doc.com/DownloadImg/2018/05/2814/134228940_5_20180528020552784)
![](http://image109.360doc.com/DownloadImg/2018/05/2814/134228940_6_20180528020552925)
四、單進程服務器-非堵塞模式
服務器
![](http://image109.360doc.com/DownloadImg/2018/05/2814/134228940_7_20180528020553113)
![](http://image109.360doc.com/DownloadImg/2018/05/2814/134228940_8_20180528020553253)
客戶端
![](http://image109.360doc.com/DownloadImg/2018/05/2814/134228940_9_20180528020553425)
五、select版-TCP服務器
1. select 原理
在多路復用的模型中,比較常用的有select模型和epoll模型。這兩個都是系統(tǒng)接口,由操作系統(tǒng)提供。當然,Python的select模塊進行了更高級的封裝。
網(wǎng)絡通信被Unix系統(tǒng)抽象為文件的讀寫,通常是一個設備,由設備驅動程序提供,驅動可以知道自身的數(shù)據(jù)是否可用。支持阻塞操作的設備驅動通常會實現(xiàn)一組自身的等待隊列,如讀/寫等待隊列用于支持上層(用戶層)所需的block或non-block操作。設備的文件的資源如果可用(可讀或者可寫)則會通知進程,反之則會讓進程睡眠,等到數(shù)據(jù)到來可用的時候,再喚醒進程。
這些設備的文件描述符被放在一個數(shù)組中,然后select調用的時候遍歷這個數(shù)組,如果對于的文件描述符可讀則會返回改文件描述符。當遍歷結束之后,如果仍然沒有一個可用設備文件描述符,select讓用戶進程則會睡眠,直到等待資源可用的時候在喚醒,遍歷之前那個監(jiān)視的數(shù)組。每次遍歷都是依次進行判斷的。
2. select 回顯服務器
使用python的select模塊很容易寫出下面一個echo(回顯)服務器:
![](http://image109.360doc.com/DownloadImg/2018/05/2814/134228940_10_20180528020553550)
![](http://image109.360doc.com/DownloadImg/2018/05/2814/134228940_11_20180528020553706)
在windows中,使用‘網(wǎng)絡調試助手’,進行連接服務器即可測試
![](http://pubimage.360doc.com/wz/default.gif)
另外一個服務器(包含writeList):
![](http://pubimage.360doc.com/wz/default.gif)
![](http://pubimage.360doc.com/wz/default.gif)
3. 總結
優(yōu)點
select目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優(yōu)點。
缺點
select的一個缺點在于單個進程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在Linux上一般為1024,可以通過修改宏定義甚至重新編譯內核的方式提升這一限制,但是這樣也會造成效率的降低。
一般來說這個數(shù)目和系統(tǒng)內存關系很大,具體數(shù)目可以cat /proc/sys/fs/file-max察看。32位機默認是1024個。64位機默認是2048.
對socket進行掃描時是依次掃描的,即采用輪詢的方法,效率較低。
當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間
六、epoll版-TCP服務器
1. epoll的優(yōu)點:
沒有最大并發(fā)連接的限制,能打開的FD(指的是文件描述符,通俗的理解就是套接字對應的數(shù)字編號)的上限遠大于1024
效率提升,不是輪詢的方式,不會隨著FD數(shù)目的增加效率下降。只有活躍可用的FD才會調用callback函數(shù);即epoll最大的優(yōu)點就在于它只管你“活躍”的連接,而跟連接總數(shù)無關,因此在實際的網(wǎng)絡環(huán)境中,epoll的效率就會遠遠高于select和poll。
2. epoll使用參考代碼
![](http://pubimage.360doc.com/wz/default.gif)
![](http://pubimage.360doc.com/wz/default.gif)
![](http://pubimage.360doc.com/wz/default.gif)
2. 說明
EPOLLIN (可讀)
EPOLLOUT (可寫)
EPOLLET (ET模式)
epoll對文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)。LT模式是默認模式,LT模式與ET模式的區(qū)別如下:
LT模式:當epoll檢測到描述符事件發(fā)生并將此事件通知應用程序,應用程序可以不立即處理該事件。下次調用epoll時,會再次響應應用程序并通知此事件。
ET模式:當epoll檢測到描述符事件發(fā)生并將此事件通知應用程序,應用程序必須立即處理該事件。如果不處理,下次調用epoll時,不會再次響應應用程序并通知此事件。
七、協(xié)程
協(xié)程,又稱微線程,纖程。英文名Coroutine。
協(xié)程是啥
首先我們得知道協(xié)程是啥?協(xié)程其實可以認為是比線程更小的執(zhí)行單元。 為啥說他是一個執(zhí)行單元,因為他自帶CPU上下文。這樣只要在合適的時機, 我們可以把一個協(xié)程 切換到另一個協(xié)程。 只要這個過程中保存或恢復 CPU上下文那么程序還是可以運行的。
通俗的理解:在一個線程中的某個函數(shù),可以在任何地方保存當前函數(shù)的一些臨時變量等信息,然后切換到另外一個函數(shù)中執(zhí)行,注意不是通過調用函數(shù)的方式做到的,并且切換的次數(shù)以及什么時候再切換到原來的函數(shù)都由開發(fā)者自己確定
協(xié)程和線程差異
那么這個過程看起來比線程差不多。其實不然, 線程切換從系統(tǒng)層面遠不止保存和恢復 CPU上下文這么簡單。 操作系統(tǒng)為了程序運行的高效性每個線程都有自己緩存Cache等等數(shù)據(jù),操作系統(tǒng)還會幫你做這些數(shù)據(jù)的恢復操作。 所以線程的切換非常耗性能。但是協(xié)程的切換只是單純的操作CPU的上下文,所以一秒鐘切換個上百萬次系統(tǒng)都抗的住。
協(xié)程的問題
但是協(xié)程有一個問題,就是系統(tǒng)并不感知,所以操作系統(tǒng)不會幫你做切換。 那么誰來幫你做切換?讓需要執(zhí)行的協(xié)程更多的獲得CPU時間才是問題的關鍵。
例子
目前的協(xié)程框架一般都是設計成 1:N 模式。所謂 1:N 就是一個線程作為一個容器里面放置多個協(xié)程。 那么誰來適時的切換這些協(xié)程?答案是有協(xié)程自己主動讓出CPU,也就是每個協(xié)程池里面有一個調度器, 這個調度器是被動調度的。意思就是他不會主動調度。而且當一個協(xié)程發(fā)現(xiàn)自己執(zhí)行不下去了(比如異步等待網(wǎng)絡的數(shù)據(jù)回來,但是當前還沒有數(shù)據(jù)到), 這個時候就可以由這個協(xié)程通知調度器,這個時候執(zhí)行到調度器的代碼,調度器根據(jù)事先設計好的調度算法找到當前最需要CPU的協(xié)程。 切換這個協(xié)程的CPU上下文把CPU的運行權交個這個協(xié)程,直到這個協(xié)程出現(xiàn)執(zhí)行不下去需要等等的情況,或者它調用主動讓出CPU的API之類,觸發(fā)下一次調度。
那么這個實現(xiàn)有沒有問題?
其實是有問題的,假設這個線程中有一個協(xié)程是CPU密集型的他沒有IO操作, 也就是自己不會主動觸發(fā)調度器調度的過程,那么就會出現(xiàn)其他協(xié)程得不到執(zhí)行的情況, 所以這種情況下需要程序員自己避免。這是一個問題,假設業(yè)務開發(fā)的人員并不懂這個原理的話就可能會出現(xiàn)問題。
協(xié)程的好處
在IO密集型的程序中由于IO操作遠遠慢于CPU的操作,所以往往需要CPU去等IO操作。 同步IO下系統(tǒng)需要切換線程,讓操作系統(tǒng)可以在IO過程中執(zhí)行其他的東西。 這樣雖然代碼是符合人類的思維習慣但是由于大量的線程切換帶來了大量的性能的浪費,尤其是IO密集型的程序。
所以人們發(fā)明了異步IO。就是當數(shù)據(jù)到達的時候觸發(fā)我的回調。來減少線程切換帶來性能損失。 但是這樣的壞處也是很大的,主要的壞處就是操作被 “分片” 了,代碼寫的不是 “一氣呵成” 這種。 而是每次來段數(shù)據(jù)就要判斷 數(shù)據(jù)夠不夠處理哇,夠處理就處理吧,不夠處理就在等等吧。這樣代碼的可讀性很低,其實也不符合人類的習慣。
但是協(xié)程可以很好解決這個問題。比如 把一個IO操作 寫成一個協(xié)程。當觸發(fā)IO操作的時候就自動讓出CPU給其他協(xié)程。要知道協(xié)程的切換很輕的。 協(xié)程通過這種對異步IO的封裝 既保留了性能也保證了代碼的容易編寫和可讀性。在高IO密集型的程序下很好。但是高CPU密集型的程序下沒啥好處。
協(xié)程一個簡單實現(xiàn)
![](http://pubimage.360doc.com/wz/default.gif)
運行結果:
![](http://pubimage.360doc.com/wz/default.gif)
八、協(xié)程-greenlet版
為了更好使用協(xié)程來完成多任務,python中的greenlet模塊對其封裝,從而使得切換任務變的更加簡單
安裝方式
使用如下命令安裝greenlet模塊:
![](http://pubimage.360doc.com/wz/default.gif)
![](http://pubimage.360doc.com/wz/default.gif)
運行效果
![](http://pubimage.360doc.com/wz/default.gif)
九、gevent
greenlet已經(jīng)實現(xiàn)了協(xié)程,但是這個還的人工切換,是不是覺得太麻煩了,不要捉急,python還有一個比greenlet更強大的并且能夠自動切換任務的模塊gevent
其原理是當一個greenlet遇到IO(指的是input output 輸入輸出,比如網(wǎng)絡、文件操作等)操作時,比如訪問網(wǎng)絡,就自動切換到其他的greenlet,等到IO操作完成,再在適當?shù)臅r候切換回來繼續(xù)執(zhí)行。
由于IO操作非常耗時,經(jīng)常使程序處于等待狀態(tài),有了gevent為我們自動切換協(xié)程,就保證總有greenlet在運行,而不是等待IO
1. gevent的使用
![](http://pubimage.360doc.com/wz/default.gif)
運行結果
![](http://pubimage.360doc.com/wz/default.gif)
可以看到,3個greenlet是依次運行而不是交替運行
2. gevent切換執(zhí)行
![](http://pubimage.360doc.com/wz/default.gif)
運行結果
![](http://pubimage.360doc.com/wz/default.gif)
3個greenlet交替運行
3. gevent并發(fā)下載器
當然,實際代碼里,我們不會用gevent.sleep()去切換協(xié)程,而是在執(zhí)行到IO操作時,gevent自動切換,代碼如下
![](http://pubimage.360doc.com/wz/default.gif)
運行結果
![](http://pubimage.360doc.com/wz/default.gif)
從上能夠看到是先發(fā)送的獲取baidu的相關信息,然后依次是itcast、itheima,但是收到數(shù)據(jù)的先后順序不一定與發(fā)送順序相同,這也就體現(xiàn)出了異步,即不確定什么時候會收到數(shù)據(jù),順序不一定
十、gevent版-TCP服務器
![](http://pubimage.360doc.com/wz/default.gif)