Tinywebserver:一個簡易的web服務器

這是學習網絡編程後寫的一個練手的小程序,能夠幫助複習I/O模型,epoll使用,線程池,HTTP協議等內容。html

程序代碼是基於《Linux高性能服務器編程》一書編寫的。c++

首先回顧程序中的核心內容和主要問題,最後給出相關代碼。web

0. 功能和I/O模型編程

實現簡易的HTTP服務端,現僅支持GET方法,經過瀏覽器訪問能夠返回相應內容。小程序

I/O模型採用Reactor(I/O複用 + 非阻塞I/O) + 線程池。 使用epoll事件循環用做事件通知,若是listenfd上可讀,則調用accept,把新建的fd加入epoll中;數組

是已鏈接sockfd,將其加入到線程池中由工做線程競爭執行任務。瀏覽器

 

1. 線程池怎麼實現?服務器

程序採用c++編寫,要本身封裝一個簡易的線程池類。大體思路是建立固定數目的線程(如跟核數相同),而後類內部維護一個生產者—消費者隊列。網絡

提供相應的添加任務(生產者)和執行任務接口(消費者)。按照操做系統書中典型的生產者—消費者模型維護增減隊列任務(使用mutex和semaphore)。併發

mutex用於互斥,保證任意時刻只有一個線程讀寫隊列,semaphore用於同步,保證執行順序(隊列爲空時不要讀,隊列滿了不要寫)。

 

2. epoll用條件觸發(LT)仍是邊緣觸發(ET)?

考慮這樣的狀況,一個工做線程在讀一個fd,但沒有讀完。若是採用LT,則下一次事件循環到來的時候,又會觸發該fd可讀,此時線程池頗有可能將該fd分配給其餘的線程處理數據。

這顯然不是咱們想要看到的,而ET則不會在下一次epoll_wait的時候返回,除非讀完之後又有新數據才返回。因此這裏應該使用ET。

固然ET用法在《Tinychatserver: 一個簡易的命令行羣聊程序》也有總結過。用法的模式是固定的,把fd設爲nonblocking,若是返回某fd可讀,循環read直到EAGAIN。

 

3. 繼續上面的問題,若是某個線程在處理fd的同時,又有新的一批數據發來(不是老數據沒讀完,是來新數據了),即便使用了ET模式,由於新數據的到來,仍然會觸發該fd可讀,因此仍然存在將該fd分給其餘線程處理的狀況。

這裏就用到了EPOLLONESHOT事件。對於註冊了EPOLLONESHOT事件的文件描述符,操做系統最大觸發其上註冊的一個可讀、可寫或者異常事件,且只觸發一次。

除非咱們使用epoll_ctl函數重置該文件描述符上註冊的EPOLLONESHOT事件。這樣,當一個線程處理某個socket時,其餘線程是不可能有機會操做該socket的,

便可解決該問題。但同時也要注意,若是註冊了EPOLLONESHOT的socket一旦被某個線程處理完畢,則應該當即重置這個socket上的EPOLLONESHOT事件,

以確保下一次可讀時,其EPOLLIN事件可以觸發。

 

4. HTTP協議解析怎麼作?數據讀到一半怎麼辦?

首先理解這個問題。HTTP協議並未提供頭部字段的長度,判斷頭部結束依據是遇到一個空行,該空行只包含一對回車換行符(<CR><LF>)。同時,若是一次讀操做沒有讀入整個HTTP請求

的頭部,咱們必須等待用戶繼續寫數據再次讀入(好比讀到 GET /index.html HTT就結束了,必須維護這個狀態,下一次必須繼續讀‘P’)。

即咱們須要斷定當前解析的這一行是什麼(請求行?請求頭?消息體?),還須要判斷解析一行是否結束?

解決上述問題,能夠採起有限狀態機。

參考【1】中設計方式,設計主從兩個狀態機(主狀態機解決前半部分問題,從狀態機解決後半部分問題)。

先分析從狀態機,從狀態機用於處理一行信息(即parse_line函數)。其中包括三個狀態:LINE_OPEN, LINE_OK,LINE_BAD,轉移過程以下所示:

當從狀態機parse_line讀到完整的一行,就能夠將改行內容遞交給process_read函數中的主狀態機處理。

 

主狀態機也有三種狀態表示正在分析請求行(CHECK_STATE_REQUESTINE),正在分析頭部字段(CHECK_STATE_HEADER),和正在分析內容(CHECK_CONTENT)。

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

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

若是當前的狀態是CHECK_STATE_HEADER,則表示parse_line函數解析出來的是頭部字段,因而主狀態機調用parse_header來分析頭部字段。

若是當前狀態是CHECK_CONTENT,則表示parse_line函數解析出來的是消息體,咱們調用parse_content來分析消息體(實際上實現時候並無分析,只是判斷是否完整讀入)

checkstate變量的初始值是CHECK_STATE_REQUESTLINE,調用相應的函數(parse_requestline,parse_header)後更新checkstate實現狀態轉移。

與主狀態機有關的核心函數以下:

