用線程池實現的簡單C++ web服務器


                                          用線程池實現的簡單C++ Web服務器

寫了一個基於半同步/半反應堆模式的線程池實現的簡單web服務器,主要可以復習IO復用,線程池,信號,有限狀態機,HTTP協議解析等內容。

自己總結了一下項目過程中遇到的問題,最后給出代碼,代碼是基於《Linux高性能服務器編程》編寫的。

0 服務器的功能和基本架構

功能:結合線程池實現的一個並發web服務器,能夠解析HTTP的GET請求,支持HTTP長連接,使用瀏覽器訪問可以返回對應的內容。

基本架構:采用Reactor模式(事件驅動+非阻塞IO) + 線程池。epoll循環用於事件通知,如果是listenfd上可讀,則調用accept將新建的fd加入到epoll例程中;如果是已經連接的fd,將其加入到生產者-消費者隊列中由工作線程競爭執行任務。

1 為什么使用線程池?線程池的具體實現?

1)多進程調用fork函數時,雖然地址空間是寫時復制的,但是需要復制父進程的頁表(CSAPP-P584),開銷大,采用線程可以解決fork開銷的問題,但是調度器和內存的問題還是沒法解決,所以采用線程池,線程的數量固定。可以解決上面的問題。由於TCP連接是長連接,read讀取會一直等待數據讀完,造成阻塞,所以要設置成非阻塞。

2)線程池的實現:使用C++封裝一個線程池類,大致是創建固定數目的線程(比如和內核數目相當),然后類內部維護一個生產者-消費者隊列(采用mutex和semaphore),提供相應的添加任務(生產者)和執行任務(消費者)的處理函數。

其中semaphore保證隊列滿了不要繼續壓入,隊列為空的時候不要取任務操作,mutex互斥鎖保證每次只有一個線程在執行隊列的讀寫操作。

 2 epoll使用邊緣觸發(Edge Trigger)還是條件觸發(level Trigger)?

答:考慮這樣的情況,如果將fd放入生產者-消費者隊列中后,拿到這個任務的工作線程還沒有讀完這個fd,因為沒讀完數據,所以這個fd可讀,那么下一次事件循環又返回這個fd,又分給別的線程,就會出現數據一半給1號線程,另一半給了2號線程,沒法處理數據,怎么處理?

//水平觸發LT每次讀取數據,沒讀完fd的數據就注冊事件,等到下一個epollwait再處理。
//邊緣觸發ET每次都需要讀完數據,用了while循環讀取輸入數據。

采用邊緣觸發ET,邊緣觸發中輸入緩沖收到數據僅注冊一次該事件,即使輸入緩沖中還有數據,也不會注冊事件。

用法是:將文件描述符fd設置為非阻塞,如果該fd可讀,就使用一個while循環讀取輸入數據,直到返回-1且EAGAIN為止。

3 如果某個線程在處理fd的同時,又有新的一批數據發來,該fd可讀(注意和上面那個問題的區別,一個是處理同一批數據時,一個是處理新來的一批數據),那么該fd會被分給另一個線程,這樣兩個線程處理同一個fd肯定就不對了。

答:即使我們使用ET模式,一個socket上的某個事件還是可能被觸發多次,這不是我們希望的,所以可以使用epoll的EPOLLONESHOT事件解決。

對於注冊了EPOLLONSESHOT事件的文件描述符,操作系統最多觸發其上注冊的一個可讀、可寫或者異常事件,且只觸發一次,除非我們使用epoll_ctl函數重置該文件描述符上注冊的EPOLLONESHOT事件。這樣,當一個線程在處理某個socket時,其他線程是不可能有機會操作該socket的。

一旦注冊了EPOLLONSESHOT事件的socket被某個線程處理完畢,該線程就應該立即重置這個socket上的EPOLLONESHOT事件,以確保這個socket下一次可讀時,其EPOLLIN事件能被觸發,進而讓其他工作線程有機會繼續處理這個socket。

4 如何實現HTTP的解析?數據讀到一半該怎么辦?

答:HTTP的請求每行結束都是回車換行\r\n,讀取的數據長度等於0,說明讀到了一個空行。

首先需要明白HTTP的頭部沒有提供頭部長度字段,我們判斷HTTP頭部結束的依據是遇到一個空行,該空行僅包括一對回車換行符(<CR><LF>),如果一次讀操作沒有讀入HTTP請求的整個頭部,即沒有遇到空行,那我們必須等待客戶繼續寫數據並再次讀入。在不斷尋找空行的過程中,我們可以完成對整個HTTP請求頭部的分析(包括請求行和消息頭),我們使用主從兩個有限狀態機實現HTTP請求的讀取和分析。

主狀態機在內部調用從狀態機,從狀態機有三種狀態:讀取到一個完整的行LINE_OK,行出錯LINE_BAD,行數據尚且不完整LINE_OPEN,從狀態機是分析是否已經讀取一行內容,主狀態機分析多行內容,即一個HTTP完整請求。

從狀態機即parse_line函數,它從buffer中解析出一個行,下圖描述了其可能的狀態及狀態轉移過程。

 

 當從狀態機讀取到一行完整的內容后,就可以將內容交給主狀態機process_read進行處理。

主狀態機使用checkstate變量來記錄當前的狀態。

如果當前狀態是CHECK_STATE_REQUESTLINE,則表示parse_line函數解析出的行是請求行,於是主狀態機調用parse_requestline來分析請求行;

如果當前狀態是CHECK_STATE_HEADER,則表示parse_line函數解析出的是頭部字段,於是主狀態機調用parse_headers來分析頭部字段。

如果當前狀態是CHECK_STATE_CONTENT,則表示parse_line函數解析出的是消息體,於是主狀態機調用parse_content來分析消息體。(實際上只是分析是否讀入了消息體)

checkstate變量的初始值是CHECK_STATE_REQUESTLINE,parse_requestline函數在成功地分析請求行之后將其設置為CHECK_STATE_HEADER,從而實現狀態轉移。

 

主狀態機的核心代碼如下:

復制代碼
//主狀態機,用於從buffer中取出所有完整的行
http_conn::HTTP_CODE http_conn::process_read(){
    LINE_STATUS line_status = LINE_OK;//記錄當前行的讀取狀態
    HTTP_CODE ret = NO_REQUEST;//記錄HTTP請求的處理結果
    char* text = 0;
</span><span style="color: #0000ff;">while</span>(((m_check_state == CHECK_STATE_CONTENT) &amp;&amp; (line_status ==<span style="color: #000000;"> LINE_OK)) 
    </span>|| (line_status = parse_line()) == LINE_OK) {<span style="color: #008000;">//</span><span style="color: #008000;">m_check_state記錄主狀態機當前的狀態</span>
     text =<span style="color: #000000;"> get_line();
     m_start_line </span>=<span style="color: #000000;"> m_checked_idx;
     printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">got 1 http line:%s\n</span><span style="color: #800000;">"</span><span style="color: #000000;">,text);

     </span><span style="color: #0000ff;">switch</span><span style="color: #000000;">(m_check_state){
         </span><span style="color: #0000ff;">case</span> CHECK_STATE_REQUESTLINE:{<span style="color: #008000;">//</span><span style="color: #008000;">分析請求行</span>
             ret =<span style="color: #000000;"> parse_request_line(text);
             </span><span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> BAD_REQUEST){
                 printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">111\n</span><span style="color: #800000;">"</span><span style="color: #000000;">);
                 </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> BAD_REQUEST;
             }
             </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
         }
         </span><span style="color: #0000ff;">case</span> CHECK_STATE_HEADER:{<span style="color: #008000;">//</span><span style="color: #008000;">分析頭部字段</span>
            ret =<span style="color: #000000;"> parse_headers(text);
            </span><span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> BAD_REQUEST){
                printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">222\n</span><span style="color: #800000;">"</span><span style="color: #000000;">);
                </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> BAD_REQUEST;
            }
            </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> GET_REQUEST){
                </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> do_request();
            }
            </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
         }
         </span><span style="color: #0000ff;">case</span> CHECK_STATE_CONTENT:{<span style="color: #008000;">//</span><span style="color: #008000;">分析消息體</span>
             ret =<span style="color: #000000;"> parse_content(text);
             </span><span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> GET_REQUEST){
                 </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> do_request();
             }
             line_status </span>=<span style="color: #000000;"> LINE_OPEN;
             </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
         }
         </span><span style="color: #0000ff;">default</span><span style="color: #000000;">:{
             </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> INTERNAL_ERROR;
         }

     }

}

