UNP伺服器設計正規化總結

2022-07-03 17:18:19 字數 3915 閱讀 7475

一:客戶端

本章總結的伺服器程式設計正規化,使用同乙個客戶端程式進行測試。客戶端執行在和伺服器處於同乙個子網上的兩個不同主機上。每個客戶端同時派生5

個子程序,每個子程序在與伺服器依次建立

500次連線,每次連線請求

4000

個位元組的資料。因此,每個客戶端將與服務端建立2500個連線。總共有5000次連線,而且任意時刻服務端最多存在

10個連線。使用的命令如下:

client  206.62.226.36  8888  5  500  4000

二:結論

上圖測量的時間僅僅是用於程序控制所需的

cpu時間,以迭代伺服器為基準(原因見下一節),從其他伺服器的實際cpu時間中減去迭代伺服器的實際cpu時間,就得到相應伺服器用於程序控制所需的cpu時間。因為迭代伺服器沒有程序控制。

上圖描述了,在預先建立子程序池的設計正規化中,閒置子程序過多對cpu時間的影響。

上圖描述了,在預先建立子程序池和執行緒池的設計正規化中,5000個連線在所有子程序或執行緒中的分布情況。

三:tcp迭代伺服器

迭代tcp

伺服器總是在完全處理完某個客戶的請求之後,才去處理下乙個客戶。因此這樣的伺服器程式比較少見。本章中用它作為測試基準,使用的測試命令如下:

client  206.62.226.36  8888  1  5000  4000

使用同樣的tcp連線數5000,請求一樣的位元組數4000,但是同一時間只有乙個連線,不進行程序控制,因而它的時間是最快的。

四:tcp併發伺服器程式,每個客戶乙個子程序

傳統上,併發伺服器呼叫

fork

派生乙個子程序來處理每個客戶。這使得伺服器能夠同時為多個客戶服務,每個程序乙個客戶。

絕大多數

tcp伺服器程式也按照這個正規化編寫。但是併發伺服器的問題在於為每個客戶現場

fork

乙個子程序比較耗費

cpu時間。在多年前的20世紀80年代後期,乙個繁忙的伺服器每天也就處理幾百個亦或幾千個客戶,這點cpu時間是可以接受的。然而web應用的爆發式增長改變了人們的態度。繁忙的web伺服器每天測得tcp連線數以百萬計,這還是就單個主機而言。

以後講解的各種技術,都是在避免併發伺服器為每個客戶現場fork的做法,不過傳統意義上的併發伺服器依然相當普遍。

結論圖1中的資料表明,傳統意義的併發伺服器所需

cpu時間最多。

五:tcp預先派生子程序,accept無上鎖保護

使用預先派生子程序的技術,不像傳統意義上的併發伺服器那樣,為每個客戶現場派生子程序,而是在啟動階段預先派生一定數量的子程序,當各個客戶連線到達時,這些子程序立即就能為他們服務。

**結構是:父程序啟動完所有子程序之後,就進入睡眠。每個子程序中:

for(; ;)

在這種程式正規化中,因為所有子程序和父程序共享同乙個監聽套接字。所以,當第乙個客戶連線到達時,所有子程序均被喚醒,因為所有子程序使用的監聽套接字指向同乙個

socket

結構。只有最先執行的那個子程序能獲得客戶連線,而其他子程序只能繼續回覆睡眠。

這種現象稱為驚群(

thundering herd

)問題。因為儘管只有乙個子程序將獲得連線,所有其他子程序卻都被喚醒了。儘管如此這段**依然起作用,只是每僅有乙個連線準備好時,卻喚醒太多程序的做法會導致效能受損。結論圖2的資料表明了這種問題。

結論圖3給出了所有連線在子程序中的分布情況,可見當可用子程序阻塞在accept呼叫上時,核心排程演算法把各個連線均勻地散布到各個子程序。

六:tcp預先派生子程序,accept使用上鎖保護

有些系統不允許多個程序監聽同乙個套接字,在這些系統中,上述**啟動不久,某個子程序的

accept

可能就會返回

eproto

錯誤。解決辦法是讓每個子程序在呼叫

accept

前後安置某種型別的鎖,這樣任意時刻只有乙個子程序阻塞在

accept

呼叫中,其他子程序則阻塞在試圖獲取用於保護

accept

的鎖上。子程序中的**結構如下:

for(; ;)

有多種方法可以提供上鎖功能,可以使用

fcntl

函式記錄鎖,也可以使用執行緒互斥鎖

pthread_mutex_lock

