linux下的epoll用法研究:

1. 先写一下epoll比select模型的优点:

    其实select模型也挺好的,但是select有一个限制,那就是最多能同时检查1024个fd,这

linux/posix_types.h中有具体定义如下:
#define __FD_SETSIZE    1024
表示select最多同时监听1024个fd,当然,可以通过修改这个值再重编译内核来扩大这个数目,但这似乎并不治本。

而epoll就没有这个限制,epoll的监听上跟内存大小有关,内存越大,那么可监听的fd数量就越多,同时由于select模型是遍历机制的,所以效率上没有epoll高。

2. epoll的使用:

epoll其实使用起来非常简单,只有3个函数:

创建: int epoll_create(int size);

控制: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

和等待事件:int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);

于是写了个简单的程序测试了下,功能很简单,20000端口监听,打印客户端发送过来的消息,然后向客户端回复消息, 代码如下:

 
  
  1. //======================= server.c ==================== 
  2. #include <stdio.h>  
  3. #include <fcntl.h>  
  4. #include <string.h>  
  5. #include <unistd.h>  
  6.   
  7. #include <sys/epoll.h>  
  8.   
  9. #include <sys/types.h>  
  10. #include <sys/socket.h>  
  11. #include <netinet/in.h>  
  12. #include <arpa/inet.h>  
  13.   
  14.   
  15. const int SERV_PORT = 20000;  
  16. const int LISTEN_BACKLOG = 10;  
  17. const int EPOLL_SIZE = 100;  
  18. const int EPOLL_EVENTS_SIZE = 20;  
  19.   
  20. void setnonblocking(int sockfd)  
  21. {  
  22.     int flags = fcntl(sockfd, F_GETFL, 0);  
  23.     fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);  
  24.     printf("set nonblock success !!\n");  
  25.   
  26.     return;  
  27. }  
  28.   
  29. int main()  
  30. {  
  31.     int servfd, epfd;  
  32.     struct epoll_event ev,events[EPOLL_EVENTS_SIZE];  
  33.     int ret;  
  34.   
  35.     //create socket  
  36.     servfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //TCP SOCKET  
  37.     if(servfd < 0)    
  38.     {  
  39.         printf("create socket failed !!\n");  
  40.         return -1;  
  41.     }  
  42.     printf("create socket success !!\n");  
  43.   
  44.     //set non block  
  45.     setnonblocking(servfd);  
  46.   
  47.     //set reuseaddr  
  48.     int rep = 1;  
  49.     ret = setsockopt(servfd, SOL_SOCKET, SO_REUSEADDR, &rep, sizeof(rep) );  
  50.     if(ret < 0)  
  51.     {  
  52.         printf("set reuse addr failed !!\n");  
  53.         close(servfd);  
  54.         return -1;  
  55.     }  
  56.     printf("set reuse addr success !!\n");  
  57.   
  58.     //init the serv addr struct  
  59.     struct sockaddr_in servAddr;  
  60.     memset(&servAddr, 0, sizeof(servAddr) );  
  61.     servAddr.sin_family = AF_INET;  
  62.     servAddr.sin_port = htons( SERV_PORT );  
  63.     servAddr.sin_addr.s_addr = htons( INADDR_ANY );  
  64.   
  65.     //bind  
  66.     ret = bind(servfd, (struct sockaddr*)&servAddr, sizeof(servAddr) );  
  67.     if(ret < 0)  
  68.     {  
  69.         printf("bind failed !!\n");  
  70.         close(servfd);  
  71.         return -1;  
  72.     }  
  73.     printf("bind success !!\n");  
  74.   
  75.     //listen  
  76.     ret = listen(servfd, LISTEN_BACKLOG);  
  77.     if(ret < 0)  
  78.     {  
  79.         printf("listen failed !!\n");  
  80.         close(servfd);  
  81.         return -1;  
  82.     }  
  83.     printf("begin to listen ... \n");  
  84.   
  85.     //create the epoll  
  86.     epfd = epoll_create( EPOLL_SIZE );  
  87.     if(epfd < 0)  
  88.     {  
  89.         printf("epoll create failed !!\n");  
  90.         close(servfd);  
  91.         return -1;  
  92.     }  
  93.     printf("epoll create success !!\n");  
  94.   
  95.     //add to epoll  
  96.     ev.data.fd = servfd;  
  97.     ev.events = EPOLLIN | EPOLLET;  
  98.     ret = epoll_ctl(epfd, EPOLL_CTL_ADD, servfd, &ev);  
  99.     if(ret < 0)  
  100.     {  
  101.         printf("epoll ctl failed !!\n");  
  102.         close( servfd );  
  103.         close( epfd );  
  104.         return -1;  
  105.     }  
  106.     printf("epoll ctl success !!\n");  
  107.   
  108.     while(true)  
  109.     {  
  110.         int nfds = epoll_wait(epfd, events, EPOLL_EVENTS_SIZE, 500);  
  111.         if(nfds < 0)    //error happens  
  112.         {  
  113.             printf("wait error !!\n");  
  114.             close(servfd);  
  115.             close(epfd);  
  116.             return -1;  
  117.         }  
  118.         if(nfds == 0)   //wait timeout  
  119.         {  
  120.             printf("epoll wait timeout !!\n");  
  121.             sleep(1);  
  122.             continue;  
  123.         }  
  124.   
  125.         printf("%d requests to process ... \n", nfds);  
  126.         for(int index=0; index<nfds; index++)  
  127.         {  
  128.             if(events[index].data.fd == servfd)  
  129.             {  
  130.                 printf("accept ... \n");  
  131.   
  132.                 struct sockaddr_in cliaddr;  
  133.                 memset(&cliaddr, 0, sizeof(cliaddr) );  
  134.                 socklen_t addrlen = sizeof(cliaddr);  
  135.   
  136.                 //accept connect  
  137.                 int confd = accept(servfd, (struct sockaddr*)&cliaddr, &addrlen);  
  138.                 if(confd < 0)  
  139.                 {  
  140.                     printf("accept failed !!\n");  
  141.                     close(servfd);  
  142.                     close(epfd);  
  143.                     return -1;  
  144.                 }  
  145.                 printf("accept success !!\n");  
  146.                 printf("client address : %s--%d \n", inet_ntoa(cliaddr.sin_addr),  
  147.                     ntohs(cliaddr.sin_port));  
  148.   
  149.                 //set non block  
  150.                 setnonblocking(confd);  
  151.   
  152.                 //add to epoll  
  153.                 epoll_event conev;  
  154.                 conev.data.fd = confd;  
  155.                 conev.events = EPOLLET | EPOLLIN;  
  156.                 int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, confd, &conev);  
  157.                 if(ret < 0)  
  158.                 {  
  159.                     printf("add confd to epoll failed !!\n");  
  160.                     close( confd );  
  161.                     close( servfd );  
  162.                     close( epfd );  
  163.                     return -1;  
  164.                 }  
  165.                 printf("add confd to epoll success !!\n");  
  166.             }  
  167.             else  
  168.             {  
  169.                 printf("other request ... \n");  
  170.   
  171.                 if((events[index].events&EPOLLIN) != 0) //通过events来判断是接收还是发送 
  172.                 {  
  173.                     //sleep(5);  
  174.                     printf("begin recv ... \n");  
  175.                     int clifd = events[index].data.fd;  
  176.   
  177.                     char msg[1024*10] = {0};  
  178.                     int nMsg = 0;  
  179.                     while(true)  
  180.                     {  
  181.                         char temp[1024] = {0};  
  182.                         int nrecv = recv(clifd, temp, 1024, 0); 
  183.                         //************************************* 
  184.                         //if(nrecv < 0) //这么判断是有问题的 
  185.                         //{  
  186.                         //  printf("recv error !!\n");  
  187.                         //    close( clifd );  
  188.                         //    close( servfd );  
  189.                         //    close( epfd );  
  190.                         //    return -1;  
  191.                         //} //下面是正确的,非阻塞socket返回-1,同时error为EAGAIN,则认为全部接收: 
  192.                         if(errno == EAGAIN) //non block, recv again 
  193.                         { 
  194.                         printf("eagain, all data has recved !!\n", errno); 
  195.                         break
  196.                         } 
  197.                         else 
  198.                         { 
  199.                         printf("recv error , errno is %d !!\n", errno); 
  200.                         close( clifd ); 
  201.                         close( servfd ); 
  202.                         close( epfd ); 
  203.                         return -1; 
  204.                         } 
  205.                         //************************************* 
  206.  
  207.                         if(nrecv == 0)  
  208.                         {  
  209.                             break;  
  210.                         }  
  211.                         memcpy(msg, temp, nrecv);  
  212.                         nMsg += nrecv;  
  213.                     }  
  214.                     printf("recved %d bytes : %s \n", nMsg, msg);  
  215.                     struct epoll_event recvev;  
  216.                     recvev.data.fd = clifd;  
  217.                     recvev.events = EPOLLET | EPOLLOUT;  
  218.   
  219.                     int ret = epoll_ctl(epfd, EPOLL_CTL_MOD, clifd, &recvev);  
  220.                     if(ret < 0)  
  221.                     {  
  222.                         printf("add confd to epoll failed !!\n");  
  223.                         close( clifd );  
  224.                         close( servfd );  
  225.                         close( epfd );  
  226.                         return -1;  
  227.                     }  
  228.                     printf("add clifd to epoll fro send success !! \n");  
  229.                 }  
  230.                 else if((events[index].events&EPOLLOUT) != 0) //判断是接收还是发送 
  231.                 {  
  232.                     printf("begin to send ... \n");  
  233.                     int clifd = events[index].data.fd; 
  234.                     char pmsg[1024] = {0};  
  235.                     sprintf(pmsg, "%s""copy it !!");  
  236.                     int msglen = strlen( pmsg );  
  237.                     int nsend = send(clifd, pmsg, msglen, 0);  
  238.                     if(nsend <= 0)  
  239.                     {  
  240.                         printf("send error !!\n");  
  241.                         close(servfd);  
  242.                         close(clifd);  
  243.                         close(epfd);  
  244.                         return -1;  
  245.                     }  
  246.                     printf("send success : %d bytes!!\n", nsend);  
  247.                     close(clifd);  
  248.                 }  
  249.             }  
  250.         }  
  251.        usleep( 100 );  
  252.     }  
  253.     close(servfd);  
  254.     close(epfd);  
  255.     printf("finished !!\n"); 
  256.     return 1;  