</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> NO_REQUEST;

}

復制代碼

5 忽略SIGPIPE信號

 SIGPIPE信號默認行為是終止,相應的事件是向一個沒有讀用戶的管道做寫操作。

如果客戶端意外關閉,那么服務器可能也就跟着直接掛了,這顯然不是我們想要的。所以網絡程序中服務端一般會忽略SIGPIPE信號。

SIGPIPE產生的原因為,對方已經關閉了這個socket,但進程還往里面寫。比如壓力測試webbench設置了一個定時器,在正常測試時間會讀取server返回的數據,並正常close;而當測試時間一到就直接close掉所有socket,不會讀server返回的數據,這就導致了server往一個已被對方關閉的socket里寫數據,系統發送了SIGPIPE。

解決方案也非常簡單,把SIGPIPE的信號handler設置為SIG_IGN,忽略該信號即可。

 

但是,我的網絡服務器程序使用了pthread線程庫。而pthread線程庫對於信號的捕獲是逐線程的,上述代碼段的線程和引起SIGPIPE的線程如果不是一個的話,將不起作用。因此,對於pthread線程庫,還需執行下面代碼段:

復制代碼
sigset_t signal_mask;  
sigemptyset (&signal_mask);  
sigaddset (&signal_mask, SIGPIPE);  
int rc = pthread_sigmask (SIG_BLOCK, &signal_mask, NULL);  
if (rc != 0) {  
    printf("block sigpipe error\n");  
}   
復制代碼

 

6 HTTP響應

readv&writev函數有助於提高數據通信效率,功能是對數據進行整合傳輸及發送的函數。服務器端明確阻止Nagle算法能夠提高效率。

通過writev函數可以將分散保存在多個緩沖區中的數據一並發送,通過readv函數可以由多個緩沖區分別接收,適當使用這兩個函數可以減少I/O的調用次數。

復制代碼
#include<sys/uio.h>
ssize_t writev(int filedes,const struct iovec* iov,int iovcnt);
//成功時返回發送的字節數,失敗時返回-1
ssize_t readv(int filedes,const struct iovec* iov,int iovcnt);
//成功時返回接收的字節數,失敗時返回-1
struct iovec{
    void* iov_base;//緩沖地址
    ize_t iov_len;//緩沖大小
}
//iovec表示數組的長度
復制代碼

 

一個HTTP響應的組成是:一個響應行,后面跟隨零個或者更多的響應報頭(response header),在跟隨一個終止報頭的空行,再跟隨一個響應主體。一個響應行的格式是:version status-code status-message。

mmap函數來創建新的虛擬內存空間,並將對象映射到這些區域中。munmap函數刪除虛擬內存的區域。

7 調試

寫完后調試期間出了一些問題,以下是調試過程的總結。

  服務器運行后,使用瀏覽器訪問對應網址總是顯示服務器已經重置,然后自己思考服務器的整個邏輯,先看服務器是否正確解析HTTP請求,收到GET請求,發現parse_request_line解析請求行的函數沒法解析請求,發現是strpbrk函數第二個參數應該寫成空格而不是\t。

然后客戶端還是顯示服務器已經重置,使用curl命令代替瀏覽器,如下命令,顯示

(56) Recv failure: Connection reset by peer

客戶端一直沒有接收到數據!!!而且服務器端顯示404錯誤(我自己填充的http狀態碼)。

  能夠解析請求報文,接下來看響應報文是否正確,響應報文是保存在自己開辟的一個write_buffer空間里面的,通過打印,發現響應報文正確,這時有個誤導,發現響應報文里面connection響應報頭是close,不是keep-alive,就認為是這個connfd已經斷開連接了,然后去找到對應函數一直不關閉瀏覽器連接,發現不是這個問題,因為server的響應報文只是告訴客戶端我想要斷開連接,然后客戶端解析響應報文,經過四次揮手將連接關閉,還有個time-wait保證中間響應不丟失可以重傳,並且我設置了套接字屬性,必須發送完數據套接字才能關閉,所以排除了這個假設。

curl 127.0.0.1:9090

  服務器端請求報文解析和響應報文生成都沒有問題,而客戶端就是沒接收到,下一步我就去定位是不是寫給客戶端的過程中出了問題,一看果然,因為我的程序中服務器需要找網站根目錄然后在找到index.html,但是我的根目錄寫錯了,導致找不到,-_-||,苦逼。不過通過這幾天的調試,進一步熟悉了整個流程。 

8 壓力測試

?
1
 
復制代碼
需要設置虛擬機端口轉發,VMware虛擬機配置端口轉發(端口映射),實現遠程訪問
Webbench能測試處在相同硬件上,不同服務的性能以及不同硬件上同一個服務的運行狀況。webbench的標准測試可以
向我們展示服務器的兩項內容:每秒鍾響應請求數和每秒鍾傳輸數據量。webbench不但能具有標准靜態頁面的測試能力,
還能對動態頁面(ASP,PHP,JAVA,CGI)進行測試的能力。還有就是他支持對含有SSL的安全網站例如電子商務網站進
行靜態或動態的性能測試。
Webbench最多可以模擬3萬個並發連接去測試網站的負載能力。
官方主頁:http://home.tiscali.cz/~cz210552/webbench.html
官方介紹:
Web Bench is very simple tool for benchmarking WWW or proxy servers. Uses fork() 
for simulating multiple clients and can use HTTP/0.9-HTTP/1.1 requests. This benchmark 
is not very realistic, but it can test if your HTTPD can realy handle that many clients
at once (try to run some CGIs) without taking your machine down. Displays pages/min and 
bytes/sec. Can be used in more aggressive mode with -f switch.
1、WebBench安裝:
wget http://www.ha97.com/code/webbench-1.5.tar.gz
tar zxvf webbench-1.5.tar.gz
cd webbench-1.5
make
make install

2、WebBench使用:
webbench
-c 1000 -t 60 http://127.0.0.1:8080/index.html
webbench -c 並發數 -t 運行測試時間 URL

復制代碼

 

瓶頸:

內存,最大文件描述符數目

測試結果

  • 測試工具為Webbench,測試時間為60s,並發數及工作線程數為測試內容。

  • 測試環境為本地,配置4核心i7處理器。

1000並發量