http_conn::HTTP_CODE http_conn::process_read()//完整的HTTP解析
{
    LINE_STATUS line_status = LINE_OK;
    HTTP_CODE ret = NO_REQUEST;
    char* text = 0;

    while ( ( ( m_check_state == CHECK_STATE_CONTENT ) && ( line_status == LINE_OK  ) )
                || ( ( line_status = parse_line() ) == LINE_OK ) ){//知足條件:正在進行HTTP解析、讀取一個完整行
        text = get_line();//從讀緩衝區(HTTP請求數據)獲取一行數據
        m_start_line = m_checked_idx;//行的起始位置等於正在每行解析的第一個字節
        printf( "got 1 http line: %s", text );

        switch ( m_check_state )//HTTP解析狀態跳轉
        {
            case CHECK_STATE_REQUESTLINE://正在分析請求行
            {
                ret = parse_request_line( text );//分析請求行
                if ( ret == BAD_REQUEST )
                {
                    return BAD_REQUEST;
                }
                break;
            }
            case CHECK_STATE_HEADER://正在分析請求頭部
            {
                ret = parse_headers( text );//分析頭部
                if ( ret == BAD_REQUEST )
                {
                    return BAD_REQUEST;
                }
                else if ( ret == GET_REQUEST )
                {
                    return do_request();//當得到一個完整的鏈接請求則調用do_request分析處理資源頁文件
                }
                break;
            }
            case CHECK_STATE_CONTENT:// 解析消息體
            {
                ret = parse_content( text );
                if ( ret == GET_REQUEST )
                {
                    return do_request();
                }
                line_status = LINE_OPEN;
                break;
            }
            default:
            {
                return INTERNAL_ERROR;//內部錯誤
            }
        }
    }

    return NO_REQUEST;
}

 

5. HTTP響應怎麼作?怎麼發送效率高一些?

首先介紹readv和writev函數。其功能能夠簡單歸納爲對數據進行整合傳輸及發送,即所謂分散讀,集中寫

也就是說,writev函數能夠把分散保存在多個緩衝中的數據一併發送,經過readv函數能夠由多個緩衝分別接收。所以適當採用這兩個函數能夠減小I/O次數。

例如這裏要作的HTTP響應。其包含一個狀態行,多個頭部字段,一個空行和文檔的內容。前三者可能被web服務器放置在一塊內存中,

而文檔的內容則一般被讀入到另一塊單獨的內存中(經過read函數或mmap函數)。這裏能夠採用writev函數將他們一併發出。

相關接口以下:

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

其中第二個參數爲以下結構體的數組
struct iovec {
    void  *iov_base;    /* Starting address */
    size_t iov_len;     /* Number of bytes to transfer */
};
第三個參數爲第二個參數的傳遞的數組的長度。

這裏還能夠再學習一下mmap與munmap函數。可是這裏關於mmap與read效率的比較,應該沒有那麼簡單的答案。mmap能夠減小系統調用和內存拷貝,可是其引起的pagefault也是開銷。效率的比較取決於不一樣系統對於這兩個效率實現的不一樣,因此這裏就簡單談一談用法。

#include <sys/mman.h>
/**addr參數容許用戶使用某個特定的地址做爲這段內存的起始地址,設置爲NULL則自動分配地址。
*length參數指定內存段的長度.
*prot參數用來設置內*存段的訪問權限,好比PROT_READ可讀, PROT_WRITE可寫。
*flags控制內存段內容被修改後程序的行爲。如MAP_PRIVATE指內存段爲調用進程所私有,對該內存段的修改不會反映到被映射的文件中。
*/
void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);
int munmap(void *addr, size_t length);

因此根據不一樣狀況(200,404)填充HTTP的程序以下:

bool http_conn::process_write( HTTP_CODE ret )//填充HTTP應答
{
    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 )
            {
                add_headers( m_file_stat.st_size );//m_file_stat資源頁文件狀態
                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_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( 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應答
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 );//EPOLLONESHOT事件每次須要重置事件
        init();
        return true;
    }

    while( 1 )//
    {
        temp = writev( m_sockfd, m_iv, m_iv_count );//集中寫,m_sockfd是http鏈接對應的描述符,m_iv是iovec結構體數組表示內存塊地址,m_iv_count是數組的長度即多少個內存塊將一次集中寫到m_sockfd
        if ( temp <= -1 )//集中寫失敗
        {
            if( errno == EAGAIN )
            {
                modfd( m_epollfd, m_sockfd, EPOLLOUT );//重置EPOLLONESHOT事件,註冊可寫事件表示若m_sockfd沒有寫失敗則關閉鏈接
                return true;
            }
            unmap();//解除內存映射
            return false;
        }

        bytes_to_send -= temp;//待發送數據
        bytes_have_send += temp;//已發送數據
        if ( bytes_to_send <= bytes_have_send )
        {
            unmap();//該資源頁已經發送完畢該解除映射
            if( m_linger )//若要保持該http鏈接
            {
                init();//初始化http鏈接
                modfd( m_epollfd, m_sockfd, EPOLLIN );
                return true;
            }
            else
            {
                modfd( m_epollfd, m_sockfd, EPOLLIN );
                return false;
            } 
        }
    }
}
將應答發送給客戶端

 

6.忽略SIGPIPE

這是一個看似很小,可是若是不注意會直接引起bug的地方。若是往一個讀端關閉的管道或者socket中寫數據,會引起SIGPIPE,程序收到SIGPIPE信號後默認的操做時終止進程。

這也就是說,若是客戶端意外關閉,那麼服務器可能也就跟着直接掛了,這顯然不是咱們想要的。因此網絡程序中服務端通常會忽略SIGPIPE信號

 

7. 程序代碼