本来以为只是一个简单的socket服务端程序,结果中间出了两个问题,研究了半天,

一个是非阻塞的socket,当对端发送的数据全部接收到以后,也就是说底层缓冲区中没有数据后,recv调用不是返回0,而是返回-1,并且errno值为EAGAIN。查了半天,还是经验不足导致的。

第二个问题很幼稚,就是判断应该send还是recv的地方,最初的写法是:

events[index].events&EPOLLOUT != 0

可能基础好的人一看就看出来,运算符的优先级不对,会先计算EPOLLOUT!=0部分,返回必然为true,也就是1,而events[index].events如果是EPOLLIN(0x001),那可能还蒙对了,可以进循环,但是如果是EPOLLOUT(0x004),那么循环就进不去,哎,基础不牢,地动山摇啊 。

下面是测试用的client端的代码(这个就是最简单的tcp client代码了 ),如下:

 
  
  1. //======================== client.c ======================
  2. #include <sys/socket.h> 
  3. #include <sys/types.h> 
  4. #include <arpa/inet.h> 
  5.  
  6. #include <stdio.h> 
  7. #include <stdlib.h> 
  8. #include <unistd.h> 
  9. #include <string.h> 
  10.  
  11. const int SERV_PORT = 20000; 
  12. const int MSG_LEN = 1024; 
  13.  
  14.  
  15. int main() 
  16.     int clifd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
  17.     if(clifd < 0) 
  18.     { 
  19.         printf("create socket failed !!\n"); 
  20.         return -1; 
  21.     } 
  22.     printf("create socket success !!\n"); 
  23.  
  24.     struct sockaddr_in servaddr; 
  25.     servaddr.sin_family = AF_INET; 
  26.     servaddr.sin_port = htons( SERV_PORT ); 
  27.     servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 
  28.  
  29.     int ret = connect(clifd, (struct sockaddr*)&servaddr, sizeof(servaddr) ); 
  30.     if(ret < 0) 
  31.     { 
  32.         printf("connect failed !!\n"); 
  33.         close( clifd ); 
  34.         return -1; 
  35.     } 
  36.     printf("connect success !!\n"); 
  37.  
  38.     char msg[1024] = "fuck you!!"
  39.     int msglen = strlen(msg); 
  40.     int nsend = send(clifd, msg, msglen, 0); 
  41.     if(nsend < 0) 
  42.     { 
  43.         printf("send failed !!\n"); 
  44.         close(clifd); 
  45.         return -1; 
  46.     } 
  47.     printf("send success : %d \n", nsend); 
  48.  
  49.     memset(msg, 0, 1024); 
  50.  
  51.     int nrecv = recv(clifd, msg, 1024, 0); 
  52.     if(nrecv < 0) 
  53.     { 
  54.         printf("recv failed !!\n"); 
  55.         close(clifd); 
  56.         return -1; 
  57.     } 
  58.     printf("recved %d bytes : %s .\n", nrecv, msg); 
  59.  
  60.     close(clifd); 
  61.     return 1; 

以上是epoll的最基本的用法,当然,在实际应用中不可能这么简单,还会有涉及到多线程的情况,更复杂的设计,会在时间空余的时候继续研究。