1. 程式人生 > >Tinywebserver:一個簡易的web伺服器

Tinywebserver:一個簡易的web伺服器

這是學習網路程式設計後寫的一個練手的小程式,可以幫助複習I/O模型,epoll使用,執行緒池,HTTP協議等內容。

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

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 
            
           

相關推薦

Tinywebserver:一個簡易web伺服器

這是學習網路程式設計後寫的一個練手的小程式,可以幫助複習I/O模型,epoll使用,執行緒池,HTTP協議等內容。 首先回顧程式中的核心內容和主要問題,最後給出相關程式碼。 0. 功能和I/O模型 實現簡易的HTTP服務端,現僅支援GET方法,通過瀏覽器訪問可以返回相應內容。 I/O模型採用Reactor(

Tinywebserver-一個簡易web伺服器

這是學習網路程式設計後寫的一個練手的小程式,可以幫助複習I/O模型,epoll使用,執行緒池,HTTP協議等內容。 程式程式碼是基於《Linux高效能伺服器程式設計》一書編寫的。 首先回顧程式中的核心內容和主要問題,最後給出相關程式碼。 0. 功能和I/O模型 實現簡易的H

TinyWS —— 一個C++寫的簡易WEB伺服器(一)

寫在前面 每個碼農可能都會偶爾有自己做一個常用軟體的想法,比如作業系統,編譯器,郵件伺服器/客戶端,文字編輯器等等。這裡面有些很難,比如作業系統,做一個最簡單的也要付出很大的努力,可是大部分常用工具都是可以比較容易的做一個簡易版本(當然也是隻能玩玩而已)。於是我做了一個非常簡陋的WEB伺服器 —— Tiny

一個簡易git伺服器的搭建

檢視本機ssh公鑰,生成公鑰 檢視ssh公鑰方法: 1. 開啟git bash視窗 2. 進入.ssh目錄: cd ~/.ssh 3. 找到id_rsa.pub檔案: ls 4. 檢視公鑰:cat id_rsa.pub 或者 vim id_rsa.pub 何為公鑰: 1. 很多

使用Socket模擬簡易Web伺服器

Web伺服器大家應該都很熟悉了,web伺服器的原理可以看這裡。講的挺詳細的。 本篇主要是模擬一下簡單的互動,通過socket通道,瀏覽器傳送請求,伺服器返回資源。 簡單圖例如上,然後來看程式碼吧   1、首先啟動服務監聽埠,使用執行緒池來完成互動 package

執行一個本地web伺服器

  本文記錄安裝一個Python伺服器的流程。   首先安裝python,地址python.org。確保選中了“將Python 3.xxx新增到PATH”複選框。   進入專案目錄,在終端中輸入python -m http.server以啟動伺服器。預設情況下,這將在本地Web伺服器上的埠8000上執行目

go搭建一個簡單web伺服器

Go語言裡面提供了一個完善的net/http包,通過http包可以很 方便的就搭建起來一個可以執行的web服務。同時使用這個包能很簡單地對web的路由,靜態檔案,模版,cookie等數 據進行設定和操

用原生Node實現一個靜態web伺服器

之前一直用過Apache nginx等靜態web伺服器。 但強大的node.js本身就能作為獨立的web伺服器,不依賴與Apache  nginx 下面我們看看怎麼用Node去寫一個靜態伺服器吧 首先,先來看看我的專案結構吧                  

從零開始搭建一個簡易伺服器(二)

超級大坑 第一篇部落格到現在拖坑有半年了(不過估計也沒人記得我),原本的打算是既然要寫伺服器,那自然要設計一門語言,類似於php這樣的工作於伺服器後端負責後端渲染,然後到目前為止的時間基本都花在寫編譯器上了囧,編譯器的專案在這裡。如果真的等編譯器全部寫

一個簡單web伺服器的java實現

一個簡單的web伺服器在不考慮其效能及健壯性的情況下,通常只需實現的功能包括伺服器的啟動,它用於監聽某一個埠,接收客戶端發來的請求,並將響應結果返回給客戶端。本文將介紹一個簡單web伺服器的實現原理,它本身只能處理某個目錄下的靜態資原始檔(文字、圖片等)。採用java

從零開始搭建一個簡易伺服器(一)

前言 其實大家大可不必被伺服器這三個字嚇到,一個入門級後端框架,所需的僅僅是HTTP相關的知識與應用這些知識的程式設計工具。據本人的經驗,絕大多數人擁有搭建後端所涉及到的基礎理論知識,但是缺乏能將之應用出去的工具,而本文即是交給讀者這樣一個工具,並能夠運用之來

手寫一個 Java web 伺服器

關於 web 伺服器 Java 中有很多優秀的 web 伺服器(容器),如 Tomcat、Weblogic、JBOSS 等等。我們都知道 web 伺服器是用於接受外部請求並給予迴應(響應)的一個玩意兒。所以今天造一個可以接受請求並響應請求的輪子,大致思路是使用

Linux下簡易web伺服器實現

今天突然對http的web伺服器感興趣了,就研究了一下,發現linux下的web伺服器就是一個socket程式設計的伺服器端,而我們用的ie,chrome等瀏覽器就是客戶端,只不過傳送和接收資料按照http網頁格式,就相當於對資料進行了封裝,相當於加上了檔案頭和檔案

用HTTP核心模組配置一個靜態web伺服器

1. 虛擬主機與請求的分發2. 檔案路徑的定義3. 記憶體幾磁碟資源的分配4. 網路連結的設定5. MIME型別的設定6. 對客戶端請求的限制7. 檔案操作的優化8. 對客戶端請求的特殊處理9. ngx

Python搭建簡易web伺服器,超好用~

有時需要手機除錯一些web頁面,於是~找到了這個超好用的方法 首先,你要有python 然後,命令列進入web資料夾根目錄,這裡假設是index.html所在目錄 輸入python命令: python -m SimpleHTTPServer 8080 8080是埠號,可以任

【node.js】使用node.js搭建一個本地web伺服器

操作步驟 1、到node官網(https://nodejs.org/en/)下載node.js安裝檔案,X64代表執行環境為windows64位 2、雙擊安裝檔案安裝node.js 3、等待安裝 4、測試是否安裝成功,按【windows+R】,執行cmd 5、輸入n

nginx教程第六篇:用HTTP核心模組配置一個靜態Web伺服器(二)

網路連線的設定 下面介紹網路連線的設定配置項: 1. 讀取HTTP頭部的超時時間 語法: client_header_timeout time( 預設單位: 秒) ; 預設: client_header_timeout 60; 配置塊: http、 serve

筆記:學習go語言的網路基礎庫,並嘗試搭一個簡易Web框架

![走你~!](https://images.cnblogs.com/cnblogs_com/tanshaoshenghao/1910827/o_210113093044go%E7%BD%91%E7%BB%9C%E5%9F%BA%E7%A1%80%E5%BA%93%E6%B0%B4%E5%8D%B0%E7%8

NodeJs實現一個簡易WEB上傳下載伺服器

專案上的需求是叢集均可生成PDF檔案或是訪問PDF檔案,但是沒有檔案伺服器,故做一個簡易的檔案伺服器。解決方案:叢集內的機器(客戶端)生成PDF檔案之後將PDF檔案推給檔案伺服器,我們暫且稱它為服務端;如果某個客戶端需要訪問到這個PDF檔案,則去服務端獲取(因為可能其他客戶端

ASP.NET一個簡易的WebServer,用控制臺程序模擬IIS 托起web服務

public 程序 控制臺 ProcessRequestHandler( page, query, TextWriter writer); WebServer : MarshalByRefObject, IRegisteredObject { Pro