程序中有比較詳細的註釋,雖然主幹在上面問題中分析過了,可是諸如如何解析一行數據之類的操做,仍是很煩的...能夠直接參考代碼

  1 #ifndef THREADPOOL_H
  2 #define THREADPOOL_H
  3 
  4 #include <list>
  5 #include <cstdio>
  6 #include <exception>
  7 #include <pthread.h>
  8 #include "locker.h" //簡單封裝了互斥量和信號量的接口
  9 
 10 //線程池類模板參數T是任務類型,T中必須有接口process
 11 template< typename T >
 12 class threadpool 
 13 {
 14 public:
 15     threadpool( int thread_number = 8, int max_requests = 10000 );//線程數目和最大鏈接處理數
 16     ~threadpool();
 17     bool append( T* request );
 18 
 19 private:
 20     static void* worker( void* arg );//線程工做函數
 21     void run(); //啓動線程池
 22 
 23 private:
 24     int m_thread_number;//線程數量
 25     int m_max_requests;//最大鏈接數目
 26     pthread_t* m_threads;//線程id數組
 27     std::list< T* > m_workqueue;//工做隊列:各線程競爭該隊列並處理相應的任務邏輯T
 28     locker m_queuelocker;//工做隊列互斥量
 29     sem m_queuestat;//信號量:用於工做隊列
 30     bool m_stop;//終止標誌
 31 };
 32 
 33 template< typename T >
 34 threadpool< T >::threadpool( int thread_number, int max_requests ) : 
 35         m_thread_number( thread_number ), m_max_requests( max_requests ), m_stop( false ), m_threads( NULL )
 36 {
 37     if( ( thread_number <= 0 ) || ( max_requests <= 0 ) )
 38     {
 39         throw std::exception();
 40     }
 41 
 42     m_threads = new pthread_t[ m_thread_number ];//工做線程數組
 43     if( ! m_threads )
 44     {
 45         throw std::exception();
 46     }
 47 
 48     for ( int i = 0; i < thread_number; ++i )//建立工做線程
 49     {
 50         printf( "create the %dth thread\n", i );
 51         if( pthread_create( m_threads + i, NULL, worker, this ) != 0 )
 52         {
 53             delete [] m_threads;
 54             throw std::exception();
 55         }
 56         if( pthread_detach( m_threads[i] ) ) //分離線程使得其它線程回收和殺死該線程
 57         {
 58             delete [] m_threads;
 59             throw std::exception();
 60         }
 61     }
 62 }
 63 
 64 template< typename T >
 65 threadpool< T >::~threadpool()
 66 {
 67     delete [] m_threads;
 68     m_stop = true;
 69 }
 70 
 71 template< typename T >
 72 bool threadpool< T >::append( T* request )//向工做隊列添加任務T
 73 {
 74     m_queuelocker.lock();//對工做隊列操做前加鎖
 75     if ( m_workqueue.size() > m_max_requests )//任務隊列滿,不能加進去
 76     {
 77         m_queuelocker.unlock();
 78         return false;
 79     }
 80     m_workqueue.push_back( request );
 81     m_queuelocker.unlock();
 82     m_queuestat.post();//信號量的V操做,多了一個工做任務T使得信號量+1
 83     return true;
 84 }
 85 
 86 template< typename T >
 87 void* threadpool< T >::worker( void* arg )//工做線程函數
 88 {
 89     threadpool* pool = ( threadpool* )arg; //獲取線程池對象,以前建立的時候傳的this
 90     pool->run(); //調用線程池run函數
 91     return pool;
 92 }
 93 
 94 template< typename T >
 95 void threadpool< T >::run() //工做線程真正工做邏輯:從任務隊列領取任務T並執行任務T,消費者
 96 {
 97     while ( ! m_stop )
 98     {
 99         m_queuestat.wait();//信號量P操做,申請信號量獲取任務T
100         m_queuelocker.lock();//對工做隊列操做前加鎖
101         if ( m_workqueue.empty() )
102         {
103             m_queuelocker.unlock();//任務隊列空沒法消費
104             continue;
105         }
106         T* request = m_workqueue.front();//獲取任務T
107         m_workqueue.pop_front();
108         m_queuelocker.unlock();
109         if ( ! request )
110         {
111             continue;
112         }
113         request->process();//執行任務T的相應邏輯,任務T中必須有process接口
114     }
115 }
116 
117 #endif
threadpool.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;//文件名最大長度,文件是HTTP請求的資源頁文件
    static const int READ_BUFFER_SIZE = 2048;//讀緩衝區,用於讀取HTTP請求
    static const int WRITE_BUFFER_SIZE = 1024;//寫緩衝區,用於HTTP回答
    enum METHOD { GET = 0, POST, HEAD, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH };//HTTP請求方法,本程序只定義了GET邏輯
    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 };//HTTP請求結果:未完整的請求(客戶端仍須要提交請求)、完整的請求、錯誤請求...只用了前三個
    enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };//HTTP每行解析狀態:改行解析完畢、錯誤的行、正在解析行

public:
    http_conn(){}
    ~http_conn(){}

public:
    void init( int sockfd, const sockaddr_in& addr );//初始化新的HTTP鏈接
    void close_conn( bool real_close = true );
    void process();//處理客戶請求,這是HTTP請求的入口函數,與在線程池中調用!!!
    bool read();//讀取客戶發送來的數據(HTTP請求)
    bool write();//將請求結果返回給客戶端
private:
    void init();//初始化鏈接,用於內部調用
    HTTP_CODE process_read();//解析HTTP請求,內部調用parse_系列函數
    bool process_write( HTTP_CODE ret );//填充HTTP應答,一般是將客戶請求的資源頁發送給客戶,內部調用add_系列函數

    HTTP_CODE parse_request_line( char* text );//解析HTTP請求的請求行
    HTTP_CODE parse_headers( char* text );//解析HTTP頭部數據
    HTTP_CODE parse_content( char* text );//獲取解析結果
    HTTP_CODE do_request();//處理HTTP鏈接:內部調用process_read(),process_write()
    char* get_line() { return m_read_buf + m_start_line; }//獲取HTTP請求數據中的一行數據
    LINE_STATUS parse_line();//解析行內部調用parse_request_line和parse_headers
    //下面的函數被process_write填充HTTP應答    
    
    void unmap();//解除內存映射,這裏內存映射是指將客戶請求的資源頁文件映射經過mmap映射到內存
    bool add_response( const char* format, ... );
    bool add_content( const char* content );
    bool add_status_line( int status, const char* title );
    bool add_headers( int content_length );
    bool add_content_length( int content_length );
    bool add_linger();
    bool add_blank_line();

public:
    static int m_epollfd;//全部socket上的事件都註冊到一個epoll事件表中因此用static
    static int m_user_count;//用戶數量