在1K並發量下,並未因線程數增多性能有所提升,相反吞吐量還略微下降並且還有191個failed產生,通常線程數為CPU核心數。在測試中1分鍾內4線程下請求671102個頁面(515 bytes/page),全部成功。另外,在滿負荷情況下TKeed總CPU使用率約40%(10% * 4threads或5% * 8threads)。請求1KB標准頁面時,吞吐量稍有下降。

  • 4工作線程

    • 性能結果

      4worker

    • 系統負載

      4works

  • 8工作線程(結果)

    • 性能結果

      8worker

    • 系統負載

      8works

  • 標准1KB頁面測試

    • 性能結果

      1KB_Page

    •  

 

C10K問題本質?

C10K問題本質是操作系統問題,創建線程多了,數據頻繁拷貝(I/O,內核數據拷貝到用戶進程空間、阻塞),

進程/線程上下文切換消耗大,從而導致操作系統崩潰。

10 為什么使用Reactor模型而不是Proactor模型?

  • Reactor為同步非阻塞模型,Proactor為異步非阻塞模型。核心區別在於I/O具體是由產生I/O的主線程完成還是交給操作系統來完成。

  • 異步I/O需要操作系統支持,因為當前Linux下AIO(異步I/O)還不夠成熟,所以TKeed使用Reactor模型。

  • Reactor模型網絡庫:libevent, libev, libuv。

  • Proactor模型網絡庫:Boost.Asio,IOCP。

 

11 程序代碼

整個邏輯是:main函數只負責讀寫,套接字的創建,讀取數據后,由線程池中的工作線程處理任務,線程池里面包含的元素有:工作隊列,線程,取出一個線程處理一個隊列中的任務,處理的類有封裝的函數,通過從狀態機分析是否讀完一行,然后主狀態機解析請求報文,發送響應報文。

其中注釋掉的內容可以為以后定位服務器故障有一定指導。當然有一些Linux工具還是可以進一步熟悉,比如GDB調試多線程,trace跟蹤。注意C++中用到模板的類,應該將x.h和x.cpp寫到一起。這里為了閱讀效果分開寫的。

//線程同步機制包裝類
#ifndef LOCKER_H
#define LOCKER_H

include<semaphore.h>

include<exception>

include<pthread.h>

/封裝信號量的類/
class sem{
public:
//創建並初始化信號量
sem(){
if(sem_init(&m_sem,0,0) != 0){
//失敗可以通過拋出異常來報告錯誤
throw std::exception();
}
}
//銷毀信號量
~sem(){
sem_destroy(
&m_sem);
}
//等待信號量
bool wait(){
return sem_wait(&m_sem) == 0;
}
//增加信號量
bool post(){
return sem_post(&m_sem) == 0;
}
private:
sem_t m_sem;
};

/封裝互斥鎖的類/
class locker{
public:
//創建並初始化互斥鎖
locker(){
if(pthread_mutex_init(&m_mutex,NULL) != 0){
throw std::exception();
}
}
//銷毀互斥鎖
~locker(){
pthread_mutex_destroy(
&m_mutex);
}
//獲取互斥鎖
bool lock(){
return pthread_mutex_lock(&m_mutex) == 0;
}
//釋放互斥鎖
bool unlock(){
return pthread_mutex_unlock(&m_mutex) == 0;
}
private:
pthread_mutex_t m_mutex;
};
/封裝條件變量的類/
class cond{
public:
//創建並初始化條件變量
cond(){
if(pthread_mutex_init(&m_mutex,NULL) != 0){
throw std::exception();
}
if(pthread_cond_init(&m_cond,NULL) != 0){
//出現問題,立即釋放成功分配的資源
pthread_mutex_destroy(&m_mutex);
throw std::exception();
}
}
//銷毀條件變量
~cond(){
pthread_mutex_destroy(
&m_mutex);
pthread_cond_destroy(
&m_cond);
}
//等待條件變量
bool wait(){
int ret = 0;
pthread_mutex_lock(
&m_mutex);
ret
= pthread_cond_wait(&m_cond,&m_mutex);
pthread_mutex_unlock(
&m_mutex);
return ret == 0;
}
//喚醒等待條件變量的線程
bool singal(){
return (pthread_cond_signal(&m_cond) == 0);
}
private:
pthread_cond_t m_cond;
pthread_mutex_t m_mutex;
};

#endif

locker.h
#ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H

include<unistd.h>

include<signal.h>

include<sys/types.h>

include<sys/epoll.h>

include<fcntl.h>

include<sys/socket.h>

include<netinet/in.h>

include<arpa/inet.h>

include<assert.h>

include<sys/stat.h>

include<string.h>

include<pthread.h>

include<stdio.h>

include<stdlib.h>

include<sys/mman.h>

include<stdarg.h>//可變參數需要的頭文件

include<errno.h>

include"locker.h"

class http_conn
{
public:
static const int FILENAME_LEN = 200;//文件名的最大長度
static const int READ_BUFFER_SIZE = 2048;//讀緩沖區的大小
static const int WRITE_BUFFER_SIZE = 1024;//寫緩沖區的大小
/HTTP請求方法,但我們僅支持GET/
enum METHOD{GET = 0,POST,HEAD,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATCH};
/解析客戶請求時,主狀態機所處的狀態/
enum CHECK_STATE{CHECK_STATE_REQUESTLINE = 0,CHECK_STATE_HEADER,CHECK_STATE_CONTENT};
/服務器處理HTTP請求的可能結果/
enum HTTP_CODE{NO_REQUEST,GET_REQUEST,BAD_REQUEST,NO_RESOURCE,
FORBIDDEN_REQUEST,FILE_REQUEST,INTERNAL_ERROR,CLOSED_CONNECTION};

