写这个东西主要是为了学习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 //bind 24 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 //进入listen 35 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 }