1. 程式人生 > >使用libevent和boost編寫一個簡單的tcp伺服器

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

寫這個東西主要是為了學習libevent的基本用法,以及學習下boost的執行緒庫。

程式結構比較簡單:

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

  2. 將這個監聽的socket繫結到一個event事件上,然後等待有客戶過來連線。

  3. 如果響應到監聽socket可讀,則accept嘗試連線的客戶端。

  4. 開啟一個執行緒來處理所有和這個連線過來的客戶端之間的互動。(實際上什麼事情也沒做,就是cout了下每次recv的資料大小)

程式碼如下:

  1. 首先是程式入口,main函式

    main函式主要是註冊了一個監聽使用的socket。另外一旦進入了監聽狀態,就不好退出程式,所以一開始就註冊了一個訊號響應函式,專門用來響應程式退出的訊號。

複製程式碼
 1 //建立監聽socket,然後等待這個socket有客戶來連結
 2 //每個連結一個執行緒去處理 3 int main(int argc, char* argv[])
 4 {
 5     //首先處理好kill -2訊號 6     struct sigaction sigact = {0};
 7     sigact.sa_sigaction = On_Exit;
 8     if ( -1 == sigaction(2, &sigact, NULL))
 9     {
10         log("建立響應函式失敗");
11         return 0;
12     }
13
14 //建立一個非阻塞的socket控制代碼15 int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); 16 if (-1 == sockfd) 17 { 18 log("建立監聽socket失敗", ERROR); 19 return 0; 20 } 21 log("建立監聽socket成功"); 22 23 //bind24 sockaddr_in sinAddr; 25 sinAddr.sin_family = AF_INET; 26
sinAddr.sin_port = htons(LISTEN_PORT); 27 sinAddr.sin_addr.s_addr = inet_addr("172.21.169.160"); 28 if( -1 == bind(sockfd, (sockaddr*)&sinAddr, sizeof(sockaddr_in))) 29 { 30 log("監聽本地埠失敗", ERROR); 31 return 0; 32 } 33 34 //進入listen35 if (-1 == listen(sockfd, SOMAXCONN)) 36 { 37 log("監聽本地埠失敗", ERROR); 38 return 0; 39 } 40 log("監聽本地埠成功"); 41 42 //進入accept狀態43 log("即將進入監聽狀態"); 44 EnterAcceptWhile(sockfd); 45 46 close(sockfd); 47 log("監聽執行緒成功結束了"); 48 return 0; 49 }
複製程式碼

  2.  進入libevent的訊息迴圈

複製程式碼
 1 //基於event的訊息響應機制 2 void EnterAcceptWhile(int nFd)
 3 {
 4    if (g_pEventLoop == NULL)
 5        g_pEventLoop = event_base_new();
 6 
 7    //繫結事件 8    struct event* pListenEvent = event_new(g_pEventLoop, nFd, EV_PERSIST|EV_READ, On_Sock_Accept, NULL);
 9    if (pListenEvent == NULL)
10    {
11        log ("建立監聽事件失敗");
12        return;
13    }
14 
15    //加入監聽事件,持續16    event_add(pListenEvent, NULL);
17 
18    //分派訊息19    int nLoopRst = 0;
20    if (0 == (nLoopRst = event_base_dispatch(g_pEventLoop)))
21    {
22        log("事件迴圈正常停止了");
23    }
24    else if (1 == nLoopRst)
25    {
26        log("沒有事件關聯到這個訊息迴圈了");
27    }
28    else
29    {
30        log("訊息迴圈出現了錯誤", ERROR);
31    }
32 
33    event_free(pListenEvent);
34    event_base_free(g_pEventLoop);
35    g_pEventLoop = NULL;
36 }
複製程式碼

  3. 一旦響應到有客戶端過來連線,就會進入On_Sock_Accept函式。因此這個函式應該儘可能的短小。

複製程式碼
 1 //響應客戶端連線 2 void On_Sock_Accept(int nFd, short sFlags, void* pArg)
 3 {
 4     if (!(sFlags&EV_READ))
 5     {
 6         log("接受到了一個莫名其妙的訊息", WARNING);
 7         return;
 8     }
 9     log("響應到一個客戶端過來連線了"); 
10 
11     socklen_t sockLen;
12     sockaddr sa;
13     int nAcceptFd = accept(nFd, &sa, &sockLen); 
14 
15     //這裡應該啟動一個執行緒來處理這個請求事務的,而不應該在這裡做大量的複雜操作16     SocketThread st(nAcceptFd); 
17     boost::thread thread_ST(st);
18 }
複製程式碼

  4. 在上個函式中,開了一個執行緒專門處理來自這個客戶端的請求。 類SocketThread的程式碼如下:

複製程式碼
 1 SocketThread::SocketThread(int nFd)
 2 {
 3     m_nFd = nFd;
 4 }
 5 
 6 void SocketThread::operator() ()
 7 {
 8     if (m_nFd == -1)
 9         return;
10 
11     //讀取資料,等到讀取完成之後,輸出出來,最後關閉掉socket連線12     char* pszTemp = new char[1024];
13     memset(pszTemp, 0, 1024);
14 
15     int nRecvSize = 0;
16     while( 0 < (nRecvSize = recv(m_nFd, pszTemp, 1024, 0))) 
17     {
18         //讀到資料了19         std::cout << "thread id: " << boost::this_thread::get_id() << " 接受到了:" << nRecvSize << "位元組的資料" << std::endl;
20     }
21 
22     delete[] pszTemp;
23     pszTemp = NULL;
24 
25     if (nRecvSize == 0)
26     {
27         std::cout << "客戶端已經關閉了" << std::endl;
28     }
29     else if (nRecvSize < 0)
30     {
31         std::cout << "接受客戶端資料失敗,請檢查原因" << std::endl;
32     }
33     close(m_nFd);
34     m_nFd = -1;
35 }
複製程式碼

  5. 最後是響應訊號2, 退出libevent的訊息迴圈

  其實這裡存在問題:如果退出訊息迴圈的時候還有很多的工作者執行緒正在執行,應該要先讓他們把事情做完再退出的。

複製程式碼
 1 //響應退出訊息 2 void On_Exit(int nSigId, siginfo_t* pSigInfo, void* pArg)
 3 {
 4     log("準備結束監聽了", WARNING);
 5     if (g_pEventLoop != NULL)
 6     {
 7         //打破監聽迴圈 8         event_base_loopbreak(g_pEventLoop);
 9     }
10 }
複製程式碼