 </span><span style="color: #008000;">/*</span><span style="color: #008000;">行的讀取狀態</span><span style="color: #008000;">*/</span>   
 <span style="color: #0000ff;">enum</span> LINE_STATUS{LINE_OK = <span style="color: #800080;">0</span><span style="color: #000000;">,LINE_BAD,LINE_OPEN};

public:
http_conn();
~http_conn();

public:
void init(int sockfd,const sockaddr_in& addr);//初始化新接受的連接
void close_conn(bool real_close = true);//關閉連接
void process();//處理客戶請求
bool read();//非阻塞讀操作
bool write();//非阻塞寫操作

private:
void init();//初始化連接
HTTP_CODE process_read();//解析HTTP請求
bool process_write(HTTP_CODE ret);//填充HTTP應答

</span><span style="color: #008000;">//</span><span style="color: #008000;">下面這一組函數被process_read調用以分析HTTP請求</span>
HTTP_CODE parse_request_line(<span style="color: #0000ff;">char</span>*<span style="color: #000000;"> text);
HTTP_CODE parse_headers(</span><span style="color: #0000ff;">char</span>*<span style="color: #000000;"> text);
HTTP_CODE parse_content(</span><span style="color: #0000ff;">char</span>*<span style="color: #000000;"> text);
HTTP_CODE do_request();
</span><span style="color: #0000ff;">char</span>* get_line(){<span style="color: #0000ff;">return</span> m_read_buf +<span style="color: #000000;"> m_start_line;}
LINE_STATUS parse_line();

</span><span style="color: #008000;">//</span><span style="color: #008000;">下面這一組函數被process_write調用以填充HTTP應答</span>
<span style="color: #0000ff;">void</span><span style="color: #000000;"> unmap();
</span><span style="color: #0000ff;">bool</span> add_response(<span style="color: #0000ff;">const</span> <span style="color: #0000ff;">char</span>* format,...);<span style="color: #008000;">//</span><span style="color: #008000;">可以允許參數個數的不確定</span>
<span style="color: #0000ff;">bool</span> add_content(<span style="color: #0000ff;">const</span> <span style="color: #0000ff;">char</span>*<span style="color: #000000;"> content);
</span><span style="color: #0000ff;">bool</span> add_status_line(<span style="color: #0000ff;">int</span> status,<span style="color: #0000ff;">const</span> <span style="color: #0000ff;">char</span>*<span style="color: #000000;"> title);
</span><span style="color: #0000ff;">bool</span> add_headers(<span style="color: #0000ff;">int</span><span style="color: #000000;"> content_length);
</span><span style="color: #0000ff;">bool</span><span style="color: #000000;"> add_linger();
</span><span style="color: #0000ff;">bool</span><span style="color: #000000;"> add_blank_line();

public:
/所有socket上的事件都被注冊到同一個epoll內核事件表中,所以將epoll文件描述符設置為靜態的/
static int m_epollfd;
static int m_user_count;//統計用戶數量

private:
//該HTTP連接的socket和對方的socket地址
int m_sockfd;
sockaddr_in m_address;

</span><span style="color: #0000ff;">char</span> m_read_buf[READ_BUFFER_SIZE];<span style="color: #008000;">//</span><span style="color: #008000;">讀緩沖區</span>
<span style="color: #0000ff;">int</span> m_read_idx;<span style="color: #008000;">//</span><span style="color: #008000;">標識讀緩沖區已經讀入的客戶數據的最后一個字節的下一個位置</span>
<span style="color: #0000ff;">int</span> m_checked_idx;<span style="color: #008000;">//</span><span style="color: #008000;">當前正在分析的字符在讀緩沖區中的位置</span>
<span style="color: #0000ff;">int</span> m_start_line;<span style="color: #008000;">//</span><span style="color: #008000;">當前正在解析的行的初始位置</span>
<span style="color: #0000ff;">char</span> m_write_buf[WRITE_BUFFER_SIZE];<span style="color: #008000;">//</span><span style="color: #008000;">寫緩沖區</span>
<span style="color: #0000ff;">int</span> m_write_idx;<span style="color: #008000;">//</span><span style="color: #008000;">寫緩沖區中待發送的字節數</span>
CHECK_STATE m_check_state;//主狀態機當前所處的狀態 METHOD m_method;//請求方法 //客戶請求的目標文件的完整路徑,其內容等於doc_root+m_url,doc_root是網站根目錄 char m_real_file[FILENAME_LEN]; char* m_url;//客戶請求的目標文件的文件名 char* m_version;//HTTP協議版本號,我們僅支持HTTP/1.1 char* m_host;//主機名 int m_content_length;//HTTP請求的消息體的長度 bool m_linger;//HTTP請求是否要求保持連接
<span style="color: #0000ff;">char</span>* m_file_address;<span style="color: #008000;">//</span><span style="color: #008000;">客戶請求的目標文件被mmap到內存中的起始位置</span>
<span style="color: #0000ff;">struct</span> stat m_file_stat;<span style="color: #008000;">//</span><span style="color: #008000;">目標文件的狀態。通過它我們可以判斷文件是否存在/是否為目錄/是否可讀,並獲得文件大小等信息
</span><span style="color: #008000;">//</span><span style="color: #008000;">我們將采用writev來執行寫操作,所以定義下面兩個成員,其中m_iv_count表示被寫內存塊的數量</span>
<span style="color: #0000ff;">struct</span> iovec m_iv[<span style="color: #800080;">2</span><span style="color: #000000;">];
</span><span style="color: #0000ff;">int</span><span style="color: #000000;"> m_iv_count;

};
#endif

http_conn.h
#include"http_conn.h"

//定義http響應的一些狀態信息
const char* ok_200_title = "OK";
const char* error_400_title = "Bad Request";
const char* error_400_form = "You request has had syntax or is inherently impossible to satisfy.\n";
const char* error_403_title = "Forbidden";
const char* error_403_form = "You do not have permission to get file from this server.\n";
const char* error_404_title = "Not Found";
const char* error_404_form = "The requested file was not found on this server.\n";
const char* error_500_title = "Internal Error";
const char* error_500_form = "There was an unusual problem serving the requested file.\n"
//網站的一些根目錄
const char* doc_root = "/var/www/html";

int setnonblocking(int fd){//將文件描述符設置為非阻塞(邊緣觸發搭配非阻塞)
int old_option = fcntl(fd,F_GETFL);
int new_option = old_option| O_NONBLOCK;
fcntl(fd,F_SETFL,new_option);
return old_option;
}

void addfd(int epollfd,int fd,bool one_shot){//向epoll例程中注冊監視對象文件描述符
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET | EPOLLRDHUB;//注冊三種事件類型
if(one_shot){
event.events |= EPOLLONESHOT;
}
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,
&event);
setnonblocking(fd);
}

void removefd(int epollfd,int fd){//移除並關閉文件描述符
epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,0);
close(fd);
}

void modfd(int epollfd,int fd,int ev){//重置事件,更改文件描述符,可以接受ev對應的讀/寫/異常事件
epoll_event event;
event.data.fd = fd;
event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLEDHUB;
epoll_cet(epollfd,EPOLL_CTL_MOD,fd,
&event);
}

int http_conn :: m_user_count = 0;//用戶數量
int http_conn :: m_epollfd = -1;

void http_conn :: close_conn(bool real_close){
if(real_close && (m_sockfd != -1)){
removefd(m_epollfd,m_sockfd);
m_sockfd
= -1;
m_user_count
--;//關閉一個連接時,將客戶總量減1
}
}

