epoll用法整理 实现回声服务端

http://blog.csdn.net/chenxun_2010/article/details/50493481

1、epoll是什么?

epoll是当前在Linux下开发大规模并发网络程序的热门人选,epoll Linux2.6内核中正式引入,和select相似,都是I/O多路复用(IO multiplexing)技术

Linux下设计并发网络程序,常用的模型有:

      Apache模型(Process Per Connection,简称PPC

     TPCThread PerConnection)模型

     select模型和poll模型。

     epoll模型

2、常用模型的缺点

   PPC/TPC模型

       这两种模型思想类似,就是让每一个到来的连接都有一个进程/线程来服务。这种模型的代价是它要时间和空间。连接较多时,进程/线程切换的开销比较大。因此这类模型能接受的最大连接数都不会高,一般在几百个左右。

   select模型

          最大并发数限制:因为一个进程所打开的fd(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此select模型的最大并发数就被相应限制了。

          效率问题select每次调用都会线性扫描全部fd集合,这样效率就会呈现线性下降,FD_SETSIZE改大可能造成这些fd都超时了。

          内核/用户空间内存拷贝问题:如何让内核把fd消息通知给用户空间呢?在这个问题上select采取了内存拷贝方法。

    poll模型

         基本上效率和select是相同的,select缺点的23它都没有改掉。

    epoll的改进

对比其他模型的问题,epoll的改进如下:

       epoll没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于2048, 一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。

       效率提升,Epoll最大的优点就在于它只管你活跃的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于selectpoll

       内存拷贝,Epoll在这点上使用了“共享内存”,这个内存拷贝也省略了。

 

3、 epoll为什么高效

epoll的高效和其数据结构的设计是密不可分的。

首先回忆一下select模型,当有I/O事件到来时,select通知应用程序有事件到了,应用程序必须轮询所有的fd集合,测试每个fd是否有事件发生,并处理事件;代码像下面这样:

int  res = select(maxfd+1, &readfds, NULL, NULL, 120);

if (res > 0)

{

    for (int i = 0; i < MAX_CONNECTION; i++)

    {

        if (FD_ISSET(allConnection[i],&readfds))

        {

            handleEvent(allConnection[i]);

        }

    }

}

// if(res == 0) handle timeout, res < 0 handle error

 

epoll不仅会告诉应用程序有I/0事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个fd集合。

int res = epoll_wait(epfd, events, 20, 120);

for (int i = 0; i < res;i++)

{

    handleEvent(events[n]);

}

 epoll关键数据结构

前面提到epoll速度快和其数据结构密不可分,其关键数据结构就是:

struct  epoll_event {

   __uint32_t      events;  //epoll events

   epoll_data_t   data;   //user data variable

};

 

typedef  union epoll_data {

   void*   ptr;

   int      fd;

   __uint32_t  u32;

   __uint64_t  u64;

}epoll_data_t;

可见epoll_data是一个union结构体,借助于它应用程序可以保存很多类型的信息:fd、指针等等。有了它,应用程序就可以直接定位目标了。

 

使用epoll

epollAPI:

int  epoll_create(int  size);

int  epoll_ctl(int epfd, int op, int fd, structepoll_event *event);

int  epoll_wait(int epfd, struct epoll_event* events, int maxevents. int timeout);

int  epoll_create(int size);


创建一个epoll的文件描述符,参数size告诉内核这个监听的数目共有多大。

  int  epoll_ctl(int epfd, int op, int fd, structepoll_event *event);

     epoll的事件注册函数。

     参数epfd是epoll_create返回值。

    参数op为

          EPOLL_CTL_ADD 注册新的fd到epfd中

          EPOLL_CTL_MOD 修改已经注册的fd的监听事件

          EPOLL_CTL_DEL 从epfd中删除一个fd

参数fd是需要监听文件描述符。

参数event是告诉内核需要监听什么事件。event->events的不同的值表示对应的文件描述符的不同事件:

   EPOLLIN  可以读(包括对端Socket正常关闭)

   EPOLLOUT 可以写

   EPOLLPRI有紧急的数据可读(有带外数据OOB到来,TCP中的URG包)

   EPOLLERR该文件描述符发生错误

   EPOLLHUP该文件描述符被挂断

   EPOLLET     将epoll设置为边缘触发(Edge Triggered)模式。

   EPOLLONESHOT只监听一次事件,监听完之后,如果还想监听需要再次把该文件描述符加入到epoll队列中

 

int  epoll_wait(int epfd, struct epoll_event* events, int maxevents. int timeout);

等待事件的产生。

参数events用来从内核得到事件的集合

参数maxevents告之内核这个events有多大(maxevents不能大于size)

参数timeout是超时时间(毫秒)

 

epoll的模式:

       LT模式:Level Triggered水平触发

            这个是缺省的工作模式。同时支持block socket和non-block socket。内核会告诉程序员一个文件描述符是否就绪了。如果程序员不作任何操作,内核仍会通知。

       ET模式:Edge Triggered 边缘触发

                是一种高速模式。仅当状态发生变化的时候才获得通知。这种模式假定程序员在收到一次通知后能够完整地处理事件,于是内核不再通知这一事件。注意:缓冲区中还          有未处理的数据不算状态变化,所以ET模式下程序员只读取了一部分数据就再也得不到通知了,正确的用法是程序员自己确认读完了所有的字节(一直调用read/write直到          出错EAGAIN为止)。

 

 

一个例子:

[cpp]  view plain  copy
  1. #include <netdb.h>  
  2.   
  3. #include <sys/socket.h>  
  4.   
  5. #include <sys/epoll.h>  
  6.   
  7. #include <netinet/in.h>  
  8.   
  9. #include <arpa/inet.h>  
  10.   
  11. #include <fcntl.h>  
  12.   
  13. #include <unistd.h>  
  14.   
  15. #include <stdio.h>  
  16.   
  17. #include <string.h>  
  18.   
  19. #include <stdlib.h>  
  20.   
  21. #include <errno.h>  
  22.   
  23.    
  24.   
  25. /*创建并绑定一个socket作为服务器。 */  
  26.   
  27. static int  create_and_bind (char *port){  
  28.   
  29.     struct  addrinfo hints;  
  30.   
  31.     struct  addrinfo *result, *rp;  
  32.   
  33.     int  s, sfd;  
  34.   
  35.     memset (&hints, 0, sizeof (struct addrinfo));  
  36.   
  37.     hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */  
  38.   
  39.     hints.ai_socktype = SOCK_STREAM; /* 设置为STREAM模式,即TCP链接 */  
  40.   
  41.     hints.ai_flags = AI_PASSIVE;     /* All interfaces */  
  42.   
  43.     s = getaddrinfo (NULL, port, &hints, &result);//获得本地主机的地址  
  44.   
  45.     if (s != 0){  
  46.   
  47.         fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));  
  48.   
  49.         return -1;  
  50.   
  51.     }  
  52.   
  53.     for (rp = result; rp != NULL; rp = rp->ai_next){//本地主机地址可能有多个,任意绑定一个即可  
  54.   
  55.         sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); //创建socket  
  56.   
  57.         if (sfd == -1)  
  58.   
  59.             continue;  
  60.   
  61.         s = bind (sfd, rp->ai_addr, rp->ai_addrlen); //并绑定socket  
  62.   
  63.         if (s == 0)  
  64.   
  65.         {  
  66.   
  67.             /* 绑定成功 */  
  68.   
  69.             break;  
  70.   
  71.         }  
  72.   
  73.         close (sfd);  
  74.   
  75.     }  
  76.   
  77.     if (rp == NULL){  
  78.   
  79.         fprintf (stderr, "Could not bind\n");  
  80.   
  81.         return -1;  
  82.   
  83.     }  
  84.   
  85.     freeaddrinfo (result);  
  86.   
  87.     return sfd;  
  88.   
  89. }  
  90.   
  91.    
  92.   
  93. /* 
  94.  
  95.    设置socket为非阻塞模式。 
  96.  
  97.    先get flag,或上O_NONBLOCK 再set flag。 
  98.  
  99.  */  
  100.   
  101. static  int   make_socket_non_blocking (int sfd) {  
  102.   
  103.    
  104.   
  105.     int flags, s;  
  106.   
  107.     flags = fcntl (sfd, F_GETFL, 0);  
  108.   
  109.     if (flags == -1){  
  110.   
  111.         perror ("fcntl");  
  112.   
  113.         return -1;  
  114.   
  115.     }  
  116.   
  117.     flags |= O_NONBLOCK;  
  118.   
  119.     s = fcntl (sfd, F_SETFL, flags);  
  120.   
  121.     if (s == -1){  
  122.   
  123.         perror ("fcntl");  
  124.   
  125.         return -1;  
  126.   
  127.     }  
  128.   
  129.     return 0;  
  130.   
  131. }  
  132.   
  133.    
  134.   
  135.    
  136.   
  137. #define  MAXEVENTS 64  
  138.   
  139.    
  140.   
  141. /* 
  142.  
  143.    用法: ./epoll_test 8080 
  144.  
  145.  */  
  146.   
  147. int  main (int argc, char *argv[]) {  
  148.   
  149.     int sfd, s;  
  150.   
  151.     int efd;  
  152.   
  153.     struct  epoll_event event;  
  154.   
  155.     struct  epoll_event *events;  
  156.   
  157.     if (argc != 2) {  
  158.   
  159.         fprintf (stderr, "Usage: %s [port]\n", argv[0]);  
  160.   
  161.         exit (EXIT_FAILURE);  
  162.   
  163.     }  
  164.   
  165.     sfd = create_and_bind (argv[1]); //sfd为绑定后等待连接接入的文件描述符  
  166.   
  167.    
  168.   
  169.     s = make_socket_non_blocking (sfd);  
  170.   
  171.     s = listen (sfd, SOMAXCONN);  
  172.   
  173.     efd = epoll_create1 (0);  
  174.   
  175.     event.data.fd = sfd;  
  176.   
  177.     event.events = EPOLLIN | EPOLLET;  
  178.   
  179.     s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);  
  180.   
  181.     /* Buffer where events are returned,为events数组分配内存 */  
  182.   
  183.     events = (struct  epoll_event*)calloc (MAXEVENTS, sizeof event);  
  184.   
  185.     /* The event loop 事件循环*/  
  186.   
  187.     while (1) {  
  188.   
  189.         int n, i;  
  190.   
  191.         n = epoll_wait (efd, events, MAXEVENTS, -1);  
  192.   
  193.         for (i = 0; i < n; i++) {  
  194.   
  195.             if ((events[i].events & EPOLLERR) ||  (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))) {  
  196.   
  197.               /* 发生了错误或者被挂断,或者没有数据可读  An error has occured on this fd, or the socket is not ready for reading (why were we notified then?) */  
  198.   
  199.                 fprintf (stderr, "epoll error\n");  
  200.   
  201.                 close (events[i].data.fd);  
  202.   
  203.                 continue;  
  204.   
  205.             }else if (sfd == events[i].data.fd) {//新连接  
  206.   
  207.               /* sfd上有数据可读,则表示有新连接 
  208.  
  209.                * We have a notification on the listening socket, 
  210.  
  211.                * which means one or more incoming connections. */  
  212.   
  213.                 printf("Incoming connection !\n");  
  214.   
  215.                 while (1) {  
  216.   
  217.                     struct sockaddr in_addr;  
  218.   
  219.                     socklen_t in_len;  
  220.   
  221.                     int infd;  
  222.   
  223.                     char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];  
  224.   
  225.                     in_len = sizeof in_addr;  
  226.   
  227.                     infd = accept (sfd, &in_addr, &in_len); //读取到来的连接socket fd。  
  228.   
  229.                     if (infd == -1) {  
  230.   
  231.                         if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {  
  232.   
  233.                             /* 已经读完了sfd上的所有数据(所有连接)。最后一次读(非阻塞读)会返回EAGAIN(=EWOULDBLOCK) 
  234.  
  235.                              * We have processed all incoming connections. */  
  236.   
  237.                             break;  
  238.   
  239.                         } else  {  
  240.   
  241.                             perror ("accept");  
  242.   
  243.                             break;  
  244.   
  245.                         }  
  246.   
  247.                     }  
  248.   
  249.                     s = getnameinfo (&in_addr, in_len, hbuf, sizeof hbuf, sbuf, sizeof sbuf, NI_NUMERICHOST | NI_NUMERICSERV);  
  250.   
  251.                     if (s == 0) {  
  252.   
  253.                         printf("Accepted connection on descriptor %d (host=%s, port=%s)\n", infd, hbuf, sbuf);  
  254.   
  255.                     }  
  256.   
  257.                     s = make_socket_non_blocking (infd);  //设置socket为非阻塞模式  
  258.   
  259.                     event.data.fd = infd;  //将data部分设置为fd  
  260.   
  261.                     event.events = EPOLLIN | EPOLLET;  //监听EPOLLIN事件,使用边缘触发模式  
  262.   
  263.                     s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);  
  264.   
  265.                 }  
  266.   
  267.                 continue;  
  268.   
  269.             } else   
  270.             {//有客户端发来数据  
  271.   
  272.                 /* 有客户端发来数据,因为处于ET模式,所以必须完全读取所有数据(要不然,剩下一部分数据后,就无法再收到内核通知了)。*/  
  273.   
  274.                 int conn = events[i].data.fd;  
  275.                 if (conn < 0)  
  276.                     continue;  
  277.   
  278.                 char recvbuf[1024] = {0};  
  279.                 int ret = read(conn, recvbuf, 1024);  
  280.                 if (ret == -1)  
  281.                     //ERR_EXIT("readline");  
  282.                 if (ret == 0)  
  283.                 {  
  284.                     printf("client close\n");  
  285.                     close(conn);  
  286.   
  287.                     event = events[i];  
  288.                     epoll_ctl(efd, EPOLL_CTL_DEL, conn, &event);  
  289.                     //clients.erase(std::remove(clients.begin(), clients.end(), conn), clients.end());  
  290.                 }  
  291.   
  292.                 fputs(recvbuf, stdout);  
  293.                 write(conn, recvbuf, strlen(recvbuf));  
  294.   
  295.                   
  296.   
  297.             }  
  298.   
  299.         }  
  300.   
  301.     }  
  302.   
  303.     free (events);//释放内存  
  304.   
  305.     close (sfd);   //关闭sfd  
  306.   
  307.     return EXIT_SUCCESS;  
  308.   
  309. }  
  310.   
  311.    


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值