private:
    int m_sockfd;//HTTP鏈接對應的客戶在服務端的描述符m_sockfd和地址m_address
    sockaddr_in m_address;

    char m_read_buf[ READ_BUFFER_SIZE ];//讀緩衝區,讀取HTTP請求
    int m_read_idx;//已讀入的客戶數據最後一個字節的下一個位置,即未讀數據的第一個位置
    int m_checked_idx;//當前已經解析的字節(HTTP請求須要逐個解析)
    int m_start_line;//當前解析行的起始位置
    char m_write_buf[ WRITE_BUFFER_SIZE ];//寫緩衝區
    int m_write_idx;//寫緩衝區待發送的數據

    CHECK_STATE m_check_state;//HTTP解析的狀態:請求行解析、頭部解析
    METHOD m_method;//HTTP請求方法,只實現了GET

    char m_real_file[ FILENAME_LEN ];//HTTP請求的資源頁對應的文件名稱,和服務端的路徑拼接就造成了資源頁的路徑
    char* m_url;//請求的具體資源頁名稱,如:www.baidu.com/index.html
    char* m_version;//HTTP協議版本號,通常是:HTTP/1.1
    char* m_host;//主機名,客戶端要在HTTP請求中的目的主機名
    int m_content_length;//HTTP消息體的長度,簡單的GET請求這個爲空
    bool m_linger;//HTTP請求是否保持鏈接

    char* m_file_address;//資源頁文件內存映射後的地址
    struct stat m_file_stat;//資源頁文件的狀態,stat文件結構體
    struct iovec m_iv[2];//調用writev集中寫函數須要m_iv_count表示被寫內存塊的數量,iovec結構體存放了一段內存的起始位置和長度,
    int m_iv_count;//m_iv_count是指iovec結構體數組的長度即多少個內存塊
};

