本篇從基於tcp/ip協議出發,**現代流行的應對高併發請求網路服務端設計架構;
1. tcp/ip 模型
首先回顧一下tcp/ip模型,並知道各個層次在作業系統的哪乙個層次;
看上圖,osi模型的底下兩層是隨系統提供的裝置驅動程式和網路硬體。通常情況下,除需知道資料鏈路的某些特性外,我們不用關心這兩層的情況。網路層由ipv4和ipv6兩個協議處理,可以選擇的傳輸層有tcp或udp。osi模型的頂上三層被合併為一層,稱為應用層,這就是web客戶(瀏覽器)、telnet客戶、web伺服器等都所在的層。
可見osi模型以傳輸層為分界線;套接字程式設計介面為應用層進入傳輸層的介面;也符合抽象的目的:對應用層隱藏網路協議的具體通訊細節,應用層只處理具體網路應用;而底下四層(從傳輸層到物理層)對具體網路應用了解不多,卻處理所有的通訊細節:傳送資料,等待確認,給無序到達的資料排序,計算並驗證校驗和,等等。
其次,頂上三層通常構成所謂的使用者程序,底下四層卻通常作為作業系統核心的一部分提供。完全符合unix和其他現代作業系統都提供分割使用者程序和核心的機制。
2. 基本tcp 套接字程式設計
tcp:傳輸控制協議(transmission control protocol).tcp是乙個面向連線的協議,為使用者程序提供可靠的全雙工位元組流。tcp套接字是一種流套接字(stream socket). tcp關心確認,超時和重傳之類的細節。大多數網路應用程式使用tcp;
以apache為例子;apache工作在osi模型的應用層;我們都知道啟動apache後他會監聽在80埠,並通過執行緒池來響應每個客戶請求;即來乙個客戶請求,從連線池那齣乙個執行緒去處理這個請求;那麼apache的內部是如何針對每個請求建立套接字,然後分配乙個執行緒的呢?
看圖2,apache伺服器啟動後通過建立監聽套接字listensocket,通過呼叫bind(),listen(),accept(),讓主線程監聽在80埠;客戶請求過來後tcp三次握手,握手成功後accept()返回乙個已連線套接字connsocket,代表與所返回客戶的tcp連線;然後apache從連線池里拉出乙個執行緒,並將這個已連線套接字給這個執行緒,讓次執行緒處理這個請求;請求處理完畢後關閉連線,已連線套接字相應的**;
注:監聽套接字(listening socket)是在該tcp服務程序生命週期內一直存在。而已連線套接字代表乙個和客戶請求建立的乙個套接字,生命週期為一次請求;
圖2 : 基本tcp客戶/伺服器程式套接字函式
注:我們的jms客戶端程式也是採用主線程監聽佇列訊息,並將收到的訊息給執行緒池裡的乙個執行緒去處理的方式來併發處理訊息;
3. 現代併發web伺服器的設計正規化:
但是apache真的是乙個主線程在監聽80埠麼? 不是的,乙個主線程監聽80埠,然後將已連線套接字拋給執行緒池裡的執行緒去處理,有些缺點;
缺點如下:1. 只有乙個執行緒監聽80埠,並負責三次握手和**已連線套接字;萬一此執行緒掛了,整個web server就沒有服務能力了,容錯能力不強;
2. 因為connect(),accept()都是阻塞函式。所以每個客戶請求來都阻塞在主線程,導致不能很好的應對併發;
基於這些缺點;apache採用每個執行緒都去監聽80埠,當然同一時刻,只有乙個執行緒在監聽80埠;其他執行緒屬於空閒狀態;乙個客戶請求過來,正在監聽的執行緒會處理這個請求並轉換為工作者執行緒;並讓出監聽者的角色,這樣其他執行緒競爭監聽者的角色,並最終有乙個執行緒監聽80埠;當乙個工作執行緒處理完請求後,回到連線池中,又處於空閒狀態;
但是這樣又有問題,如果乙個客戶請求過來,會導致所有的空閒執行緒都去競爭監聽者的角色。會導致很多空閒執行緒一下被喚醒,並只有乙個執行緒獲得監聽者角色,其他執行緒繼續空閒(睡覺),這種大批執行緒從空閒狀態突然被喚醒有突然又睡過去,就是驚群現象;驚群現象會導致效能下降;
apache通過在每個accept()函式上增加互斥鎖和條件變數來解決這個驚群問題。保證每個請求只會被乙個執行緒剛好拿到,不會影響其他執行緒;
這裡詳細介紹下:條件變數與互斥鎖不同,條件變數是用來等待而不是用來上鎖的。條件變數用來自動阻塞乙個執行緒,直到某特殊情況發生為止。通常條件變數和互斥鎖同時使用;互斥鎖提供互斥機制,條件變數提供訊號機制;
那麼apache是如何利用條件變數和互斥鎖來解決每次只有乙個空閒執行緒被喚醒,並且處於監聽者角色呢?
每次乙個新的客戶請求過來,正在監聽的執行緒與該請求建立連線,並變為worker工作者執行緒。讓出監聽者角色時它同時傳送訊號到條件變數,並釋放鎖。這樣在空閒(idle)狀態的乙個執行緒將被喚醒並獲得鎖。
也就是說:條件變數保證了其他執行緒在等待條件變化期間處於睡眠;互斥鎖保證一次只有乙個執行緒被喚醒;
圖3 :apache 的preforking 機制
總結:通過了解了tcp/ip程式設計模型。和apache的mpm、preforking機制後,我們再去看jms訊息客戶端**,memcache伺服器**,jetty,等流行的web伺服器的機制就不是很難了。
參考:《unix網路程式設計 卷1 :套接字聯網api》
客戶 伺服器程式設計正規化
unix 網路程式設計第30章讀書筆記,這裡只記錄大致實現方式,具體 實現還請閱讀此書 tcp 迭代伺服器 完全同步方式,完全處理某個客戶的請求之後才專向下乙個客戶,優點是 簡單,並且沒有程序控制所需的時間 tcp 併發伺服器程式,每個客戶乙個子程序 傳統上併發伺服器呼叫fork 派生乙個子程序來處...
主題 客戶 伺服器程式設計正規化
原文出處 本篇從基於tcp ip協議出發,現代流行的應對高併發請求網路服務端設計架構 1.tcp ip 模型 看上圖,osi模型的底下兩層是隨系統提供的裝置驅動程式和網路硬體。通常情況下,除需知道資料鏈路的某些特性外,我們不用關心這兩層的情況。網路層由ipv4和ipv6兩個協議處理,可以選擇的傳輸層...
網路程式設計 客戶 伺服器程式設計正規化
迭代tcp伺服器總是在完全處理某個客戶的請求之後才開始下乙個客戶的請求處理。這樣的伺服器實際中比較少見。基於udp的大多伺服器卻是這樣實現。傳統併發伺服器呼叫fork派生乙個子程序來處理每個客戶,這使得伺服器能夠同時為多個客戶服務,每個程序乙個客戶。客戶數目的唯一限制是作業系統對其能夠同時擁有多少子...