引言
我看乙個專案的時候,比較喜歡首先看它的架構和設計。因為這樣在研讀原始碼的時候,有乙個指導作用,不會迷失於具體細節,並能夠引導我如何去將點串成線,將線串成面。而且乙個軟體怎麼樣,很大程度上取決於它採用的架構。
1、執行緒模型
2、從程式入口著手
3、mongoose的生命旅程
1、執行緒模型
mongoose採用了乙個自適應的執行緒池的模型。有乙個主線程(master thread)用於開啟配置埠和等待連線的到了。一旦新的連線到來,主線程將衍生乙個新的執行緒去服務該連線。當衍生的執行緒處理完連線的請求之後,它會保持一段時間的空閒(可以通過配置選項-idle_time 控制空閒時間),在此期間主線程可能會傳遞另乙個連線給它,讓它服務。
因此,每個連線都是在自己的執行緒中執行,且執行緒數量隨著web伺服器的負載而變化。然而,最大的活躍執行緒數由-max_thread 控制。如果一旦總的執行緒數達到了這個閾值,當新的連線到來時,主線程將等到有執行緒空閒時在分配執行緒服務新到來的連線。以此同時,建立了tcp監聽佇列,即當沒有執行緒空閒時到來的新連線會被置入該佇列,當有執行緒空閒了會從佇列中取出連線並服務。如果沒有執行緒變空閒,而tcp佇列又滿了,web伺服器將拒絕新到來的連線請求。
上面所述的過程大致如下所示:
圖1 執行緒模型
2、從程式入口著手
在《mongoose原始碼剖析:introduction and installation》中,我們簡單分析了makefile檔案知道生成的mongoose執行檔案的入口肯定在main.c中(如果將mongoose嵌入到你的應用程式中,就由你來決定入口了!)。在典型的main函式入口中,我們可以看到下面的流程:
main()
上面即是main函式中的主流程。需要注意的是呼叫mg_start()之後返回乙個mg_context結構體的例項,這個例項將會在整個連線請求中用到,而且如果你在啟動mongoose中設定了引數選項,在下面的process_command_line_arguments()函式中還會對ctx進行修改。從這裡我們也知道了,mongoose程式的核心入口時mg_start(3、mongoose的生命旅程
通過上面的分析我們知道mongoose起始於mg_stop(),終結於mg_stop()。下面我們就從生命之初到生命終結之間的「故事」。說明:在這裡我們不會去過於追究細節,只是串線式的把mongoose的生命流程串起來,哪些細節或許後續的文章來解釋,或者留給讀者你去做了!
在mg_start()主要是做一些初始化的工作,最後才會正式進入工作服務於client。這裡的初始化工作就好比乙個人的出生需要十月懷胎,為誕生積蓄能量,要從受精卵長成乙個完整的人。在準備工作完成之後,mg_start()會啟動乙個主線程master_thread,它用於監聽所有的client連線請求。
啟動乙個主線程即啟動了乙個web server,在主線程中首先會將該server監聽的位址(socket)加入到監聽集合中去。然後一直監聽該埠,只要有client的連線請求到來,它會呼叫accept_new_connection()去處理連線請求。
接下來,我們關注的是accept_new_connection()是如何去處理連線請求的。首先它會進行一些預判工作,決定是否允許該連線。如果允許,則呼叫put_socket()並將處理工作轉交給它,所謂權力下放。
在put_socket()首先也會進行一些預判工作——判斷mg_context結構體的成員變數queue佇列是否已滿,如果滿了就等待直到queue有位置容納請求。還有一點要說明的是:由於有可能多個client請求同時到達,對queue進行操作,所以在put_socket()中一開始就設定(void) pthread_mutex_lock(&ctx->thr_mutex);而且請求是通過調節變數來控制等待queue是否有位置容納請求(void) pthread_cond_wait(&ctx->full_cond, &ctx->thr_mutex)。說了這麼多準備工作,現在該正式進入工作了。至此,如果沒有空閒程序且程序數量沒有達到最大閾值,就會啟動乙個新的工作程序worker_thread去處理client的請求。之後就是釋放訊號量等資源,讓其它client請求也能夠請求到資源工作,如啟動了乙個工作程序去處理client請求,這時queue就空出乙個位置了,它會呼叫pthread_cond_signal(&ctx->empty_cond)讓等待的client請求知道queue中有位置了。最後就是釋放put_socket()中一開始設定的鎖,(void) pthread_mutex_unlock(&ctx->thr_mutex)。
到了這裡,client的請求已經被分配打乙個工作執行緒中去了。而且不同的client請求處理執行在不同的工作執行緒中,能夠互不干擾。在worker_thread中,首先與client建立連線,只有連線上了才能為client服務。連線建立之後呼叫process_new_connection()去處理請求。處理完之後返回關閉連線,並通過訊號機制告訴主線程,我的做工做完了。
在process_new_connection()中處理工作:首先解析請求parse_http_request(),知道請求的內容;接著就是進入mongoose處理client請求的真正核心工作了analyze_request()。這裡就不詳細介紹parse_http_request()、analyze_request()是如何去解析、驗證、提供具體服務的,否則就陷入了細節出不來了,這裡主要是介紹mongoose的生命之旅的主線。
下面用圖形來形象描述一下mongoose的生命之旅,說明:該圖形並不是乙個精確的邏輯關係,圖中的箭頭方向只是描述了程式的大概流程,並都是上級呼叫下級的關係,如並不是parse_http_request()呼叫analyze_request()等,而實際上它們都是在process_new_connection()被呼叫。),最後終結於mg_stop()。
說明:第乙個函式應該為:mg_start(),不是mg_stop()
圖2、mongoose的大概生命主線
說明:就mongoose原始碼中自帶的main.c,最終呼叫的函式是send_directory().
4、總結
至此,算是介紹完了mongoose的乙個完整的工作模型了,你可以安裝此主線去進行code review。只有你腦海裡有這樣乙個模型,你就不會在研讀**是迷失了。
當然mongoose提供的很多api,這裡都沒有介紹到,因為它不是本文的重點。我希望此能夠帶後來者步入mongoose原始碼研讀的大門,給後來者節省徘徊在門外停滯不前的時間。
IIs之web伺服器,FTP伺服器
既往不戀,當下不雜,未來不迎。1 web伺服器也成為網頁伺服器或者http伺服器。2 web伺服器使用的協議是http或者https。3 http協議埠號 tcp 80,https協議埠號 tcp 443。ftp協議埠號 21。linux apache lamp tomcat nginx etc,第...
web伺服器 簡單web伺服器實現
三次握手 一般情況下是瀏覽器先傳送請求資料,c s ack 應答 三次握手成功後,才開始進行通訊資料的收發。四次揮手 一般情況下是客戶端先關閉,給瀏覽器傳送關閉資訊。如果瀏覽器傳送了關閉資訊,但是伺服器沒有回過去,較慢 那麼瀏覽器一直發是不是就會有問題?所以會等待 2msl的時間。一般為2 5分鐘。...
Nodejs資料之web伺服器
建立web伺服器示例 引用系統模組 建立web伺服器 當客戶端傳送請求的時候 響應 res.end 監聽3000埠 請求資訊獲取方法 req.headers 獲取請求報文 req.url 獲取請求位址 req.method 獲取請求方法 get請求處理 引數被放置在瀏覽器位址列中,獲取引數需要使用系...