#endif
http_conn.h
  1 #include "http_conn.h"
  2 
  3 const char* ok_200_title = "OK";
  4 const char* error_400_title = "Bad Request";
  5 const char* error_400_form = "Your request has bad syntax or is inherently impossible to satisfy.\n";
  6 const char* error_403_title = "Forbidden";
  7 const char* error_403_form = "You do not have permission to get file from this server.\n";
  8 const char* error_404_title = "Not Found";
  9 const char* error_404_form = "The requested file was not found on this server.\n";
 10 const char* error_500_title = "Internal Error";
 11 const char* error_500_form = "There was an unusual problem serving the requested file.\n";
 12 const char* doc_root = "/var/www/html";//服務端資源頁的路徑,將其和HTTP請求中解析的m_url拼接造成資源頁的位置
 13 
 14 int setnonblocking( int fd )//將fd設置爲非阻塞
 15 {
 16     int old_option = fcntl( fd, F_GETFL );
 17     int new_option = old_option | O_NONBLOCK;
 18     fcntl( fd, F_SETFL, new_option );
 19     return old_option;
 20 }
 21 
 22 void addfd( int epollfd, int fd, bool one_shot )//將fd添加到事件表epollfd
 23 {
 24     epoll_event event;
 25     event.data.fd = fd;
 26     event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
 27     if( one_shot )
 28     {
 29         event.events |= EPOLLONESHOT;
 30     }
 31     epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
 32     setnonblocking( fd );
 33 }
 34 
 35 void removefd( int epollfd, int fd )//將fd從事件表epollfd中移除
 36 {
 37     epoll_ctl( epollfd, EPOLL_CTL_DEL, fd, 0 );
 38     close( fd );
 39 }
 40 
 41 void modfd( int epollfd, int fd, int ev )//EPOLLONESHOT須要重置事件後事件才能進行下次偵聽
 42 {
 43     epoll_event event;
 44     event.data.fd = fd;
 45     event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
 46     epoll_ctl( epollfd, EPOLL_CTL_MOD, fd, &event );
 47 }
 48 
 49 int http_conn::m_user_count = 0;//鏈接數
 50 int http_conn::m_epollfd = -1;//事件表,注意是static故全部http_con類對象共享一個事件表
 51 
 52 void http_conn::close_conn( bool real_close )//關閉鏈接,從事件表中移除描述符
 53     if( real_close && ( m_sockfd != -1 ) )
 54     {
 55         //modfd( m_epollfd, m_sockfd, EPOLLIN );
 56         removefd( m_epollfd, m_sockfd );
 57         m_sockfd = -1;
 58         m_user_count--;
 59     }
 60 }
 61 
 62 void http_conn::init( int sockfd, const sockaddr_in& addr )//初始化鏈接
 63 {
 64   m_sockfd = sockfd;//sockfd是http鏈接對應的描述符用於接收http請求和http回答
 65     m_address = addr;//客戶端地址
 66     int error = 0;
 67     socklen_t len = sizeof( error );
 68     getsockopt( m_sockfd, SOL_SOCKET, SO_ERROR, &error, &len );
 69     int reuse = 1;
 70     setsockopt( m_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof( reuse ) );//獲取描述符狀態,能夠在調試時用
 71     addfd( m_epollfd, sockfd, true );
 72     m_user_count++;//多了一個http用戶
 73 
 74     init();//調用重載函數
 75 }
 76 
 77 void http_conn::init()//重載init函數進行些鏈接前的初始化操做
 78 {
 79     m_check_state = CHECK_STATE_REQUESTLINE;
 80     m_linger = false;
 81 
 82     m_method = GET;
 83     m_url = 0;
 84     m_version = 0;
 85     m_content_length = 0;
 86     m_host = 0;
 87     m_start_line = 0;
 88     m_checked_idx = 0;
 89     m_read_idx = 0;
 90     m_write_idx = 0;
 91     memset( m_read_buf, '\0', READ_BUFFER_SIZE );
 92     memset( m_write_buf, '\0', WRITE_BUFFER_SIZE );
 93     memset( m_real_file, '\0', FILENAME_LEN );
 94 }
 95 
 96 /*從狀態機,用於解析出一行內容*/
 97 http_conn::LINE_STATUS http_conn::parse_line()
 98 {
 99     char temp;
100     //checked_index指向buffer(應用程序讀緩衝區)中當前正在分析的字節,read_index指向buffer中客戶數據尾部的下一個字節;
101     //buffer中的第0 ~ checked_index字節都已分析完畢, 第checked_index ~ (read_index - 1)字節由下面的循環挨個分析
102     for ( ; m_checked_idx < m_read_idx; ++m_checked_idx )
103     {
104         //得到當前要分析的字節;
105         temp = m_read_buf[ m_checked_idx ];
106         //若是是「\r」,即回車符號,說明可能讀取到一個完整的行
107         if ( temp == '\r' )
108         {
109             //若是\r恰巧是目前buffer中最後一個已經被讀入的客戶數據,那麼此次分析沒有讀取到完整的行,
110             //返回LINE_OPEN表示還須要繼續讀取客戶數據來進一步分析
111             if ( ( m_checked_idx + 1 ) == m_read_idx )
112             {
113                 return LINE_OPEN;
114             }
115             //若是下一個字符是\n,說明讀到一個完整的行
116             else if ( m_read_buf[ m_checked_idx + 1 ] == '\n' )
117             {
118                 m_read_buf[ m_checked_idx++ ] = '\0';
119                 m_read_buf[ m_checked_idx++ ] = '\0';
120                 return LINE_OK;
121             }
122             //不然,說明存在語法問題
123             return LINE_BAD;
124         }
125         //若是當前字符是\n,說明也可能讀到一個完整的行
126         //not necessary
127         else if( temp == '\n' )
128         {
129             if( ( m_checked_idx > 1 ) && ( m_read_buf[ m_checked_idx - 1 ] == '\r' ) )
130             {
131                 m_read_buf[ m_checked_idx-1 ] = '\0';
132                 m_read_buf[ m_checked_idx++ ] = '\0';
133                 return LINE_OK;
134             }
135             return LINE_BAD;
136         }
137     }
138     //若是全部內容分析完畢都沒有遇到\r字符,則返回LINE_OPEN,表示還須要繼續讀取客戶數據才能進一步分析
139     return LINE_OPEN;
140 }
141 
142 //循環讀取客戶數據,直到無數據可讀或者對方關閉鏈接
143 bool http_conn::read()
144 {
145     if( m_read_idx >= READ_BUFFER_SIZE )
146     {
147         return false;
148     }
149 
150     int bytes_read = 0;
151     while( true )
152     {
153         bytes_read = recv( m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0 );
154         if ( bytes_read == -1 )
155         {
156             if( errno == EAGAIN || errno == EWOULDBLOCK )
157             {
158                 break;
159             }
160             return false;
161         }
162         else if ( bytes_read == 0 )
163         {
164             return false;
165         }
166 
167         m_read_idx += bytes_read;
168     }
169     return true;
170 }
171 
172 
173 http_conn::HTTP_CODE http_conn::parse_request_line( char* text )
174 {
175     //strpbrk()函數檢索兩個字符串中首個相同字符的位置,其原型爲:
176     //    char *strpbrk( char *s1, char *s2)
177     m_url = strpbrk( text, " \t" );
178     
179     //請求行中若是沒有\t,則該HTTP請求有問題
180     if ( ! m_url )
181     {
182         return BAD_REQUEST;
183     }
184     
185     *m_url++ = '\0';
186 
187     char* method = text;
188     //定義函數:int strcasecmp (const char *s1, const char *s2);
189     //函數說明:strcasecmp()用來比較參數s1 和s2 字符串,比較時會自動忽略大小寫的差別。
190     //返回值:若參數s1 和s2 字符串相同則返回0。s1 長度大於s2 長度則返回大於0 的值,s1 長度若小於s2 長度則返回小於0 的值。
191     if ( strcasecmp( method, "GET" ) == 0 )
192     {
193         m_method = GET;
194     }
195     else
196     {
197         return BAD_REQUEST;
198     }
199     //strspn() 函數用來計算字符串 str 中連續有幾個字符都屬於字符串 accept,其原型爲:
200     //size_t strspn(const char *str, const char * accept);
201     m_url += strspn( m_url, " \t" );
202     m_version = strpbrk( m_url, " \t" );
203     if ( ! m_version )
204     {
205         return BAD_REQUEST;
206     }
207     *m_version++ = '\0';
208     m_version += strspn( m_version, " \t" );
209     if ( strcasecmp( m_version, "HTTP/1.1" ) != 0 )
210     {
211         return BAD_REQUEST;
212     }
213 
214     if ( strncasecmp( m_url, "http://", 7 ) == 0 )
215     {
216         m_url += 7;
217         //strchr() 用來查找某字符在字符串中首次出現的位置,其原型爲:
218         //char * strchr (const char *str, int c)
219         m_url = strchr( m_url, '/' );
220     }
221 
222     if ( ! m_url || m_url[ 0 ] != '/' )
223     {
224         return BAD_REQUEST;
225     }
226     //HTTP請求行處理完畢,狀態轉移到頭部字段的分析
227     m_check_state = CHECK_STATE_HEADER;
228     return NO_REQUEST;
229 }
230 
231 /*解析http請求的一個頭部信息*/
232 http_conn::HTTP_CODE http_conn::parse_headers( char* text )
233 {    //空行,頭部字段解析完畢
234     if( text[ 0 ] == '\0' )
235     {
236         if ( m_method == HEAD )
237         {
238             return GET_REQUEST;
239         }
240         //若是HTTP請求有消息體,則還須要讀取m_content_length字節的消息體,從狀態機轉移到CHECK_STATE_CONTENT狀態
241         if ( m_content_length != 0 )
242         {
243             m_check_state = CHECK_STATE_CONTENT;
244             return NO_REQUEST;
245         }
246         //不然,說明獲得了一個完整的HTTP請求
247         return GET_REQUEST;
248     }
249     //處理connection頭部字段
250     else if ( strncasecmp( text, "Connection:", 11 ) == 0 )
251     {
252         text += 11;
253         text += strspn( text, " \t" );
254         if ( strcasecmp( text, "keep-alive" ) == 0 )
255         {
256             m_linger = true;
257         }
258     }
259     //處理Content_length頭部字段
260     else if ( strncasecmp( text, "Content-Length:", 15 ) == 0 )
261     {
262         text += 15;
263         text += strspn( text, " \t" );
264         m_content_length = atol( text );
265     }
266     //處理host頭部字段
267     else if ( strncasecmp( text, "Host:", 5 ) == 0 )
268     {
269         text += 5;
270         text += strspn( text, " \t" );
271         m_host = text;
272     }
273     else
274     {
275         printf( "oop! unknow header %s\n", text );
276     }
277 
278     return NO_REQUEST;
279 
280 }
281 //消息體不解析,判斷一下是否完整讀入
282 http_conn::HTTP_CODE http_conn::parse_content( char* text )
283 {
284     if ( m_read_idx >= ( m_content_length + m_checked_idx ) )
285     {
286         text[ m_content_length ] = '\0';
287         return GET_REQUEST;
288     }
289 
290     return NO_REQUEST;
291 }
292 
293 http_conn::HTTP_CODE http_conn::process_read()
294 {
295     LINE_STATUS line_status = LINE_OK;
296     HTTP_CODE ret = NO_REQUEST;
297     char* text = 0;
298 
299     while ( ( ( m_check_state == CHECK_STATE_CONTENT ) && ( line_status == LINE_OK  ) )
300                 || ( ( line_status = parse_line() ) == LINE_OK ) )
301     {
302         text = get_line();
303         m_start_line = m_checked_idx;
304         printf( "got 1 http line: %s\n", text );
305 
306         switch ( m_check_state )
307         {
308             case CHECK_STATE_REQUESTLINE:
309             {
310                 ret = parse_request_line( text );
311                 if ( ret == BAD_REQUEST )
312                 {
313                     return BAD_REQUEST;
314                 }
315                 break;
316             }
317             case CHECK_STATE_HEADER:
318             {
319                 ret = parse_headers( text );
320                 if ( ret == BAD_REQUEST )
321                 {
322                     return BAD_REQUEST;
323                 }
324                 else if ( ret == GET_REQUEST )
325                 {
326                     return do_request();
327                 }
328                 break;
329             }
330             case CHECK_STATE_CONTENT:
331             {
332                 ret = parse_content( text );
333                 if ( ret == GET_REQUEST )
334                 {
335                     return do_request();
336                 }
337                 line_status = LINE_OPEN;
338                 break;
339             }
340             default:
341             {
342                 return INTERNAL_ERROR;
343             }
344         }
345     }
346 
347     return NO_REQUEST;
348 }
349 
350 http_conn::HTTP_CODE http_conn::do_request()//用於獲取資源頁文件的狀態
351 {
352     strcpy( m_real_file, doc_root );
353     int len = strlen( doc_root );
354     strncpy( m_real_file + len, m_url, FILENAME_LEN - len - 1 );
355     if ( stat( m_real_file, &m_file_stat ) < 0 )
356     {
357         return NO_RESOURCE;//若資源頁不存在則HTTP解析結果爲404
358     }
359 
360     if ( ! ( m_file_stat.st_mode & S_IROTH ) )
361     {
362         return FORBIDDEN_REQUEST;//資源沒有權限獲取
363     }
364 
365     if ( S_ISDIR( m_file_stat.st_mode ) )
366     {
367         return BAD_REQUEST;//請求有錯
368     }
369 
370     int fd = open( m_real_file, O_RDONLY );
371     m_file_address = ( char* )mmap( 0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );//將資源頁文件映射到內存
372     close( fd );
373     return FILE_REQUEST;//資源頁請求成功
374 }
375 
376 void http_conn::unmap()//解除資源頁文件映射的內存
377 {
378     if( m_file_address )
379     {
380         munmap( m_file_address, m_file_stat.st_size );//解除映射
381         m_file_address = 0;
382     }
383 }
384 
385 bool http_conn::write()//將資源頁文件發送給客戶端
386 {
387     int temp = 0;
388     int bytes_have_send = 0;
389     int bytes_to_send = m_write_idx;
390     if ( bytes_to_send == 0 )
391     {
392         modfd( m_epollfd, m_sockfd, EPOLLIN );//EPOLLONESHOT事件每次須要重置事件
393         init();
394         return true;
395     }
396 
397     while( 1 )//
398     {
399         temp = writev( m_sockfd, m_iv, m_iv_count );//集中寫,m_sockfd是http鏈接對應的描述符,m_iv是iovec結構體數組表示內存塊地址,m_iv_count是數組的長度即多少個內存塊將一次集中寫到m_sockfd
400         if ( temp <= -1 )//集中寫失敗
401         {
402             if( errno == EAGAIN )
403             {
404                 modfd( m_epollfd, m_sockfd, EPOLLOUT );//重置EPOLLONESHOT事件,註冊可寫事件表示若m_sockfd沒有寫失敗則關閉鏈接
405                 return true;
406             }
407             unmap();//解除內存映射
408             return false;
409         }
410 
411         bytes_to_send -= temp;//待發送數據
412         bytes_have_send += temp;//已發送數據
413         if ( bytes_to_send <= bytes_have_send )
414         {
415             unmap();//該資源頁已經發送完畢該解除映射
416             if( m_linger )//若要保持該http鏈接
417             {
418                 init();//初始化http鏈接
419                 modfd( m_epollfd, m_sockfd, EPOLLIN );
420                 return true;
421             }
422             else
423             {
424                 modfd( m_epollfd, m_sockfd, EPOLLIN );
425                 return false;
426             } 
427         }
428     }
429 }
430 
431 bool http_conn::add_response( const char* format, ... )//HTTP應答主要是將應答數據添加到寫緩衝區m_write_buf
432 {
433     if( m_write_idx >= WRITE_BUFFER_SIZE )
434     {
435         return false;
436     }
437     va_list arg_list;
438     va_start( arg_list, format );
439     int len = vsnprintf( m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list );//將fromat內容輸出到m_write_buf
440     if( len >= ( WRITE_BUFFER_SIZE - 1 - m_write_idx ) )
441     {
442         return false;
443     }
444     m_write_idx += len;
445     va_end( arg_list );
446     return true;
447 }
448 
449 bool http_conn::add_status_line( int status, const char* title )
450 {
451     return add_response( "%s %d %s\r\n", "HTTP/1.1", status, title );
452 }
453 
454 bool http_conn::add_headers( int content_len )
455 {
456     add_content_length( content_len );
457     add_linger();
458     add_blank_line();
459 }
460 
461 bool http_conn::add_content_length( int content_len )
462 {
463     return add_response( "Content-Length: %d\r\n", content_len );
464 }
465 
466 bool http_conn::add_linger()
467 {
468     return add_response( "Connection: %s\r\n", ( m_linger == true ) ? "keep-alive" : "close" );
469 }
470 
471 bool http_conn::add_blank_line()
472 {
473     return add_response( "%s", "\r\n" );
474 }
475 
476 bool http_conn::add_content( const char* content )
477 {
478     return add_response( "%s", content );
479 }
480 
481 bool http_conn::process_write( HTTP_CODE ret )
482 {
483     switch ( ret )
484     {
485         case INTERNAL_ERROR:
486         {
487             add_status_line( 500, error_500_title );
488             add_headers( strlen( error_500_form ) );
489             if ( ! add_content( error_500_form ) )
490             {
491                 return false;
492             }
493             break;
494         }
495         case BAD_REQUEST:
496         {
497             add_status_line( 400, error_400_title );
498             add_headers( strlen( error_400_form ) );
499             if ( ! add_content( error_400_form ) )
500             {
501                 return false;
502             }
503             break;
504         }
505         case NO_RESOURCE:
506         {
507             add_status_line( 404, error_404_title );
508             add_headers( strlen( error_404_form ) );
509             if ( ! add_content( error_404_form ) )
510             {
511                 return false;
512             }
513             break;
514         }
515         case FORBIDDEN_REQUEST:
516         {
517             add_status_line( 403, error_403_title );
518             add_headers( strlen( error_403_form ) );
519             if ( ! add_content( error_403_form ) )
520             {
521                 return false;
522             }
523             break;
524         }
525         case FILE_REQUEST:
526         {
527             add_status_line( 200, ok_200_title );
528             if ( m_file_stat.st_size != 0 )
529             {
530                 add_headers( m_file_stat.st_size );
531                 m_iv[ 0 ].iov_base = m_write_buf;
532                 m_iv[ 0 ].iov_len = m_write_idx;
533                 m_iv[ 1 ].iov_base = m_file_address;
534                 m_iv[ 1 ].iov_len = m_file_stat.st_size;
535                 m_iv_count = 2;
536                 return true;
537             }
538             else
539             {
540                 const char* ok_string = "<html><body></body></html>";
541                 add_headers( strlen( ok_string ) );
542                 if ( ! add_content( ok_string ) )
543                 {
544                     return false;
545                 }
546             }
547         }
548         default:
549         {
550             return false;
551         }
552     }
553 
554     m_iv[ 0 ].iov_base = m_write_buf;
555     m_iv[ 0 ].iov_len = m_write_idx;
556     m_iv_count = 1;
557     return true;
558 }
559 
560 void http_conn::process()
561 {
562     HTTP_CODE read_ret = process_read();
563     if ( read_ret == NO_REQUEST )
564     {
565         modfd( m_epollfd, m_sockfd, EPOLLIN );
566         return;
567     }
568 
569     bool write_ret = process_write( read_ret );
570     if ( ! write_ret )
571     {
572         close_conn();
573     }
574 
575     modfd( m_epollfd, m_sockfd, EPOLLOUT );
576 }
http_conn.cpp
  1 #ifndef LOCKER_H
  2 #define LOCKER_H
  3 
  4 #include <exception>
  5 #include <pthread.h>
  6 #include <semaphore.h>
  7 
  8 class sem
  9 {
 10 public:
 11     sem()
 12     {
 13         if( sem_init( &m_sem, 0, 0 ) != 0 )
 14         {
 15             throw std::exception();
 16         }
 17     }
 18     ~sem()
 19     {
 20         sem_destroy( &m_sem );
 21     }
 22     bool wait()
 23     {
 24         return sem_wait( &m_sem ) == 0;
 25     }
 26     bool post()
 27     {
 28         return sem_post( &m_sem ) == 0;
 29     }
 30 
 31 private:
 32     sem_t m_sem;
 33 };
 34 
 35 class locker
 36 {
 37 public:
 38     locker()
 39     {
 40         if( pthread_mutex_init( &m_mutex, NULL ) != 0 )
 41         {
 42             throw std::exception();
 43         }
 44     }
 45     ~locker()
 46     {
 47         pthread_mutex_destroy( &m_mutex );
 48     }
 49     bool lock()
 50     {
 51         return pthread_mutex_lock( &m_mutex ) == 0;
 52     }
 53     bool unlock()
 54     {
 55         return pthread_mutex_unlock( &m_mutex ) == 0;
 56     }
 57 
 58 private:
 59     pthread_mutex_t m_mutex;
 60 };
 61 
 62 class cond
 63 {
 64 public:
 65     cond()
 66     {
 67         if( pthread_mutex_init( &m_mutex, NULL ) != 0 )
 68         {
 69             throw std::exception();
 70         }
 71         if ( pthread_cond_init( &m_cond, NULL ) != 0 )
 72         {
 73             pthread_mutex_destroy( &m_mutex );
 74             throw std::exception();
 75         }
 76     }
 77     ~cond()
 78     {
 79         pthread_mutex_destroy( &m_mutex );
 80         pthread_cond_destroy( &m_cond );
 81     }
 82     bool wait()
 83     {
 84         int ret = 0;
 85         pthread_mutex_lock( &m_mutex );
 86         ret = pthread_cond_wait( &m_cond, &m_mutex );
 87         pthread_mutex_unlock( &m_mutex );
 88         return ret == 0;
 89     }
 90     bool signal()
 91     {
 92         return pthread_cond_signal( &m_cond ) == 0;
 93     }
 94 
 95 private:
 96     pthread_mutex_t m_mutex;
 97     pthread_cond_t m_cond;
 98 };
 99 
100 #endif
locker.h
  1 #include <sys/socket.h>
  2 #include <netinet/in.h>
  3 #include <arpa/inet.h>
  4 #include <stdio.h>
  5 #include <unistd.h>
  6 #include <errno.h>
  7 #include <string.h>
  8 #include <fcntl.h>
  9 #include <stdlib.h>
 10 #include <cassert>
 11 #include <sys/epoll.h>
 12 
 13 #include "locker.h"
 14 #include "threadpool.h"
 15 #include "http_conn.h"
 16 
 17 #define MAX_FD 65536
 18 #define MAX_EVENT_NUMBER 10000
 19 
 20 extern int addfd( int epollfd, int fd, bool one_shot );
 21 extern int removefd( int epollfd, int fd );
 22 
 23 void addsig( int sig, void( handler )(int), bool restart = true )
 24 {
 25     struct sigaction sa;
 26     memset( &sa, '\0', sizeof( sa ) );
 27     sa.sa_handler = handler;
 28     if( restart )
 29     {
 30         sa.sa_flags |= SA_RESTART;
 31     }
 32     sigfillset( &sa.sa_mask );
 33     assert( sigaction( sig, &sa, NULL ) != -1 );
 34 }
 35 
 36 void show_error( int connfd, const char* info )
 37 {
 38     printf( "%s", info );
 39     send( connfd, info, strlen( info ), 0 );
 40     close( connfd );
 41 }
 42 
 43 
 44 int main( int argc, char* argv[] )
 45 {
 46     if( argc <= 2 )
 47     {
 48         printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
 49         return 1;
 50     }
 51     const char* ip = argv[1];
 52     int port = atoi( argv[2] );
 53 
 54     addsig( SIGPIPE, SIG_IGN );
 55     
 56     //建立線程池
 57     threadpool< http_conn >* pool = NULL;
 58     try
 59     {
 60         pool = new threadpool< http_conn >;
 61     }
 62     catch( ... )
 63     {
 64         return 1;
 65     }
 66 
 67     //預先爲每一個客戶鏈接分配一個http_conn對象
 68     http_conn* users = new http_conn[ MAX_FD ];
 69     assert( users );
 70     int user_count = 0;
 71 
 72     int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
 73     assert( listenfd >= 0 );
 74     struct linger tmp = { 1, 0 };
 75     setsockopt( listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof( tmp ) );
 76 
 77     int ret = 0;
 78     struct sockaddr_in address;
 79     bzero( &address, sizeof( address ) );
 80     address.sin_family = AF_INET;
 81     inet_pton( AF_INET, ip, &address.sin_addr );
 82     address.sin_port = htons( port );
 83 
 84     ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
 85     assert( ret >= 0 );
 86 
 87     ret = listen( listenfd, 5 );
 88     assert( ret >= 0 );
 89 
 90     epoll_event events[ MAX_EVENT_NUMBER ];
 91     int epollfd = epoll_create( 5 );
 92     assert( epollfd != -1 );
 93     addfd( epollfd, listenfd, false );
 94     http_conn::m_epollfd = epollfd;
 95 
 96     while( true )
 97     {
 98         int number = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
 99         if ( ( number < 0 ) && ( errno != EINTR ) )
100         {
101             printf( "epoll failure\n" );
102             break;
103         }
104 
105         for ( int i = 0; i < number; i++ )
106         {
107             int sockfd = events[i].data.fd;
108             if( sockfd == listenfd )
109             {
110                 struct sockaddr_in client_address;
111                 socklen_t client_addrlength = sizeof( client_address );
112                 int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
113                 if ( connfd < 0 )
114                 {
115                     printf( "errno is: %d\n", errno );
116                     continue;
117                 }
118                 if( http_conn::m_user_count >= MAX_FD )
119                 {
120                     show_error( connfd, "Internal server busy" );
121                     continue;
122                 }
123                 //初始化客戶鏈接
124                 users[connfd].init( connfd, client_address );
125             }
126             else if( events[i].events & ( EPOLLRDHUP | EPOLLHUP | EPOLLERR ) )
127             {
128                 users[sockfd].close_conn();
129             }
130             else if( events[i].events & EPOLLIN )
131             {
132                 //根據讀的結果,以爲將任務加入線程池,仍是關閉鏈接
133                 if( users[sockfd].read() )
134                 {
135                     pool->append( users + sockfd );
136                 }
137                 else
138                 {
139                     users[sockfd].close_conn();
140                 }
141             }
142             else if( events[i].events & EPOLLOUT )
143             {
144                 //根據寫的結果,以爲是否關閉鏈接
145                 if( !users[sockfd].write() )
146                 {
147                     users[sockfd].close_conn();
148                 }
149             }
150             else
151             {}
152         }
153     }
154 
155     close( epollfd );
156     close( listenfd );
157     delete [] users;
158     delete pool;
159     return 0;
160 }
main.cpp

 

8. 參考資料

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

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

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

[4]. W.RICHARDSTEVENS, STEPHENA.RAGO. UNIX環境高級編程[M]. 人民郵電出版社, 2014.

相關文章
相關標籤/搜索