void http_conn :: init(int sockfd,const sockaddr_in& addr){
m_sockfd
= sockfd;
m_address
= addr;
//以下兩行為了避免TIME_WAIT狀態
int reuse = 1;
setsockopt(m
+sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
addfd(m_epollfd,sockfd,
true);
m_user_count
++;

init();

}

void http_conn::init(){
m_check_state
= CHECK_STATE_REQUESTLINE;
m_linger
= false;

m_method </span>=<span style="color: #000000;"> GET;
m_url </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
m_version </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
m_content_length </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
m_host </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
m_start_line </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
m_checked_idx </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
m_read_idx </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
m_write_idx </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
memset(m_read_buf,</span><span style="color: #800000;">'</span><span style="color: #800000;">\0</span><span style="color: #800000;">'</span><span style="color: #000000;">,READ_BUFFER_SIZE);
memset(m_write_buf,</span><span style="color: #800000;">'</span><span style="color: #800000;">\0</span><span style="color: #800000;">'</span><span style="color: #000000;">,WRITE_BUFER_SIZE);
memset(m_real_file,</span><span style="color: #800000;">'</span><span style="color: #800000;">\0</span><span style="color: #800000;">'</span><span style="color: #000000;">,FILENAME_LEN);

}
//從狀態機=>得到行的讀取狀態,分別表示讀取一個完整的行,行出錯,行的數據尚且不完整
http_conn::LINE_STATUS http_conn::parse_line(){
char temp;
/m_checked_idx指向buffer中當前正在分析的字節,m_read_idx指向buffer中客戶數據的尾部的下一字節。
buffer中[0~m_checked_idx - 1]都已經分析完畢,下面的循環分析[m_checked_idx~m_read_idx- 1]的數據
/
for(;m_checked_idx < m_read_idx;++m_checked_idx){
//獲取當前要分析的字節
temp = m_read_buf[m_checked_idx];
//如果當前字符是‘\t’則有可能讀取一行
if(temp == '\r'){
//如果\t是buffer中最后一個已經被讀取的數據,那么當前沒有讀取一個完整的行,還需要繼續讀
if(m_checked_idx + 1 == m_read_idx){
return LINE_OPEN;
}
//如果下一個字節是\n,那么已經讀取到完整的行
else if(m_read_buf[m_checked_idx + 1] == '\n'){
m_read_buf[m_checked_idx
++] = '\0';
m_read_buf[m_checked_idx
++] = '\0';
return LINE_OK;
}
//否則的話,說明客戶發送的HTTP請求存在語法問題
return LINE_BAD;
}
//如果當前的字節是\n,也說明可能讀取到一個完整的行
else if(temp == '\n'){
if(m_checked_idx > 1 && m_read_buf[m_checked_idx - 1] == '\r'){
m_read_buf[m_checked_idx
- 1] = '\0';
m_read_buf[m_checked_idx
++] = '\0';
return LINE_OK;
}
else {
return LINE_BAD;
}
}
}
//如果所有內容分析完畢也沒遇到\t字符,則還需要繼續讀取客戶數據
return LINE_OPEN;
}

//循環讀取客戶數據,直到無數據可讀或者對方關閉連接
bool http_conn::read(){
if(m_read_idx >= READ_BUFFER_SIZE){
return false;
}
while(true){
bytes_read
= recv(m_sockfd,m_read_buf + m_read_idx,READ_BUFFER_SIZE - m_read_idx,0);
if(bytes_read == -1){
if(errno == EAGAIN || errno == EWOULDBLOCK){
break;
}
return false;
}
else if(bytes_read == 0){
return false;
}
m_read_idx
+= bytes_read;
}
return true;
}

//解析HTTP請求行,獲得請求方法,目標URL,以及HTTP版本號
// GET http://www.google.com:80/index.html HTTP/1.1
http_conn :: HTTP_CODE http_conn::parse_request_line(char text){
m_url
= strpbrk(text,"\t");//A找到第一個等於空格或者制表符的下標位置GET
if(!m_url){
return BAD_REQUEST;
}
m_url++ = '\0';//GET\0

<span style="color: #0000ff;">char</span>* method =<span style="color: #000000;"> text;
</span><span style="color: #0000ff;">if</span>(strcasecmp(method,<span style="color: #800000;">"</span><span style="color: #800000;">GET</span><span style="color: #800000;">"</span>) == <span style="color: #800080;">0</span>){<span style="color: #008000;">//</span><span style="color: #008000;">忽略大小寫比較大小</span>
    m_method =<span style="color: #000000;"> GET;
}
</span><span style="color: #0000ff;">else</span><span style="color: #000000;">{
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> BAD_REQUEST;
}

m_url </span>+= strspn(m_url,<span style="color: #800000;">"</span><span style="color: #800000;">\t</span><span style="color: #800000;">"</span>);<span style="color: #008000;">//</span><span style="color: #008000;">去掉GET后面多余空格的影響,找到其中最后一個空格位置</span>
m_version = strpbrk(m_url,<span style="color: #800000;">"</span><span style="color: #800000;">\t</span><span style="color: #800000;">"</span>);<span style="color: #008000;">//</span><span style="color: #008000;">找到url的結束位置html和HTTP中間的空格位置</span>
<span style="color: #0000ff;">if</span>(!<span style="color: #000000;">m_version){
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> BAD_REQUEST;
}
</span>*m_version++ = <span style="color: #800000;">'</span><span style="color: #800000;">\0</span><span style="color: #800000;">'</span>;<span style="color: #008000;">//</span><span style="color: #008000;">html\0HTTP</span>
m_version += strspn(m_version,<span style="color: #800000;">"</span><span style="color: #800000;">\t</span><span style="color: #800000;">"</span>);<span style="color: #008000;">//</span><span style="color: #008000;">去掉中間空格,此時m_version指向H,從狀態機中已經將每行的結尾設置為\0\0</span>
<span style="color: #0000ff;">if</span>(strcasecmp(m_version,<span style="color: #800000;">"</span><span style="color: #800000;">HTTP/1.1</span><span style="color: #800000;">"</span>) != <span style="color: #800080;">0</span>){<span style="color: #008000;">//</span><span style="color: #008000;">僅支持HTTP/1.1</span>
    <span style="color: #0000ff;">return</span><span style="color: #000000;"> BAD_REQUEST;
}
</span><span style="color: #0000ff;">if</span>(strncmp(m_url,<span style="color: #800000;">"</span><span style="color: #800000;">HTTP://</span><span style="color: #800000;">"</span>,<span style="color: #800080;">7</span>) == <span style="color: #800080;">0</span>){<span style="color: #008000;">//</span><span style="color: #008000;">檢查url是否合法</span>
    m_url += <span style="color: #800080;">7</span><span style="color: #000000;">;
    m_url </span>= strchr(m_url,<span style="color: #800000;">'</span><span style="color: #800000;">/</span><span style="color: #800000;">'</span>);<span style="color: #008000;">//</span><span style="color: #008000;">找/index中的/        </span>

}

</span><span style="color: #0000ff;">if</span>(!m_url || m_url[<span style="color: #800080;">0</span>] != <span style="color: #800000;">'</span><span style="color: #800000;">/</span><span style="color: #800000;">'</span>){<span style="color: #008000;">//</span><span style="color: #008000;">記住URL后綴是/</span>
    <span style="color: #0000ff;">return</span><span style="color: #000000;"> NO_REQUEST;
}
</span><span style="color: #008000;">//</span><span style="color: #008000;">HTTP請求行處理完畢,狀態轉移到頭部字段的分析</span>
m_check_state =<span style="color: #000000;"> CHECK_STATE_HEADER;
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> NO_REQUEST;

}

//分析頭部字段
//HTTP請求的組成是一個請求行,后面跟隨0個或者多個請求頭,最后跟隨一個空的文本行來終止報頭列表
HTTP_CODE parse_headers(char* temp){
//遇到空行,說明頭部字段解析完畢
if(text[0] == '\0'){
//如果HTTP請求有消息體,則還需要讀取m_content_length字節的消息體,狀態機轉移到CHECK_STATE_CONTENT狀態
if(m_content_length != 0){
m_check_state
= CHECK_STATE_CONTENT;
return NO_REQUEST;
}
//否則說明我們得到一個完整的HTTP請求
return GET_REQUEST;
}
//下面處理多種請求報頭
//處理Connection頭部字段
else if(strncasecmp(text,"Connection:",11) == 0){
text
+= 11;
text
+= strspn(text,"\t");
if(strcasecmp(text,"keep-alive") == 0){
m_linger
= true;
}
}
//處理content-length頭部字段
else if(strcasecmp(text,"Content-Length:",15) == 0){
text
+= 15;
text
+= strspn(text,"\t");
m_content_length
= atol(text);
}
//處理Host頭部信息
else if(strncasecmp(text,"Host:",5) == 0){
text
+= 5;
text
+= strspn(text,"\t");
m_host
= text;
}
else{
printf(
"oop!unkonwn header %s\n",text);
}

</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> NO_REQUEST;

}

//我們沒有真正的解析HTTP請求的消息體,只是判斷它是否被完整的讀入了
http_conn::HTTP_CODE http_conn::parse_content(char* text){
if(m_read_idx >= (m_content_length + m_checked_idx)){
text[m_content_length]
= '\0';
return GET_REQUEST;
}

</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> NO_REQUEST;

}

//主狀態機,用於從buffer中取出所有完整的行
http_conn::HTTP_CODE http_conn::process_read(){
LINE_STATUS line_status
= LINE_OK;//記錄當前行的讀取狀態
HTTP_CODE ret = NO_REQUEST;//記錄HTTP請求的處理結果
char* text = 0;

</span><span style="color: #0000ff;">while</span>(((m_check_state == CHECK_STATE_CONTENT) &amp;&amp; (line_status ==<span style="color: #000000;"> LINE_OK)) 
    </span>|| (line_status = parse_line()) == LINE_OK) {<span style="color: #008000;">//</span><span style="color: #008000;">m_check_state記錄主狀態機當前的狀態</span>
     text =<span style="color: #000000;"> get_line();
     m_start_line </span>=<span style="color: #000000;"> m_checked_idx;
     printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">got 1 http line:%s\n</span><span style="color: #800000;">"</span><span style="color: #000000;">,text);

     </span><span style="color: #0000ff;">switch</span><span style="color: #000000;">(m_check_state){
         </span><span style="color: #0000ff;">case</span> CHECK_STATE_REQUEST:{<span style="color: #008000;">//</span><span style="color: #008000;">分析請求行</span>
             ret =<span style="color: #000000;"> parse_request_line(text);
             </span><span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> BAD_REQUEST){
                 </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> BAD_REQUEST;
             }
             </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
         }
         </span><span style="color: #0000ff;">case</span> CHECK_STATE_HEADER:{<span style="color: #008000;">//</span><span style="color: #008000;">分析頭部字段</span>
            ret =<span style="color: #000000;"> parse_header(text);
            </span><span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> BAD_REQUEST){
                </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> BAD_REQUEST;
            }
            </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> GET_REQUEST){
                </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> do_request();
            }
            </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
         }
         </span><span style="color: #0000ff;">case</span> CHECK_STATE_CONTENT:{<span style="color: #008000;">//</span><span style="color: #008000;">分析消息體</span>
             ret =<span style="color: #000000;"> parse_content(text);
             </span><span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> GET_REQUEST){
                 </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> do_request();
             }
             line_status </span>=<span style="color: #000000;"> LINE_OPEN;
             </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
         }
         </span><span style="color: #0000ff;">default</span><span style="color: #000000;">:{
             </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> INTERNAL_ERROR;
         }

     }

}

</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> NO_REQUEST;

}

/當得到一個完整正確的HTTP請求時,我們就分析目標文件的屬性。如果目標文件存在,
對所有用戶可讀,且不是目錄,則使用mmap將其映射內存地址m_file_address處,並告訴調用者獲取文件成功
/
http_conn::HTTP_CODE http_conn::do_request(){
strcpy(m_real_file,doc_root);
int len = strlen(doc_root);
//m_real_file客戶請求的目標文件的完整路徑,其內容等於doc_root + m_url,doc_root是網站根目錄
strncpy( m_real_file + len,m_url,FILENAME_LEN - len - 1);
if(stat(m_real_file,&m_file_stat)){//獲取文件的狀態並保存在m_file_stat中
return NO_RESOURCE;
}
if(!(m_file_stat.st_mode & S_TROTH)){
return FORBIDDEN_REQUEST;
}
if(S_ISDIR(m_file_stat.st_mode)){
return BAD_REQUEST;
}

</span><span style="color: #0000ff;">int</span> fd =<span style="color: #000000;"> open(m_real_file,O_RDONLY);
</span><span style="color: #008000;">//</span><span style="color: #008000;">創建虛擬內存區域,並將對象映射到這些區域</span>
m_file_address = (<span style="color: #0000ff;">char</span>*)mmap(<span style="color: #800080;">0</span>,m_file_stat.st_size,PROT_READ,MAP_PRIVATE,fd,<span style="color: #800080;">0</span><span style="color: #000000;">);
close(fd);
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> FILE_REQUEST;

}

//對內存映射區執行munmap操作
void http_conn::unmap(){
if(m_file_address){
munmap(m_file_address,m_file_stat.st_size);
//刪除虛擬內存的區域
m_file_address = 0
}
}

//寫HTTP響應
bool http_conn::write(){
int temp = 0;
int bytes_have_send = 0;
int bytes_to_send = m_write_idx;
if(bytes_to_send == 0){
modfd(m_epollfd,m_sockfd,EPOLLIN);
init();
return true;
}

</span><span style="color: #0000ff;">while</span>(<span style="color: #800080;">1</span><span style="color: #000000;">){
    temp </span>=<span style="color: #000000;"> writev(m_sockfd,m_iv,m_iv_count);
    </span><span style="color: #0000ff;">if</span>(temp &lt;= -<span style="color: #800080;">1</span><span style="color: #000000;">){
    </span><span style="color: #008000;">//</span><span style="color: #008000;">如果TCP寫緩存沒有空間,則等待下一輪EPOLLOUT事件。雖然在此期間,服務器無法立即接收到同一客戶的下一個請求,但是可以保證連接的完整性</span>
        <span style="color: #0000ff;">if</span>(errno == EAGAIN){<span style="color: #008000;">//</span><span style="color: #008000;">當前不可寫</span>

modfd(m_epollfd,m_sockfd,EPOLLOUT);
return true;
}
unmap();
return false;
}

    bytes_to_send </span>-=<span style="color: #000000;"> temp;
    bytes_have_send </span>+=<span style="color: #000000;"> temp;
    </span><span style="color: #0000ff;">if</span>(nytes_to_send &lt;=<span style="color: #000000;"> bytes_have_send){
        </span><span style="color: #008000;">//</span><span style="color: #008000;">發送HTTP響應成功,根據HTTP請求中的Connection字段決定是否立即關閉連接</span>

unmap();
if(m_linger){
init();
modfd(m_epollfd,m_sockfd,EPOLLIN);
return true;
}
else{
modfd(m_epollfd,m_sockfd,EPOLLIN);
return false;
}
}
}
}

//往寫緩沖區寫入待發送的數據
bool http_conn::add_response(const char*format,...){
if(m_write_idx >= WRITE_BUFFER_SIZE){
return false;
}
va_list arg_list;
va_start(arg_list,format);
//將可變參數格式化輸出到一個字符數組
int len = vsnprintf(m_write_buf + m_write_idx,WRITE_BUFFER_SIZE - 1 - m_write_idx,format,arg_list);
if(len >= (WRITE_BUFFER_SIZE - 1 - m_write_idx)){
return false;
}
m_write_idx
+= len;
va_end(arg_list);
return true;
}

bool http_conn::add_status_line(int status,const char* title){
return add_response("%s %d %s\r\n","HTTP/1.1",status,title);
}

bool http_conn::add_headers(int content_len){//頭部就三種信息
add_content_length(content_len);//內容長度
add_linger();//客戶連接信息
add_blank_line();//空行
}

bool http_conn::add_content_length(int content_len){
return add_response("Content-Length: %d\r\n",content_len);
}

bool http_conn::add_linger(){
return add_response("Connection: %s\r\n",(m_linger == true) ? "keep-alive" : "close");
}

bool http_conn::add_blank_line(){
return add_response("%s","\r\n");
}

bool http_conn::add_content(const char* content){
return add_response("%s",content);
}

//根據服務器處理HTTP請求的結果,決定返回給客戶端的內容
bool http_conn::process_write(HTTP_CODE ret){
switch(ret){
case INTERNAL_ERROR:{
add_status_line(
500,error_500_title);
add_headers(strlen(error_500_form));
if(!add_content(error_500_form)){
return false;
}
break;
}
case BAD_REQUEST:{
add_status_line(
400,error_400_title);
add_headers(strlen(error_400_form));
if(!add_content(error_400_form)){
return false;
}
break;
}
case NO_RESOURCE:{
add_status_line(
404,error_404_title);
add_headers(strlen(error_404_form));
if(!add_content(error_404_form)){
return false;
}
break;
}
case FORBIDDEN_REQUEST:{
add_status_line(
403,error_403_title);
add_headers(strlen(error_403_form));
if(!add_content(error_403_form)){
return false;
}
break;
}
case FILE_REQUEST:{
add_status_line(
200,ok_200_title);
if(m_file_stat.st_size != 0){//st_size表示文件的大小
add_headers(m_file_stat.st_size);
m_iv[
0].iov_base = m_write_buf;
m_iv[
0].iov_len = m_write_idx;
m_iv[
1].iov_base = m_file_address;
m_iv[
1].iov_len = m_file_stat.st_size;
m_iv_count
= 2;
return true;
}
else{
const char* ok_string = "<html><body></body></html>";
add_headers(strlen(strlen(ok_string)));
if(!add_content(ok_string)){
return false;
}
}
}
default:{
return false;
}
m_iv[
0].iov_base = m_write_buf;
m_iv[
0].iov_len = m_write_idx;
m_iv_count
= 1;
return true;
}
}

//有線程池中的工作線程調用,這是處理HTTP請求的入口函數
void http_conn::process(){
HTTP_CODE read_ret
= process_read();
if(read_ret == NO_REQUEST){
modfd(m_epollfd,m_sockfd,EPOLLIN);
return;
}

</span><span style="color: #0000ff;">bool</span> write_ret =<span style="color: #000000;"> process_write(read_ret);
</span><span style="color: #0000ff;">if</span>(!<span style="color: #000000;">write_ret){
     close_conn();
}
modfd(m_epollfd,m_sockfd,EPOLLOUT);

}

http_conn.cpp
#ifndef THREADPOOL_H
#define THREADPOOL_H

include<list>

include<cstdio>

include<exception>

include<pthread.h>

include"locker.h"

/線程池類,把它定義為模板類是為了代碼復用,模板參數T是任務類/
template
<typename T>
class threadpool{
public:
/參數thread_number是線程池中線程的數量,max_requests是請求隊列中最多允許的,等待處理的請求的數量/
threadpool(
int thread_number = 8,int max_requests = 1000);
~threadpool();
//往請求隊列中添加任務
bool append(T* request);

private:
//工作線程運行的函數,它不斷從工作隊列中取出任務並執行之
static void* worker(void* arg);
void run();

private:
int m_thread_number;//線程池中的線程數
int m_max_requests; //請求隊列中允許的最大請求數
pthread_t* m_threads;//描述線程池的數組,其大小為m_thread_number
std::list<T*> m_workqueue;//請求隊列
locker m_queuelocker;//保護請求隊列的互斥鎖
sem m_queuestat;//是否有任務需要處理
bool m_stop;//是否結束線程
};
template
<typename T>
threadpool
<T>::threadpool(int thread_number,int max_requests):
m_thread_number(thread_number),m_max_requests(max_requests),
m_stop(
false),m_threads(NULL)

{
if(thread_number <= 0 || max_requests <= 0){
throw std::exception();
}
m_threads
= new pthread_t[m_thread_number];
if(!m_threads){
throw std::exception();
}
//創建thread_number個線程,並設置為分離線程
for(int i = 0;i < thread_number;++i){
printf(
"create the %dth thread\n",i + 1);
if(pthread_create(&m_threads[i],NULL,worker,this) != 0){
delete [] m_threads;
throw std::exception();
}
if(pthread_detach(m_threads[i]) != 0){
delete [] m_threads;
throw std::exception();
}
}

}

template<typename T>
threadpool
<T> :: ~threadpool(){
delete [] m_threads;
throw std:: exception();
}

template<typename T>
bool threadpool<T>::append(T request){
/
操作工作隊列前一定要加鎖,因為它被所有工作隊列共享*/
m_queuelocker.
lock();
if(m_workqueue.size() > m_max_requests){
m_queuelocker.unlock();
return false;
}
m_workqueue.push_back(request);
m_queuelocker.unlock();
m_queuestat.post();
//保證隊列非空
return true;
}

template<typename T>
void* threadpool<T> :: worker(void* arg){//傳入的是this
threadpool* pool = (threadpool)arg;//將void×指針轉化為threadpool指針
pool -> run();//就是下面實現的run函數
return pool;
}

template<typename T>
void threadpool<T>::run(){//消費者
while(!m_stop){
m_queuestat.wait();
//執行p操作
m_queuelocker.lock();//對工作隊列操作錢加鎖
if(m_workqueue.empty()){
m_queuelocker.unlock();
continue;
}
T
* request = m_workqueue.front();
m_workqueue.pop_front();
m_queuelocker.unlock();
if(!request){
continue;
}
request
-> process();//任務中要有process處理函數
}
}

#endif

threadpool.h
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
#include<stdlib.h>
#include<cassert>
#include<sys/epoll.h>

include"locker.h"

include"threadpool.h"

include"http_conn1.h"

#define MAX_FD 65536
#define MAX_EVENT_NUMBER 10000

extern void addfd(int epollfd,int fd,bool one_shot);
extern void removefd(int epollfd,int fd);

void addsig(int sig,void(handler)(int),bool restart = true){
struct sigaction sa;
memset(
&sa,'\0',sizeof(sa));
sa.sa_handler
= handler;
if(restart){
sa.sa_flags
|= SA_RESTART;
}
sigfillset(
&sa.sa_mask);
assert(sigaction(sig,
&sa,NULL) != -1);
}

void show_error(int connfd,const char* info){
send(connfd,info,strlen(info),
0);
close(connfd);
}

int main(int argc,char argv[]){
if(argc <= 2){//argv[0]可執行文件名/main,argv[1]IP地址,argv[2]是端口號
printf("usage: %s ip_address port_number\n",basename(argv[0]));//最后一個/的字符串內容
return 1;
}
const char
ip = argv[1];
char* port = argv[2];

</span><span style="color: #008000;">//</span><span style="color: #008000;">忽略SIGPIPE信號</span>
addsig(SIGPIPE,SIG_IGN);<span style="color: #008000;">//</span><span style="color: #008000;">SIG_IGN表示忽略SIGPIPE那個注冊的信號。

</span><span style="color: #008000;">//</span><span style="color: #008000;">創建線程池</span>
threadpool&lt;http_conn&gt;* pool =<span style="color: #000000;"> NULL;
</span><span style="color: #0000ff;">try</span>{<span style="color: #008000;">//</span><span style="color: #008000;">這里的語句有任何異常就執行下面的return</span>
    pool = <span style="color: #0000ff;">new</span> threadpool&lt;http_conn&gt;<span style="color: #000000;">;        
}
</span><span style="color: #0000ff;">catch</span><span style="color: #000000;">(...){
    </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">1</span><span style="color: #000000;">;
}

</span><span style="color: #008000;">//</span><span style="color: #008000;">預先為每個可能的客戶連接分配一個http_conn對象</span>
http_conn* users = <span style="color: #0000ff;">new</span><span style="color: #000000;"> http_conn[MAX_FD];
assert(users);
</span><span style="color: #0000ff;">int</span> user_count = <span style="color: #800080;">0</span><span style="color: #000000;">;

</span><span style="color: #0000ff;">int</span> listenfd = socket(PF_INET,SOCK_STREAM,<span style="color: #800080;">0</span><span style="color: #000000;">);
assert(listenfd </span>&gt;= <span style="color: #800080;">0</span><span style="color: #000000;">);
</span><span style="color: #0000ff;">struct</span> linger tmp = {<span style="color: #800080;">1</span>,<span style="color: #800080;">0</span>};<span style="color: #008000;">//</span><span style="color: #008000;">1表示還有數據沒發送完畢的時候容許逗留,0表示逗留時間</span>
setsockopt(listenfd,SOL_SOCKET,SO_LINGER,&amp;tmp,<span style="color: #0000ff;">sizeof</span>(tmp));<span style="color: #008000;">//</span><span style="color: #008000;">即讓沒發完的數據發送出去后在關閉socket</span>

<span style="color: #0000ff;">int</span> ret = <span style="color: #800080;">0</span><span style="color: #000000;">;
</span><span style="color: #0000ff;">struct</span><span style="color: #000000;"> sockaddr_in address;
bzero(</span>&amp;address,<span style="color: #0000ff;">sizeof</span><span style="color: #000000;">(address));
address.sin_family </span>=<span style="color: #000000;"> AF_INET;
</span><span style="color: #008000;">//</span><span style="color: #008000;">address.sin_addr.s_addr = htonl(ip);</span>
inet_aton(ip,&amp;<span style="color: #000000;">address.sin_addr);
address.sin_port </span>=<span style="color: #000000;"> htons(atoi(port));

ret </span>= bind(listenfd,(<span style="color: #0000ff;">struct</span> sockaddr*)&amp; address,<span style="color: #0000ff;">sizeof</span><span style="color: #000000;">(address));
assert(ret </span>&gt;= <span style="color: #800080;">0</span><span style="color: #000000;">);

ret </span>= listen(listenfd,<span style="color: #800080;">5</span><span style="color: #000000;">);
epoll_event events[MAX_EVENT_NUMBER];
</span><span style="color: #0000ff;">int</span> epollfd = epoll_create(<span style="color: #800080;">5</span><span style="color: #000000;">);
assert(epollfd </span>!= -<span style="color: #800080;">1</span><span style="color: #000000;">);
addfd(epollfd,listenfd,</span><span style="color: #0000ff;">false</span><span style="color: #000000;">);
http_conn::m_epollfd </span>=<span style="color: #000000;"> epollfd;

</span><span style="color: #0000ff;">while</span>(<span style="color: #0000ff;">true</span><span style="color: #000000;">){
    </span><span style="color: #0000ff;">int</span> number = epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-<span style="color: #800080;">1</span><span style="color: #000000;">);
    </span><span style="color: #0000ff;">if</span>((number &lt; <span style="color: #800080;">0</span>) &amp;&amp; (errno !=<span style="color: #000000;"> EINTR)){
        printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">epoll failure\n</span><span style="color: #800000;">"</span><span style="color: #000000;">);
        </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
    }
    
    </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i = <span style="color: #800080;">0</span>;i &lt; number;++<span style="color: #000000;">i){
        </span><span style="color: #0000ff;">int</span> sockfd =<span style="color: #000000;"> events[i].data.fd;
        </span><span style="color: #0000ff;">if</span>(sockfd ==<span style="color: #000000;"> listenfd){
            </span><span style="color: #0000ff;">struct</span><span style="color: #000000;"> sockaddr_in client_address;
            socklen_t client_addrlength </span>= <span style="color: #0000ff;">sizeof</span><span style="color: #000000;">(client_address);                
            </span><span style="color: #0000ff;">int</span> connfd = accept(listenfd,(<span style="color: #0000ff;">struct</span> sockaddr*)&amp; client_address,&amp;<span style="color: #000000;">client_addrlength);
            </span><span style="color: #0000ff;">if</span>(connfd &lt; <span style="color: #800080;">0</span><span style="color: #000000;">){
                printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">error is: %d\n</span><span style="color: #800000;">"</span><span style="color: #000000;">,errno);
                </span><span style="color: #0000ff;">continue</span><span style="color: #000000;">;
            }
            </span><span style="color: #0000ff;">if</span>(http_conn::m_user_count &gt;=<span style="color: #000000;"> MAX_FD){
                show_error(connfd,</span><span style="color: #800000;">"</span><span style="color: #800000;">Internal server busy</span><span style="color: #800000;">"</span><span style="color: #000000;">);
                </span><span style="color: #0000ff;">continue</span><span style="color: #000000;">;
            }
            </span><span style="color: #008000;">//</span><span style="color: #008000;">初始化客戶連接</span>

users[connfd].init(connfd,client_address);
printf(
"sock_close\n");
}
else if(events[i].events & (EPOLLRDHUP | EPOLLHUP |EPOLLERR)){
//如果有異常,直接關閉客戶連接
printf("sock_exception_close\n");
users[sockfd].close_conn();
}
else if(events[i].events & EPOLLIN){
//根據讀的結果,決定將任務添加到線程池,還是關閉連接
if(users[sockfd].read()){
pool
->append(users + sockfd);
}
else{
printf(
"sock_read_close\n");
users[sockfd].close_conn();
}
}
else if(events[i].events & EPOLLOUT){
//根據寫的結果,決定是否關閉連接
printf("write_main\n");
if(!users[sockfd].write()){
printf(
"sock_write_close\n");
users[sockfd].close_conn();
}
}
else
{printf(
"close\n");}
}

}

close(epollfd);
close(listenfd);
</span><span style="color: #0000ff;">delete</span><span style="color: #000000;"> [] users;
</span><span style="color: #0000ff;">delete</span><span style="color: #000000;"> pool;
</span><span style="color: #0000ff;">return</span> <span style="color: #800080;">0</span><span style="color: #000000;">;

}

main.cpp

 

12 參考資料

[1]. 游雙. Linux高性能服務器編程[M]. 機械工業出版社, 2013.

[2]. 如何寫一個Web服務器 http://lifeofzjs.com/blog/2015/05/16/how-to-write-a-server/

[3]. 尹聖雨. TCP/IP網絡編程[M]. 人民郵電出版社, 2014.

[4]. Randal E.Bryant.深入理解計算機系統[M].機械工業出版社,2016.

 

<div id="blog_post_info">
0
0
<div class="clear"></div>
<div id="post_next_prev">

<a href="https://www.cnblogs.com/dingxiaoqiang/p/7881706.html" class="p_n_p_prefix">« </a> 上一篇:    <a href="https://www.cnblogs.com/dingxiaoqiang/p/7881706.html" title="發布於 2017-11-22 22:36">6旋轉數組的最小數字</a>
<br>
<a href="https://www.cnblogs.com/dingxiaoqiang/p/7905721.html" class="p_n_p_prefix">» </a> 下一篇:    <a href="https://www.cnblogs.com/dingxiaoqiang/p/7905721.html" title="發布於 2017-11-27 19:37">7斐波那契數列</a>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM