SbWebServer的幾個設計

基於生產者消費者模型的線程池設計:

數據結構:緩存

成員:數據結構

1.一個隊列m_queue.其中存放Task,也就是任務,生產者線程,也就是主線程,往這個隊列中push;消費者,也就是工做線程,不停地從其中拿走Task去工做。app

關於Task,原型爲boost::function<void()> Task;socket

2.一個表示線程數量的變量m_threadNum函數

3.一個表示隊列,即流水線的最大長度變量m_maxSizethis

4.一個互斥量m_mutex,用於條件變量判斷時對隊列的狀態進行保護url

5.一個條件變量m_notFull,用於通知喚醒消費者線程線程

6.一個條件變量m_notEmpty,用於通知喚醒生產者線程設計

函數:指針

1.線程池構造函數ThreadPool(int threadNum,int maxSize)

threadNum爲線程數量,maxSize爲隊列最大大小

在構造函數中建立threadNum個工做線程

2.靜態函數startThread

工做線程實際上的工做內容,建立的工做線程從startThread開始工做。

首先pthread_detach,分離後線程的資源自動回收,不用join

startThread中傳入線程池的this指針,這樣靜態函數startThread能夠經過這個this指針調用線程池中的Work函數,Work纔是真正的線程工做的主體

3.Work函數

Work內是一個for循環f(;;),循環內調用函數Take()從線程池的隊列中取走任務,若是取來的任務可用,則執行這個任務

4.Take函數
典型的生產者消費者模型,首先用m_mutex加鎖,加鎖保護當前隊列狀態的原子性,用while(m_queue.empty())判斷流水線中是否有工做,若是沒有則wait陷入沉睡,直到生產者喚醒

若是有工做,則拿走,並返回這個Task;Work函數拿到這個Task後執行這個任務

5.append函數

參數傳入bind的function

典型的生產者消費者模型,用於生產者往流水線中添加任務。依然是mutex加鎖保護隊列的狀態,若是非滿則將這個function加入到隊列中

頁面緩存設計

緩存Cache:
數據成員:

一個哈希表unordered_map:key爲頁面名,做爲key;value爲一個shared_ptr指針,指向FileInfo,FileInfo爲封裝了頁面mmap到內存的類

一個互斥鎖mutex,用於緩存滿了刪除時保證原子性

函數:

1.getFile用於在頁面緩存中尋找請求的資源。

首先加鎖,防止請求資源時該緩存被淘汰而出現問題。而後在哈希表中用文件名去尋找,若是未找到,則將這個頁面加入緩存。若是此時緩存已滿,則淘汰掉訪問次數較少的頁面,訪問次數的統計被封裝在FileInfo中。

2.清楚緩存的函數cleancache:

遍歷哈希表,若是訪問次數小於10的頁面均淘汰掉。

 

頁面封裝FileInfo:

數據成員:

1.存儲 經過mmap映射到內存的地址 的變量addr:所訪問的頁面都經過mmap映射到內存,而後記錄這個地址,封裝到FileInfo中,這樣每次打開就不用了從新IO打開文件,只要經過這個文件對應的FileInfo中的addr便可尋址到頁面對應的位置

2.size表示文件的大小

3.count用於統計頁面的訪問次數,當緩存滿時以此爲基準進行淘汰

方法:

1.構造函數:經過文件名打開對應文件,而後使用mmap將之映射到內存,mmap返回的結果賦予addr,用於尋址

2.析構函數:munmap來釋放映射到內存的內容。固然,析構的時間由shared_ptr來掌握

 

Http_Handle封裝:

數據成員:

1.文件描述符fd,用於表示對應的套接字

2.狀態變量state:

state爲Read,process轉入processRead進行處理,表示此時應該將對端的數據存入readBuf緩衝區,並分析readBuf緩衝區的內容,根據url找到對應的文件,打開並加入緩存,並寫好響應頭到writeBuf

state爲Write,process轉入processWrite進行處理,將writeBuf內的響應信息寫到對端,將請求的資源寫入到對端。

3.兩個緩存readBuf和writeBuf:readBuf用於存放對端發送過來的數據,好比Http頭的信息,將之存放在readBuf中;writeBuf用於存放響應信息,好比響應行

4.一個指向所請求資源的FileInfo封裝的shared_ptr指針,經過這個指針來找到資源內容,並寫道對端。

總體流程:

首先服務端進入被動打開:

建立一個Socket,將這個socket綁定到8080端口,listen後轉爲監聽套接字,用於監聽請求

建立epoll,設定監聽的文件描述符個數

將監聽套接字listenfd加入epoll的監聽範圍

初始化線程池,建立若干工做線程,設定隊列(流水線)大小

進入while(true)循環:

epoll_wait等待事件發生,當listenfd可讀,表明有請求,因爲使用的是ET模式,所以listenfd要一直accept到返回EAGAIN,每accept一個,返回套接字connfd,將這個套接字封裝成Http_Handle,此時狀態爲Read,及等待對端像這個socket發送數據並分析。將這個套接字connfd也加入到epoll的監聽裏,監聽時間爲EPOLLIN,也就是可讀。

當epoll_wait等待來的是其餘事件,將這個事件對應的fd,把它的process函數bind到function上,並將這個function加入到流水線

工做線程從流水線上拿到這個Task,進行執行:

此時這個Task至關於綁定到某個Http_Handle上的Http_Handle::process,消費者執行process時,根據Http_Handle中state的狀態來決定接下來的動做:

1.Read:表明讀取對端發過來的鏈接包到readBuf,並分析readBuf的內容,找到URL指定的資源,這是經過緩存來找到的。找到後將響應行和響應頭相關的信息寫入writeBuf(由於writeBuf原本就被初始化爲全0,因此不用在尾部加\0)。結束後將狀態設置爲Write,即只須要講這些信息以及文件寫入到對端就能夠了。修改本fd在epoll中的監聽事件,原本是監聽可讀,如今要寫了,所以監聽EPOLLOUT。

2.Write:表明此時應該向對端寫數據了,轉入執行processWrite:這個過程就是將writeBuf中的頭信息寫到對端,而後用Http_Handle中封裝的FileInfo的智能指針找到映射到內存裏的文件,將文件寫到對端。一切結束後表明這個Socket的任務已經完成,斷開鏈接。

這裏主線程至關於生產者,不斷的往隊列中append不一樣fd的process函數,而後消費者從隊列中拿走這些process函數, process函數會根據Http_Handle.中State的狀態不一樣執行processRead或processWrite.

相關文章
相關標籤/搜索