網絡編程——客戶/服務器程序設計範式

網絡編程
網絡編程
  • 咱們在須要開發一個服務器程序時,有較多的的程序設計範式可供選擇,不一樣範式有其自身的特色和實用範圍,明瞭不一樣範式的特性有助於咱們服務器程序的開發。
  • 客戶端程序一般比服務器容易些,固然客戶端程序也可使用這些程序設計範式,由於它們蘊含的道理都是相通的。
  • 本文所設計的服務器主要是指基於TCP的服務器

迭代服務器

迭代TCP服務器 老是在徹底處理某個客戶的請求以後纔開始下一個客戶的請求處理
這樣的服務器實際中比較少見。
基於UDP的大多服務器倒是這樣實現編程

併發服務器,每一個客戶請求fork一個子進程

傳統併發服務器 調用fork派生一個子進程來處理每一個客戶,這使得服務器可以同時爲多個客戶服務,每一個進程一個客戶
客戶數目的惟一限制是操做系統對其可以同時擁有多少子進程的限制。
絕大多數TCP服務器程序都是按這個範式編寫。
併發服務器的問題在於爲每一個客戶現場fork一個子進程比較耗費CPU時間。服務器

預先派生子進程,每一個子進程無保護地調用accept

不一樣於傳統意義的併發服務器那樣爲每一個客戶現場派生一個子進程,而是 在啓動階段預先派生必定數量的子進程,當有客戶鏈接到達時,這些子進程就能當即爲它提供服務
這種技術的有點在於無需引入父進程執行fork的開銷就能處理新到來的客戶。缺點是父進程必須在服務啓動階段猜想須要預先派生多少子進程。若是某個時刻客戶數剛好等於子進程總數,那麼新到的客戶將被忽略,直到至少有一個子進程完成處理從新可用。網絡

預先派生子進程,使用文件上鎖保護accept

在多個進程中引用同一個監聽套接字的描述符上調用accept的作法在某些系統實現是不被支持的,那麼解決辦法是讓應用進程在調用accept先後安裝某種形式的鎖(lock),這樣任意時刻只有一個子進程阻塞在accept調用中,其餘子進程則阻塞在獲取保護accept的鎖上。
這裏使用文件鎖來保護,文件鎖涉及到文件系統的操做,可能比較耗時。多線程

預先派生子進程,使用線程互斥鎖上鎖保護accept

相比於 預先派生子進程,使用文件上鎖保護accept,使用線程鎖保護accept,這種方法 不只適用於同一進程內各個線程間的鎖保護,並且可以用於不一樣進程之間的鎖保護
在不一樣進程間的鎖保護須要注意的是併發

  • 互斥鎖變量必須存放在由全部進程共享的內存區中
  • 必須告知線程函數庫這個鎖是用於不一樣進程間共享的互斥鎖

預先派生子進程,父進程向子進程傳遞套接字描述符

只讓父進程嗲用accept,而後把所接受的已經鏈接的套接字 傳遞 給某個子進程。
這樣繞過了爲全部子進程的accept調用提供上鎖保護的需求,可是須要從父進程到子進程進行某種形式的描述符傳遞。
這種技術會上代碼比較複雜,父進程必須跟蹤子進程的閒忙狀態,以便於給空閒的子進程傳遞新的套接字。app

併發服務器,每一個客戶端請求建立一個線程

相比於多進程模型,若是服務器主機提供支持線程,咱們能夠改用線程以取代進程。線程相比於進程的優點這裏再也不累述。函數

預先建立線程服務器,使用互斥鎖上鎖保護accept

相比預先派生一個子進程池快於爲每一個客戶線程fork一個子進程池相似的道理,在有線程支持的系統上,預先建立的線程池取代爲每一個客戶現場建立一個線程的作法有相似的性能提高。
這種模式的基本設計是預先建立一個線程,並讓每一個線程各自調用accept,取代讓每一個線程都阻塞在accept調用中的作法,使用互斥鎖保證任什麼時候刻只有一個線程在調用accept。性能

預先建立線程服務器,由主線程調用accept

程序啓動階段建立一個線程池後讓主線程調用accept;
主線程把每一個客戶鏈接傳遞給池中某個可用的線程,相似於進程版本的作法。
這樣的設計問題在於主線程如何將一個已鏈接套接字傳遞給線程池中某個可用線程spa

咱們有不少實現手段,本可用如前面同樣使用描述符傳遞,可是既然全部線程和全部描述符都在同一個進程中,那麼也就沒有必要把一個描述符從一個線程傳遞到另外一個線程。接收線程只須要知道這個已鏈接套接字描述符的值(傳遞描述符可不僅是傳遞這個值,事實上是須要傳遞這個套接字的引用,所以也將返回一個不一樣於原值的描述符,該套接字的引用計數也會增長操作系統

總結

  • 系統負載較輕時,每來一個客戶請求現場派生一個子進程爲之服務的傳統併發服務器程序模型就足夠了
  • 相比傳統的每一個客戶fork一次設計範式,預先建立一個子進程池或一個線程池的範式可以把進程控制CPU時間下降10倍或以上。編寫這些範式的程序並不會複雜,不過會有額外的工做,好比監視如今子進程數,隨着所服務客戶數的動態變化而增長或減小這個數目
  • 某些實現容許多個子進程或線程阻塞在同一個accept調用中,另外的實現卻要求對accept調用須要某種類型的鎖加以保(文件鎖或者互斥鎖等)
  • 讓全部子進程或線程自行調用accept一般比讓父進程或主線程獨自調用accept並把描述符傳遞個子進程或線程來的簡單和快捷
  • 因爲潛在select衝突的緣由,讓全部子進程或線程阻塞在同一accept調用中比讓他們阻塞在同一個select調用中更可取。
  • 使用 線程一般遠快於使用進程,不過選擇每一個客戶一個子進程仍是每一個客戶一個線程取決於操做系統提供什麼支持(某些系統不提供線程支持),還可能取決於爲服務每一個客戶須要激活其餘什麼程序。例如,若是accept客戶鏈接的服務器調用fork和exec,那麼fork一個單線程的進程可能快於fork一個多線程的進程,另外還有資源等方面的綜合考慮。
相關文章
相關標籤/搜索