- 咱們在須要開發一個服務器程序時,有較多的的程序設計範式可供選擇,不一樣範式有其自身的特色和實用範圍,明瞭不一樣範式的特性有助於咱們服務器程序的開發。
- 客戶端程序一般比服務器容易些,固然客戶端程序也可使用這些程序設計範式,由於它們蘊含的道理都是相通的。
- 本文所設計的服務器主要是指基於TCP的服務器
迭代TCP服務器 老是在徹底處理某個客戶的請求以後纔開始下一個客戶的請求處理。
這樣的服務器實際中比較少見。
基於UDP的大多服務器倒是這樣實現。編程
傳統併發服務器 調用fork派生一個子進程來處理每一個客戶,這使得服務器可以同時爲多個客戶服務,每一個進程一個客戶。
客戶數目的惟一限制是操做系統對其可以同時擁有多少子進程的限制。
絕大多數TCP服務器程序都是按這個範式編寫。
併發服務器的問題在於爲每一個客戶現場fork一個子進程比較耗費CPU時間。服務器
不一樣於傳統意義的併發服務器那樣爲每一個客戶現場派生一個子進程,而是 在啓動階段預先派生必定數量的子進程,當有客戶鏈接到達時,這些子進程就能當即爲它提供服務。
這種技術的有點在於無需引入父進程執行fork的開銷就能處理新到來的客戶。缺點是父進程必須在服務啓動階段猜想須要預先派生多少子進程。若是某個時刻客戶數剛好等於子進程總數,那麼新到的客戶將被忽略,直到至少有一個子進程完成處理從新可用。網絡
在多個進程中引用同一個監聽套接字的描述符上調用accept的作法在某些系統實現是不被支持的,那麼解決辦法是讓應用進程在調用accept先後安裝某種形式的鎖(lock),這樣任意時刻只有一個子進程阻塞在accept調用中,其餘子進程則阻塞在獲取保護accept的鎖上。
這裏使用文件鎖來保護,文件鎖涉及到文件系統的操做,可能比較耗時。多線程
相比於 預先派生子進程,使用文件上鎖保護accept,使用線程鎖保護accept,這種方法 不只適用於同一進程內各個線程間的鎖保護,並且可以用於不一樣進程之間的鎖保護。
在不一樣進程間的鎖保護須要注意的是併發
- 互斥鎖變量必須存放在由全部進程共享的內存區中
- 必須告知線程函數庫這個鎖是用於不一樣進程間共享的互斥鎖
只讓父進程嗲用accept,而後把所接受的已經鏈接的套接字 傳遞 給某個子進程。
這樣繞過了爲全部子進程的accept調用提供上鎖保護的需求,可是須要從父進程到子進程進行某種形式的描述符傳遞。
這種技術會上代碼比較複雜,父進程必須跟蹤子進程的閒忙狀態,以便於給空閒的子進程傳遞新的套接字。app
相比於多進程模型,若是服務器主機提供支持線程,咱們能夠改用線程以取代進程。線程相比於進程的優點這裏再也不累述。函數
相比預先派生一個子進程池快於爲每一個客戶線程fork一個子進程池相似的道理,在有線程支持的系統上,預先建立的線程池取代爲每一個客戶現場建立一個線程的作法有相似的性能提高。
這種模式的基本設計是預先建立一個線程,並讓每一個線程各自調用accept,取代讓每一個線程都阻塞在accept調用中的作法,使用互斥鎖保證任什麼時候刻只有一個線程在調用accept。性能
程序啓動階段建立一個線程池後讓主線程調用accept;
主線程把每一個客戶鏈接傳遞給池中某個可用的線程,相似於進程版本的作法。
這樣的設計問題在於主線程如何將一個已鏈接套接字傳遞給線程池中某個可用線程spa
咱們有不少實現手段,本可用如前面同樣使用描述符傳遞,可是既然全部線程和全部描述符都在同一個進程中,那麼也就沒有必要把一個描述符從一個線程傳遞到另外一個線程。接收線程只須要知道這個已鏈接套接字描述符的值(傳遞描述符可不僅是傳遞這個值,事實上是須要傳遞這個套接字的引用,所以也將返回一個不一樣於原值的描述符,該套接字的引用計數也會增長)操作系統