進行程序上的上鎖。

根據結論圖1,可見這種上鎖增加了伺服器的程序控制

cpu時間,但是執行緒互斥鎖還是要快於記錄鎖的。

根據結論圖3,看見上鎖時,作業系統還是均勻的把鎖散步到等待執行緒中。

七:tcp預先派生子程序,傳遞描述符

這種設計是只讓父程序呼叫

accept

,然後把所接受的已連線套接字「傳遞」給某個子程序。這麼做繞過了為所有子程序的accept呼叫提供上鎖保護的需求,不過需要從父程序到子程序的某種形式的描述符傳遞。這種技術會使**多少有點複雜,因為父程序必須跟蹤子程序的忙閒狀態,以便給空閒子程序傳遞新的套接字。

子程序中的**結構如下,子程序阻塞在read_fd呼叫中,等待父程序傳遞描述符,收到描述符之後,處理客戶請求。子程序在處理完客戶請求之後,會向管道中寫入乙個位元組,以通知父程序本子程序可重用(閒置狀態):

for ( ; ; )

根據結論圖1,可見本伺服器慢於所有其他子程序池的設計。

八:tcp併發伺服器,每個客戶乙個執行緒

使用執行緒代替子程序,客戶連線到來時,現場建立乙個執行緒處理客戶請求,**結構如下:

主線程:

for ( ;  ; )

子執行緒:

void  * doit(void *arg)

結論圖1

表明,這個簡單的建立執行緒的版本快於所有預先派生子程序的版本。

九:tcp預先建立執行緒,每個執行緒各自accept

既然預先派生子程序快於為每個客戶現場派生乙個子程序,那麼有理由相信預先建立乙個執行緒池也快於為每個客戶現場建立乙個執行緒的做法。本節中的設計,是讓每個執行緒各自呼叫

accept

,使用互斥鎖加以保護,以保證任何時刻只有乙個執行緒在呼叫

accept

。結論圖1表明,這樣的設計確實快於每個客戶現場乙個執行緒的版建立執行緒池,事實上當前版本的伺服器是所有版木之中最快的。

結論圖3表明,所有連線也均勻的分布在各個執行緒上了,這種均衡性是由執行緒排程演算法帶來的。

十:tcp預先建立執行緒,主線程統一accept

主線程在建立乙個執行緒池之後,只讓主線程呼叫accept,並把每個客戶連線傳遞給池中某個可用執行緒。

本設計正規化的問題在於,主線程如何把乙個已連線套接字傳遞給執行緒池中某個可用執行緒。

可以如前使用描述符傳遞,不過既然所有執行緒和所有描述符都在同乙個程序之內,我們沒有必要把乙個描述符從乙個執行緒傳遞到另乙個執行緒。接收執行緒只需知道這個已連線套接字描述符的值,而描述符傳遞實際傳遞的井非這個值,而是對這個套接字的乙個引用,因而將返回乙個不同於原值的描述符。

所以,在多執行緒環境中,這實際上又是乙個生產者消費者問題,主線程為乙個生產者,所有子執行緒為多個消費者。

根據結論圖1,可見這個版本的伺服器慢於上一節中的互斥鎖保護accept的版本,這是因為生產者和消費者之間的同步問題造成的。

摘自《unix網路程式設計卷一:套接字聯網api》第30章

unp筆記二 多程序伺服器

listenfd socket bind listenfd,listen listenfd,signal sigchld,sig chld 特意加上處理僵死程序,請看2 for close connfd 連線套接字有子程序負責,父程序關了它 伺服器子程序終止時,給父程序傳送乙個sigchld訊號 s...

客戶 伺服器程式設計正規化

本篇從基於tcp ip協議出發,現代流行的應對高併發請求網路服務端設計架構 1.tcp ip 模型 首先回顧一下tcp ip模型,並知道各個層次在作業系統的哪乙個層次 看上圖,osi模型的底下兩層是隨系統提供的裝置驅動程式和網路硬體。通常情況下,除需知道資料鏈路的某些特性外,我們不用關心這兩層的情況...

客戶 伺服器程式設計正規化

unix 網路程式設計第30章讀書筆記,這裡只記錄大致實現方式,具體 實現還請閱讀此書 tcp 迭代伺服器 完全同步方式,完全處理某個客戶的請求之後才專向下乙個客戶,優點是 簡單,並且沒有程序控制所需的時間 tcp 併發伺服器程式,每個客戶乙個子程序 傳統上併發伺服器呼叫fork 派生乙個子程序來處...