Chinaunix首页 | 论坛 | 博客
  • 博客访问: 202539
  • 博文数量: 37
  • 博客积分: 4624
  • 博客等级: 上校
  • 技术积分: 433
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-10 14:06
文章分类

全部博文(37)

文章存档

2012年(6)

2011年(25)

2008年(6)

我的朋友

分类: LINUX

2012-02-01 17:05:48

epoll的接口非常简单,一共就三个函数。
epoll用到的所有函数都是在头文件sys/epoll.h中声明:
int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。
需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,
在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值,
第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,
struct epoll_event结构如下:
typedef union epoll_data
{
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;
struct epoll_event
{
    uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生。
参数events用来从内核得到事件的集合,
maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。
该函数返回需要处理的事件数目,如返回0表示已超时。

ET和LT
EPOLL事件有两种模型:
Edge Triggered (ET)
Level Triggered (LT)
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket。
在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。
如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。
ET(edge-triggered) 是高速工作方式,只支持no-block socket。
在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。
然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。
另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,
读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,
那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取。
还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),
由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发给接收端,
这样不断的读和发,当缓冲区满后会产生EAGAIN错误,同时,不理会这次请求发送的数据。
所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回- 1表示出错。
在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试。
这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send()内部,但暂没有更好的办法.
epoll_wait运行的原理是
等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。
并且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,
则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型。
这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事件类型清空。这一步非常重要。
 
实例代码:
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <fcntl.h>
  4. #include <sys/epoll.h>
  5. #include <sys/socket.h>
  6. #include <netinet/in.h>

  7. #define MAX_SOCKET 10000

  8. void
  9. add_event(int epfd, int fd, struct epoll_event *event)
  10. {
  11.     epoll_ctl(epfd, EPOLL_CTL_ADD, fd, event);
  12. }

  13. void
  14. mod_event(int epfd, int fd, struct epoll_event *event)
  15. {
  16.     epoll_ctl(epfd, EPOLL_CTL_MOD, fd, event);
  17. }

  18. void
  19. del_event(int epfd, int fd, struct epoll_event *event)
  20. {
  21.     epoll_ctl(epfd, EPOLL_CTL_DEL, fd, event);
  22. }

  23. int
  24. init_listen(int epfd, int port)
  25. {
  26.     
  27.     int listenfd = socket(AF_INET, SOCK_STREAM, 0);
  28.     fcntl(listenfd, F_SETFL, O_NONBLOCK);

  29.     struct epoll_event event;
  30.     event.data.fd = listenfd;
  31.     event.events = EPOLLIN | EPOLLET;    
  32.     add_event(epfd, listenfd, &event);

  33.     struct sockaddr_in serveraddr;
  34.     memset(&serveraddr, 0, sizeof(struct sockaddr_in));
  35.     serveraddr.sin_family = AF_INET;
  36.     inet_aton("127.0.0.1", &(serveraddr.sin_addr));
  37.     serveraddr.sin_port = htons(port);    
  38.     bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in));
  39.     listen(listenfd, 20);
  40.     return listenfd;
  41. }

  42. void
  43. accept_conn(int epfd, int listenfd)
  44. {
  45.     socklen_t length;
  46.     struct sockaddr_in clientaddr;
  47.     memset(&clientaddr, 0, sizeof(struct sockaddr_in));
  48.     int fd = accept(listenfd, (struct sockaddr *)&clientaddr, &length);
  49.     fcntl(fd, F_SETFL, O_NONBLOCK);

  50.     char *clientip = inet_ntoa(clientaddr.sin_addr);
  51.     unsigned short clientport = ntohs(clientaddr.sin_port);
  52.     printf("client connected: %s:%d\n", clientip, clientport);
  53.     
  54.     struct epoll_event event;
  55.     event.data.fd = fd;
  56.     event.events = EPOLLIN | EPOLLET;
  57.     add_event(epfd, fd, &event);
  58. }

  59. void
  60. recv_data(int epfd, int fd)
  61. {
  62.     char buffer[1024];
  63.     memset(buffer, 0, sizeof(buffer));
  64.     
  65.     ssize_t count = read(fd, buffer, sizeof(buffer));
  66.     if (count <= 0) {
  67.         close(fd);
  68.         return;
  69.     }

  70.     printf("fd %d recv: %s\n", fd, buffer);

  71.     struct epoll_event event;
  72.     event.data.fd = fd;
  73.     event.events = EPOLLOUT | EPOLLET;
  74.     mod_event(epfd, fd, &event);
  75. }

  76. void
  77. send_data(int epfd, int fd)
  78. {
  79.     char buffer[1024];
  80.     memset(buffer, 0, sizeof(buffer));
  81.     sprintf(buffer, "hello fd %d", fd);
  82.     ssize_t count = write(fd, buffer, strlen(buffer));
  83.     if (count <= 0) {
  84.         close(fd);
  85.         return;
  86.     }

  87.     printf("fd %d send: %s\n", fd, buffer);

  88.     struct epoll_event event;
  89.     event.data.fd = fd;
  90.     event.events = EPOLLIN | EPOLLET;
  91.     mod_event(epfd, fd, &event);
  92. }

  93. int
  94. main()
  95. {
  96.     int port = 12345;
  97.     int epfd = epoll_create(MAX_SOCKET);
  98.     int listenfd = init_listen(epfd, port);
  99.     printf("fd %d listen: %d\n", listenfd, port);
  100.     
  101.     while (1) {
  102.         printf("epoll_wait...\n");
  103.         struct epoll_event events[20];
  104.         int fds = epoll_wait(epfd, events, 20, 5000);
  105.         printf("epoll_wait fds: %d\n", fds);
  106.         
  107.         int i;
  108.         for (i = 0; i < fds; i++) {
  109.             int fd = events[i].data.fd;
  110.             if (fd == listenfd) {
  111.                 printf("accept_conn...\n");
  112.                 accept_conn(epfd, listenfd);
  113.                 continue;
  114.             }
  115.             if (events[i].events & EPOLLIN) {
  116.                 printf("recv_data fd: %d\n", fd);
  117.                 recv_data(epfd, fd);
  118.             }
  119.             if (events[i].events & EPOLLOUT) {
  120.                 printf("send_data fd: %d\n", fd);
  121.                 send_data(epfd, fd);
  122.             }
  123.         }
  124.     }

  125.     close(epfd);
  126. }

阅读(3254) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

校长的马夹2012-02-01 23:21:03

哎呀~喜欢接口简单的函数^_^