使用libevent和boost編寫一個簡單的tcp服務器

寫這個東西主要是爲了學習libevent的基本用法,以及學習下boost的線程庫。
程序結構比較簡單:

  1. 首先是創建一個監聽socke。

  2. 將這個監聽的socket綁定到一個event事件上,而後等待有客戶過來鏈接。

  3. 若是響應到監聽socket可讀,則accept嘗試鏈接的客戶端。

  4. 開啓一個線程來處理全部和這個鏈接過來的客戶端之間的交互。(實際上什麼事情也沒作,就是cout了下每次recv的數據大小)

代碼以下:
1. 首先是程序入口,main函數
main函數主要是註冊了一個監聽使用的socket。另一旦進入了監聽狀態,就很差退出程序,因此一開始就註冊了一個信號響應函數,
專門用來響應程序退出的信號。

//建立監聽socket,而後等待這個socket有客戶來連接
 //每一個連接一個線程去處理
 int main(int argc, char* argv[])
 {
     //首先處理好kill -2信號
     struct sigaction sigact = {0};
     sigact.sa_sigaction = On_Exit;
     if ( -1 == sigaction(2, &sigact, NULL))
     {
         log("創建響應函數失敗");
         return 0;
     }
 
     //創建一個非阻塞的socket句柄
     int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
     if (-1 == sockfd)
     {
         log("建立監聽socket失敗", ERROR);
         return 0;
     }
     log("建立監聽socket成功");
 
     //bind
     sockaddr_in sinAddr;
     sinAddr.sin_family = AF_INET;
     sinAddr.sin_port = htons(LISTEN_PORT);
     sinAddr.sin_addr.s_addr = inet_addr("172.21.169.160");
     if( -1 == bind(sockfd, (sockaddr*)&sinAddr, sizeof(sockaddr_in)))
     {
         log("監聽本地端口失敗", ERROR);
         return 0;
     }
 
     //進入listen
     if (-1 == listen(sockfd, SOMAXCONN))
     {
         log("監聽本地端口失敗", ERROR);
         return 0;
     }
     log("監聽本地端口成功");
 
     //進入accept狀態
     log("即將進入監聽狀態");
     EnterAcceptWhile(sockfd);
 
     close(sockfd);
     log("監聽線程成功結束了");
     return 0;
 }

2.  進入libevent的消息循環
 //基於event的消息響應機制
 void EnterAcceptWhile(int nFd)
 {
    if (g_pEventLoop == NULL)
        g_pEventLoop = event_base_new();
 
    //綁定事件
    struct event* pListenEvent = event_new(g_pEventLoop, nFd, EV_PERSIST|EV_READ, On_Sock_Accept, NULL);
    if (pListenEvent == NULL)
    {
        log ("創建監聽事件失敗");
        return;
    }
 
    //加入監聽事件,持續
    event_add(pListenEvent, NULL);
 
    //分派消息
    int nLoopRst = 0;
    if (0 == (nLoopRst = event_base_dispatch(g_pEventLoop)))
    {
        log("事件循環正常中止了");
    }
    else if (1 == nLoopRst)
    {
        log("沒有事件關聯到這個消息循環了");
    }
    else
    {
        log("消息循環出現了錯誤", ERROR);
    }
 
    event_free(pListenEvent);
    event_base_free(g_pEventLoop);
    g_pEventLoop = NULL;
 }

3. 一旦響應到有客戶端過來鏈接,就會進入On_Sock_Accept函數。所以這個函數應該儘量的短小。
 //響應客戶端鏈接
 void On_Sock_Accept(int nFd, short sFlags, void* pArg)
 {
     if (!(sFlags&EV_READ))
     {
         log("接受到了一個莫名其妙的消息", WARNING);
         return;
     }
     log("響應到一個客戶端過來鏈接了"); 
 
     socklen_t sockLen;
     sockaddr sa;
     int nAcceptFd = accept(nFd, &sa, &sockLen); 
 
     //這裏應該啓動一個線程來處理這個請求事務的,而不該該在這裏作大量的複雜操做
     SocketThread st(nAcceptFd); 
     boost::thread thread_ST(st);
 }

4. 在上個函數中,開了一個線程專門處理來自這個客戶端的請求。 類SocketThread的代碼以下:
 SocketThread::SocketThread(int nFd)
 {
     m_nFd = nFd;
 }
 
 void SocketThread::operator() ()
 {
     if (m_nFd == -1)
         return;

    //讀取數據,等到讀取完成以後,輸出出來,最後關閉掉socket鏈接
    char* pszTemp = new char[1024];
    memset(pszTemp, 0, 1024);

    int nRecvSize = 0;
    while( 0 < (nRecvSize = recv(m_nFd, pszTemp, 1024, 0))) 
    {
        //讀到數據了
        std::cout << "thread id: " << boost::this_thread::get_id() << " 接受到了:" << nRecvSize << "字節的數據" << std::endl;
    }

    delete[] pszTemp;
    pszTemp = NULL;

    if (nRecvSize == 0)
    {
        std::cout << "客戶端已經關閉了" << std::endl;
    }
    else if (nRecvSize < 0)
    {
        std::cout << "接受客戶端數據失敗,請檢查緣由" << std::endl;
    }
    close(m_nFd);
    m_nFd = -1;
}

5. 最後是響應信號2, 退出libevent的消息循環
其實這裏存在問題:若是退出消息循環的時候還有不少的工做者線程正在運行,應該要先讓他們把事情作完再退出的。


 //響應退出消息
 void On_Exit(int nSigId, siginfo_t* pSigInfo, void* pArg)
 {
     log("準備結束監聽了", WARNING);
     if (g_pEventLoop != NULL)
     {
         //打破監聽循環
         event_base_loopbreak(g_pEventLoop);
     }
 }
相關文章
相關標籤/搜索