Java网络编程和NIO详解6:Linux epoll实现原理详解

微信公众号【黄小斜】做者是蚂蚁金服 JAVA 工程师,目前在蚂蚁财富负责后端开发工做,专一于 JAVA 后端技术栈,同时也懂点投资理财,坚持学习和写做,用大厂程序员的视角解读技术与互联网,个人世界里不仅有 coding!关注公众号后回复”架构师“便可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送做者原创的Java学习指南、Java程序员面试指南等干货资源node

Linux epoll实现原理详解

在linux 没有实现epoll事件驱动机制以前,咱们通常选择用select或者poll等IO多路复用的方法来实现并发服务程序。在大数据、高并发、集群等一些名词唱得火热之年代,select和poll的用武之地愈来愈有限,风头已经被epoll占尽。linux

本文便来介绍epoll的实现机制,并附带讲解一下select和poll。经过对比其不一样的实现机制,真正理解为什么epoll能实现高并发。程序员

这部分转自https://jeff.wtf/2017/02/IO-multiplexing/面试

为何要 I/O 多路复用

当须要从一个叫 r_fd 的描述符不停地读取数据,并把读到的数据写入一个叫 w_fd 的描述符时,咱们能够用循环使用阻塞 I/O :数据库

123
while((n = read(rfd, buf, BUFSIZE)) > 0) if(write(wfd, buf, n) != n) errsys("write error");

可是,若是要从两个地方读取数据呢?这时,不能再使用会把程序阻塞住的 read 函数。由于可能在阻塞地等待 r_fd1 的数据时,来不及处理 r_fd2,已经到达的 r_fd2 的数据可能会丢失掉。编程

这个状况下须要使用非阻塞 I/O。后端

只要作个标记,把文件描述符标记为非阻塞的,之后再对它使用 read 函数:若是它尚未数据可读,函数会当即返回并把 errorno 这个变量的值设置为 35,因而咱们知道它没有数据可读,而后能够立马去对其余描述符使用 read;若是它有数据可读,咱们就读取它数据。对全部要读的描述符都调用了一遍 read 以后,咱们能够等一个较长的时间(好比几秒),而后再从第一个文件描述符开始调用 read 。这种循环就叫作轮询(polling)。数组

这样,不会像使用阻塞 I/O 时那样由于一个描述符 read 长时间处于等待数据而使程序阻塞。服务器

轮询的缺点是浪费太多 CPU 时间。大多数时候咱们没有数据可读,可是仍是用了 read 这个系统调用,使用系统调用时会从用户态切换到内核态。而大多数状况下咱们调用 read,而后陷入内核态,内核发现这个描述符没有准备好,而后切换回用户态而且只获得 EAGAIN (errorno 被设置为 35),作的是无用功。描述符很是多的时候,每次的切换过程就是巨大的浪费。微信

因此,须要 I/O 多路复用。I/O 多路复用经过使用一个系统函数,同时等待多个描述符的可读、可写状态。

为了达到这个目的,咱们须要作的是:创建一个描述符列表,以及咱们分别关心它们的什么事件(可读仍是可写仍是发生例外状况);调用一个系统函数,直到这个描述符列表里有至少一个描述符关联的事件发生时,这个函数才会返回。

select, poll, epoll 就是这样的系统函数。

select,poll,epoll 源码分析

select "select")select

咱们能够在全部 POSIX 兼容的系统里使用 select 函数来进行 I/O 多路复用。咱们须要经过 select 函数的参数传递给内核的信息有:

  • 咱们关心哪些描述符
  • 咱们关心它们的什么事件
  • 咱们但愿等待多长时间

select 的返回时,内核会告诉咱们:

  • 可读的描述符的个数
  • 哪些描述符发生了哪些事件
123456
#include 
     
     
     
     
      
      
      
      int select(int maxfdp1, fd
      
      
      
      set* readfds, fdset
      
      
      
       writefds, fd_set exceptfds, struct timeval* timeout);// 返回值: 已就绪的描述符的个数。超时时为 0 ,错误时为 -1
     
     
     
     

maxfdp1 意思是 “max file descriptor plus 1” ,就是把你要监视的全部文件描述符里最大的那个加上 1 。(它实际上决定了内核要遍历文件描述符的次数,好比你监视了文件描述符 5 和 20 并把 maxfdp1 设置为 21 ,内核每次都会从描述符 0 依次检查到 20。)

中间的三个参数是你想监视的文件描述符的集合。能够把 fdset 类型视为 1024 位的二进制数,这意味着 select 只能监视小于 1024 的文件描述符(1024 是由 Linux 的 sys/select.h 里 `FDSETSIZE 宏设置的值)。在 select 返回后咱们经过 FD_ISSET` 来判断表明该位的描述符是不是已准备好的状态。

最后一个参数是等待超时的时长:到达这个时长可是没有任一描述符可用时,函数会返回 0 。

用一个代码片断来展现 select 的用法:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
// 这个例子要监控文件描述符 3, 4 的可读状态,以及 4, 5 的可写状态// 初始化两个 fdset 以及 timevalfdset readset, writeset;FDZERO(readset);FDZERO(writeset);timeval t;t.tvsec = 5;   // 超时为 5 秒t.tvusec = 0;  // 加 0 微秒// 设置好两个 fdsetint fd1 = 3;int fd2 = 4;int fd3 = 5;int maxfdp1 = 5 + 1;FDSET(fd1, &readset);FDSET(fd2, &readset);FDSET(fd2, &writeset);FDSET(fd3, &writeset);// 准备备用的 fdsetfdset rtemp = readset;fdset wtemp = writeset;while(true){ // 每次都要从新设置放入 select 的 fdset readset = rtemp; writeset = wtemp; // 使用 select int n = select(maxfdp1, &readset, &writeset, NULL, &t); // 上面的 select 函数会一直阻塞,直到 // 3, 4 可读以及 4, 5 可写这四件事中至少一项发生 // 或者等待时间到达 5 秒,返回 0 for(int i=0; i
      
      
      
      
        0; i++){ if(FD 
      ISSET(i, &readset)){ n--; if(i==fd1) prinf("描述符 3 可读"); if(i==fd2) prinf("描述符 4 可读"); } if(FDISSET(i, &write_set)){ n--; if(i==fd2) prinf("描述符 3 可写"); if(i==fd3) prinf("描述符 4 可写"); } } // 上面的 printf 语句换成对应的 read 或者 write 函数就 // 能够当即读取或者写入相应的描述符而不用等待}

能够看到,select 的缺点有:

  • 默认能监视的文件描述符不能大于 1024,也表明监视的总数不超过1024。即便你由于须要监视的描述符大于 1024 而改动内核的 FD_SETSIZE 值,但因为 select 是每次都会线性扫描整个fd_set,集合越大速度越慢,因此性能会比较差。
  • select 函数返回时只能看见已准备好的描述符数量,至因而哪一个描述符准备好了须要循环用 FD_ISSET 来检查,当未准备好的描述符不少而准备好的不多时,效率比较低。
  • select 函数每次执行的时候,都把参数里传入的三个 fdset 从用户空间复制到内核空间。而每次 fdset 里要监视的描述符变化不大时,所有从新复制一遍并不划算。一样在每次都是未准备好的描述符不少而准备好的不多时,调用 select 会很频繁,用户/内核间的的数据复制就成了一个大的开销。

还有一个问题是在代码的写法上给我一些困扰的,就是每次调用 select 前必须从新设置三个 fdset。 fdset 类型只是 1024 位的二进制数(实际上结构体里是几个 long 变量的数组;好比 64 位机器上 long 是 64 bit,那么 fd_set 里就是 16 个 long 变量的数组),由一位的 1 和 0 表明一个文件描述符的状态,可是其实调用 select 先后位的 1/0 状态意义是不同的。

先讲一下几个对 fdset 操做的函数的做用:`FDZERO 把 fd_set 全部位设置为 0 ;FDSET 把一个位设置为 1 ;FDISSET` 判断一个位是否为 1 。

调用 select 前:咱们用 FD_ZERO 把 fdset 先所有初始化,而后用 `FDSET 把咱们关心的表明描述符的位设置为 1 。咱们这时能够用 FD_ISSET` 判断这个位是否被咱们设置,这时的含义是咱们想要监视的描述符是否被设置为被监视的状态。

调用 select 时:内核判断 fdset 里的位并把各个 fdset 里全部值为 1 的位记录下来,而后把 fdset 所有设置成 0 ;一个描述符上有对应的事件发生时,把对应 fdset 里表明这个描述符的位设置为 1 。

在 select 返回以后:咱们一样用 FD_ISSET 判断各个咱们关心的位是 0 仍是 1 ,这时的含义是,这个位是不是发生了咱们关心的事件。

因此,在下一次调用 select 前,咱们不得不把已经被内核改掉的 fd_set 所有从新设置一下。

select 在监视大量描述符尤为是更多的描述符未准备好的状况时性能不好。《Unix 高级编程》里写,用 select 的程序一般只使用 3 到 10 个描述符。

poll "poll")poll

poll 和 select 是类似的,只是给的接口不一样。

1234
#include 
     
     
     
     
      
      
      
      int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);// 返回值: 已就绪的描述符的个数。超时时为 0 ,错误时为 -1
     
     
     
     

fdarray 是 pollfd 的数组。pollfd 结构体是这样的:

12345
struct pollfd { int fd;         // 文件描述符 short events;   // 我期待的事件 short revents;  // 实际发生的事件:我期待的事件中发生的;或者异常状况};

nfds 是 fdarray 的长度,也就是 pollfd 的个数。

timeout 表明等待超时的毫秒数。

相比 select ,poll 有这些优势:因为 poll 在 pollfd 里用 int fd 来表示文件描述符而不像 select 里用的 fd_set 来分别表示描述符,因此没有必须小于 1024 的限制,也没有数量限制;因为 poll 用 `events` 表示期待的事件,经过修改 `revents` 来表示发生的事件,因此不须要像 select 在每次调用前从新设置描述符和期待的事件。

除此以外,poll 和 select 几乎相同。在 poll 返回后,须要遍历 fdarray 来检查各个 pollfd 里的 revents 是否发生了期待的事件;每次调用 poll 时,把 fdarray 复制到内核空间。在描述符太多而每次准备好的较少时,poll 有一样的性能问题。

epoll "epoll")epoll

epoll 是在 Linux 2.5.44 中首度登场的。不像 select 和 poll ,它提供了三个系统函数而不是一个。

epoll-create-%E7%94%A8%E6%9D%A5%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA-epoll-%E6%8F%8F%E8%BF%B0%E7%AC%A6%EF%BC%9A "epoll create 用来建立一个 epoll 描述符:")epollcreate 用来建立一个 epoll 描述符:

1234
#include 
     
     
     
     
      
      
      
      int epoll_create(int size);// 返回值:epoll 描述符
     
     
     
     

size 用来告诉内核你想监视的文件描述符的数目,可是它并非限制了能监视的描述符的最大个数,而是给内核最初分配的空间一个建议。而后系统会在内核中分配一个空间来存放事件表,并返回一个 epoll 描述符,用来操做这个事件表。

epoll-ctl-%E7%94%A8%E6%9D%A5%E5%A2%9E-%E5%88%A0-%E6%94%B9%E5%86%85%E6%A0%B8%E4%B8%AD%E7%9A%84%E4%BA%8B%E4%BB%B6%E8%A1%A8%EF%BC%9A "epoll ctl 用来增/删/改内核中的事件表:")epollctl 用来增/删/改内核中的事件表:

123
int epollctl(int epfd, int op, int fd, struct epollevent *event);// 返回值:成功时返回 0 ,失败时返回 -1

epfd 是 epoll 描述符。

op 是操做类型(增长/删除/修改)。

fd 是但愿监视的文件描述符。

event 是一个 epollevent 结构体的指针。epollevent 的定义是这样的:

1234567891011
typedef union epolldata { void        *ptr; int          fd; uint32t     u32; uint64t     u64;} epolldatat;struct epollevent { uint32t     events;      // 我期待的事件 epolldata_t data;        // 用户数据变量};

这个结构体里,除了期待的事件外,还有一个 data ,是一个 union,它是用来让咱们在获得下面第三个函数的返回值之后方便的定位文件描述符的。

epoll-wait-%E7%94%A8%E6%9D%A5%E7%AD%89%E5%BE%85%E4%BA%8B%E4%BB%B6 "epoll wait 用来等待事件")epollwait 用来等待事件

1234
int epollwait(int epfd, struct epollevent *result_events, int maxevents, int timeout);// 返回值:已就绪的描述符个数。超时时为 0 ,错误时为 -1

epfd 是 epoll 描述符。

result_events 是 epollevent 结构体的指针,它将指向的是全部已经准备好的事件描述符相关联的 epollevent(在上个步骤里调用 epoll_ctl 时关联起来的)。下面的例子可让你知道这个参数的意义。

maxevents 是返回的最大事件个数,也就是你能经过 result_events 指针遍历到的最大的次数。

timeout 是等待超时的毫秒数。

用一个代码片断来展现 epoll 的用法:

123456789101112131415161718192021222324252627282930313233343536373839404142434445
// 这个例子要监控文件描述符 3, 4 的可读状态,以及 4, 5 的可写状态/ 经过 epoll_create 建立 epoll 描述符 /int epfd = epollcreate(4);int fd1 = 3;int fd2 = 4;int fd3 = 5;/* 经过 epollctl 注册好四个事件 /struct epollevent ev1;ev1.events = EPOLLIN; // 期待它的可读事件发生ev1.data = fd1; // 咱们一般就把 data 设置为 fd ,方便之后查看epollctl(epfd, EPOLLCTLADD, fd1, &ev1);  // 添加到事件表struct epollevent ev2;ev2.events = EPOLLIN;ev2.data = fd2;epollctl(epfd, EPOLLCTLADD, fd2, &ev2);struct epollevent ev3;ev3.events = EPOLLOUT; // 期待它的可写事件发生ev3.data = fd2;epollctl(epfd, EPOLLCTLADD, fd2, &ev3);struct epollevent ev4;ev4.events = EPOLLOUT;ev4.data = fd3;epollctl(epfd, EPOLLCTLADD, fd3, &ev4);/ 经过 epollwait 等待事件 */# DEFINE MAXEVENTS 4struct epollevent resultevents[MAXEVENTS];while(true){ int n = epollwait(epfd, &resultevents, MAXEVENTS, 5000); for(int i=0; i
      
      
      
      
        events[i] 必定是 ev1 到 ev4 中的一个 if(result_events[i].events&EPOLLIN) printf("描述符 %d 可读", result_events[i].fd); else if(result_events[i].events&EPOLLOUT) printf("描述符 %d 可写", result_events[i].fd) }} 
      

因此 epoll 解决了 poll 和 select 的问题:

  • 只在 epollctl 的时候把数据复制到内核空间,这保证了每一个描述符和事件必定只会被复制到内核空间一次;每次调用 epollwait 都不会复制新数据到内核空间。相比之下,select 每次调用都会把三个 fd_set 复制一遍;poll 每次调用都会把 `fdarray` 复制一遍。
  • epoll_wait 返回 n ,那么只须要作 n 次循环,能够保证遍历的每一次都是有意义的。相比之下,select 须要作至少 n 次至多 `maxfdp1` 次循环;poll 须要遍历完 fdarray 即作 `nfds` 次循环。
  • 在内部实现上,epoll 使用了回调的方法。调用 epollctl 时,就是注册了一个事件:在集合中放入文件描述符以及事件数据,而且加上一个回调函数。一旦文件描述符上的对应事件发生,就会调用回调函数,这个函数会把这个文件描述符加入到就绪队列上。当你调用 epollwait 时,它只是在查看就绪队列上是否有内容,有的话就返回给你的程序。select() poll()epoll_wait() 三个函数在操做系统看来,都是睡眠一下子而后判断一下子的循环,可是 select 和 poll 在醒着的时候要遍历整个文件描述符集合,而 epoll_wait 只是看看就绪队列是否为空而已。这是 epoll 高性能的理由,使得其 I/O 的效率不会像使用轮询的 select/poll 随着描述符增长而大大下降。

注 1 :select/poll/epollwait 三个函数的等待超时时间都有同样的特性:等待时间设置为 0 时函数不阻塞而是当即返回,不管是否有文件描述符已准备好;poll/epollwait 中的 timeout 为 -1,select 中的 timeout 为 NULL 时,则无限等待,直到有描述符已准备好才会返回。

注 2 :有的新手会把文件描述符是否标记为阻塞 I/O 等同于 I/O 多路复用函数是否阻塞。其实文件描述符是否标记为阻塞,决定了你 read 或 write 它时若是它未准备好是阻塞等待,仍是当即返回 EAGAIN ;而 I/O 多路复用函数除非你把 timeout 设置为 0 ,不然它老是会阻塞住你的程序。

注 3 :上面的例子只是入门,多是不许确或不全面的:一是数据要当即处理防止丢失;二是 EPOLLIN/EPOLLOUT 不彻底等同于可读可写事件,具体要去搜索 poll/epoll 的事件具体有哪些;三是大多数实际例子里,好比一个 tcp server ,都会在运行中不断增长/删除的文件描述符而不是记住固定的 3 4 5 几个描述符(用这种例子更能看出 epoll 的优点);四是 epoll 的优点更多的体如今处理大量闲链接的状况,若是场景是处理少许短链接,用 select 反而更好,并且用 select 的代码能运行在全部平台上。

Epoll数据结构:

select()和poll() IO多路复用模型

select的缺点:

  1. 单个进程可以监视的文件描述符的数量存在最大限制,一般是1024,固然能够更改数量,但因为select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差;(在linux内核头文件中,有这样的定义:#define __FD_SETSIZE    1024)
  2. 内核 / 用户空间内存拷贝问题,select须要复制大量的句柄数据结构,产生巨大的开销;
  3. select返回的是含有整个句柄的数组,应用程序须要遍历整个数组才能发现哪些句柄发生了事件;
  4. select的触发方式是水平触发,应用程序若是没有完成对一个已经就绪的文件描述符进行IO操做,那么以后每次select调用仍是会将这些文件描述符通知进程。

相比select模型,poll使用链表保存文件描述符,所以没有了监视文件数量的限制,但其余三个缺点依然存在。

拿select模型为例,假设咱们的服务器须要支持100万的并发链接,则在__FD_SETSIZE 为1024的状况下,则咱们至少须要开辟1k个进程才能实现100万的并发链接。除了进程间上下文切换的时间消耗外,从内核/用户空间大量的无脑内存拷贝、数组轮询等,是系统难以承受的。所以,基于select模型的服务器程序,要达到10万级别的并发访问,是一个很难完成的任务。

所以,该epoll上场了。

epoll IO多路复用模型实现机制

因为epoll的实现机制与select/poll机制彻底不一样,上面所说的 select的缺点在epoll上不复存在。

设想一下以下场景:有100万个客户端同时与一个服务器进程保持着TCP链接。而每一时刻,一般只有几百上千个TCP链接是活跃的(事实上大部分场景都是这种状况)。如何实现这样的高并发?

在select/poll时代,服务器进程每次都把这100万个链接告诉操做系统(从用户态复制句柄数据结构到内核态),让操做系统内核去查询这些套接字上是否有事件发生,轮询完后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大,所以,select/poll通常只能处理几千的并发链接。

epoll的设计和实现与select彻底不一样。epoll经过在Linux内核中申请一个简易的文件系统(文件系统通常用什么数据结构实现?B+树)。把原先的select/poll调用分红了3个部分:

1)调用epoll_create()创建一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)

2)调用epoll_ctl向epoll对象中添加这100万个链接的套接字

3)调用epoll_wait收集发生的事件的链接

如此一来,要实现上面说是的场景,只须要在进程启动时创建一个epoll对象,而后在须要的时候向这个epoll对象中添加或者删除链接。同时,epollwait的效率也很是高,由于调用epollwait时,并无一股脑的向操做系统复制这100万个链接的句柄数据,内核也不须要去遍历所有的链接。

下面来看看Linux内核具体的epoll机制实现思路。

当某一进程调用epoll_create方法时,Linux内核会建立一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关。eventpoll结构体以下所示:

[cpp] [view plain](https://blog.csdn.net/shenya1314/article/details/73691088# "view plain") [copy](https://blog.csdn.net/shenya1314/article/details/73691088# "copy")

  1. struct eventpoll{  
  2.     ....  
  3.     /红黑树的根节点,这颗树中存储着全部添加到epoll中的须要监控的事件/  
  4.     struct rb_root  rbr;  
  5.     /双链表中则存放着将要经过epoll_wait返回给用户的知足条件的事件/  
  6.     struct list_head rdlist;  
  7.     ....  
  8. };  

每个epoll对象都有一个独立的eventpoll结构体,用于存放经过epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂载在红黑树中,如此,重复添加的事件就能够经过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)。

而全部添加到epoll中的事件都会与设备(网卡)驱动程序创建回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫eppollcallback,它会将发生的事件添加到rdlist双链表中。

在epoll中,对于每个事件,都会创建一个epitem结构体,以下所示:

[cpp] [view plain](https://blog.csdn.net/shenya1314/article/details/73691088# "view plain") [copy](https://blog.csdn.net/shenya1314/article/details/73691088# "copy")

  1. struct epitem{  
  2.     struct rb_node  rbn;//红黑树节点  
  3.     struct list_head    rdllink;//双向链表节点  
  4.     struct epoll_filefd  ffd;  //事件句柄信息  
  5.     struct eventpoll *ep;    //指向其所属的eventpoll对象  
  6.     struct epoll_event event; //期待发生的事件类型  
  7. }  

当调用epoll_wait检查是否有事件发生时,只须要检查eventpoll对象中的rdlist双链表中是否有epitem元素便可。若是rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。

epoll.jpg

epoll数据结构示意图

从上面的讲解可知:经过红黑树和双链表数据结构,并结合回调机制,造就了epoll的高效。

OK,讲解完了Epoll的机理,咱们便能很容易掌握epoll的用法了。一句话描述就是:三步曲。

第一步:epoll_create()系统调用。此调用返回一个句柄,以后全部的使用都依靠这个句柄来标识。

第二步:epoll_ctl()系统调用。经过此调用向epoll对象中添加、删除、修改感兴趣的事件,返回0标识成功,返回-1表示失败。

第三部:epoll_wait()系统调用。经过此调用收集收集在epoll监控中已经发生的事件。

epoll实例

最后,附上一个epoll编程实例。

 

几乎全部的epoll程序都使用下面的框架:

[cpp] [view plain](http://blog.csdn.net/xiajun07061225/article/details/9250579# "view plain")[copy](http://blog.csdn.net/xiajun07061225/article/details/9250579# "copy")[print](http://blog.csdn.net/xiajun07061225/article/details/9250579# "print")[?](http://blog.csdn.net/xiajun07061225/article/details/9250579# "?")

  1. for( ; ; )  
  2.    {  
  3.        nfds = epoll_wait(epfd,events,20,500);  
  4.        for(i=0;i < span=""> <>
  5.        {  
  6.            if(events[i].data.fd==listenfd) //有新的链接  
  7.            {  
  8.                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个链接  
  9.                ev.data.fd=connfd;  
  10.                ev.events=EPOLLIN|EPOLLET;  
  11.                epollctl(epfd,EPOLLCTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中  
  12.            }  
  13.   
  14.            else if( events[i].events&EPOLLIN ) //接收到数据,读socket  
  15.            {  
  16.                n = read(sockfd, line, MAXLINE)) < 0     读  ="" <="" li="">
  17.                ev.data.ptr = md;     //md为自定义类型,添加数据  
  18.                ev.events=EPOLLOUT|EPOLLET;  
  19.                epollctl(epfd,EPOLLCTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓  
  20.            }  
  21.            else if(events[i].events&EPOLLOUT) //有数据待发送,写socket  
  22.            {  
  23.                struct myepolldata* md = (myepolldata*)events[i].data.ptr;    //取数据  
  24.                sockfd = md->fd;  
  25.                send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //发送数据  
  26.                ev.data.fd=sockfd;  
  27.                ev.events=EPOLLIN|EPOLLET;  
  28.                epollctl(epfd,EPOLLCTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据  
  29.            }  
  30.            else  
  31.            {  
  32.                //其余的处理  
  33.            }  
  34.        }  
  35.    }  

epoll的程序实例

[cpp] [view plain](http://blog.csdn.net/xiajun07061225/article/details/9250579# "view plain")[copy](http://blog.csdn.net/xiajun07061225/article/details/9250579# "copy")[print](http://blog.csdn.net/xiajun07061225/article/details/9250579# "print")[?](http://blog.csdn.net/xiajun07061225/article/details/9250579# "?")

  1.  #include   
  2. include   

  3. include   

  4. include   

  5. include   

  6. include   

  7. include   

  8. include   

  9. include   

  10.   
  11. define MAXEVENTS 64  

  12.   
  13. //函数:  
  14. //功能:建立和绑定一个TCP socket  
  15. //参数:端口  
  16. //返回值:建立的socket  
  17. static int  
  18. createandbind (char *port)  
  19. {  
  20.   struct addrinfo hints;  
  21.   struct addrinfo result, rp;  
  22.   int s, sfd;  
  23.   
  24.   memset (&hints, 0, sizeof (struct addrinfo));  
  25.   hints.aifamily = AFUNSPEC;     / Return IPv4 and IPv6 choices /  
  26.   hints.aisocktype = SOCKSTREAM; / We want a TCP socket /  
  27.   hints.aiflags = AIPASSIVE;     / All interfaces /  
  28.   
  29.   s = getaddrinfo (NULL, port, &hints, &result);  
  30.   if (s != 0)  
  31.     {  
  32.       fprintf (stderr, "getaddrinfo: %sn", gai_strerror (s));  
  33.       return -1;  
  34.     }  
  35.   
  36.   for (rp = result; rp != NULL; rp = rp->ai_next)  
  37.     {  
  38.       sfd = socket (rp->aifamily, rp->aisocktype, rp->ai_protocol);  
  39.       if (sfd == -1)  
  40.         continue;  
  41.   
  42.       s = bind (sfd, rp->aiaddr, rp->aiaddrlen);  
  43.       if (s == 0)  
  44.         {  
  45.           / We managed to bind successfully! /  
  46.           break;  
  47.         }  
  48.   
  49.       close (sfd);  
  50.     }  
  51.   
  52.   if (rp == NULL)  
  53.     {  
  54.       fprintf (stderr, "Could not bindn");  
  55.       return -1;  
  56.     }  
  57.   
  58.   freeaddrinfo (result);  
  59.   
  60.   return sfd;  
  61. }  
  62.   
  63.   
  64. //函数  
  65. //功能:设置socket为非阻塞的  
  66. static int  
  67. makesocketnon_blocking (int sfd)  
  68. {  
  69.   int flags, s;  
  70.   
  71.   //获得文件状态标志  
  72.   flags = fcntl (sfd, F_GETFL, 0);  
  73.   if (flags == -1)  
  74.     {  
  75.       perror ("fcntl");  
  76.       return -1;  
  77.     }  
  78.   
  79.   //设置文件状态标志  
  80.   flags |= O_NONBLOCK;  
  81.   s = fcntl (sfd, F_SETFL, flags);  
  82.   if (s == -1)  
  83.     {  
  84.       perror ("fcntl");  
  85.       return -1;  
  86.     }  
  87.   
  88.   return 0;  
  89. }  
  90.   
  91. //端口由参数argv[1]指定  
  92. int  
  93. main (int argc, char *argv[])  
  94. {  
  95.   int sfd, s;  
  96.   int efd;  
  97.   struct epoll_event event;  
  98.   struct epoll_event *events;  
  99.   
  100.   if (argc != 2)  
  101.     {  
  102.       fprintf (stderr, "Usage: %s [port]n", argv[0]);  
  103.       exit (EXIT_FAILURE);  
  104.     }  
  105.   
  106.   sfd = createandbind (argv[1]);  
  107.   if (sfd == -1)  
  108.     abort ();  
  109.   
  110.   s = makesocketnon_blocking (sfd);  
  111.   if (s == -1)  
  112.     abort ();  
  113.   
  114.   s = listen (sfd, SOMAXCONN);  
  115.   if (s == -1)  
  116.     {  
  117.       perror ("listen");  
  118.       abort ();  
  119.     }  
  120.   
  121.   //除了参数size被忽略外,此函数和epoll_create彻底相同  
  122.   efd = epoll_create1 (0);  
  123.   if (efd == -1)  
  124.     {  
  125.       perror ("epoll_create");  
  126.       abort ();  
  127.     }  
  128.   
  129.   event.data.fd = sfd;  
  130.   event.events = EPOLLIN | EPOLLET;//读入,边缘触发方式  
  131.   s = epollctl (efd, EPOLLCTL_ADD, sfd, &event);  
  132.   if (s == -1)  
  133.     {  
  134.       perror ("epoll_ctl");  
  135.       abort ();  
  136.     }  
  137.   
  138.   / Buffer where events are returned /  
  139.   events = calloc (MAXEVENTS, sizeof event);  
  140.   
  141.   / The event loop /  
  142.   while (1)  
  143.     {  
  144.       int n, i;  
  145.   
  146.       n = epoll_wait (efd, events, MAXEVENTS, -1);  
  147.       for (i = 0; i < n; i++)   <="" li="">
  148.         {  
  149.           if ((events[i].events & EPOLLERR) ||  
  150.               (events[i].events & EPOLLHUP) ||  
  151.               (!(events[i].events & EPOLLIN)))  
  152.             {  
  153.               /* An error has occured on this fd, or the socket is not 
  154.                  ready for reading (why were we notified then?) */  
  155.               fprintf (stderr, "epoll errorn");  
  156.               close (events[i].data.fd);  
  157.               continue;  
  158.             }  
  159.   
  160.           else if (sfd == events[i].data.fd)  
  161.             {  
  162.               /* We have a notification on the listening socket, which 
  163.                  means one or more incoming connections. */  
  164.               while (1)  
  165.                 {  
  166.                   struct sockaddr in_addr;  
  167.                   socklent inlen;  
  168.                   int infd;  
  169.                   char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];  
  170.   
  171.                   inlen = sizeof inaddr;  
  172.                   infd = accept (sfd, &inaddr, &inlen);  
  173.                   if (infd == -1)  
  174.                     {  
  175.                       if ((errno == EAGAIN) ||  
  176.                           (errno == EWOULDBLOCK))  
  177.                         {  
  178.                           /* We have processed all incoming 
  179.                              connections. */  
  180.                           break;  
  181.                         }  
  182.                       else  
  183.                         {  
  184.                           perror ("accept");  
  185.                           break;  
  186.                         }  
  187.                     }  
  188.   
  189.                                   //将地址转化为主机名或者服务名  
  190.                   s = getnameinfo (&inaddr, inlen,  
  191.                                    hbuf, sizeof hbuf,  
  192.                                    sbuf, sizeof sbuf,  
  193.                                    NINUMERICHOST | NINUMERICSERV);//flag参数:以数字名返回  
  194.                                   //主机地址和服务地址  
  195.   
  196.                   if (s == 0)  
  197.                     {  
  198.                       printf("Accepted connection on descriptor %d "  
  199.                              "(host=%s, port=%s)n", infd, hbuf, sbuf);  
  200.                     }  
  201.   
  202.                   /* Make the incoming socket non-blocking and add it to the 
  203.                      list of fds to monitor. */  
  204.                   s = makesocketnon_blocking (infd);  
  205.                   if (s == -1)  
  206.                     abort ();  
  207.   
  208.                   event.data.fd = infd;  
  209.                   event.events = EPOLLIN | EPOLLET;  
  210.                   s = epollctl (efd, EPOLLCTL_ADD, infd, &event);  
  211.                   if (s == -1)  
  212.                     {  
  213.                       perror ("epoll_ctl");  
  214.                       abort ();  
  215.                     }  
  216.                 }  
  217.               continue;  
  218.             }  
  219.           else  
  220.             {  
  221.               /* We have data on the fd waiting to be read. Read and 
  222.                  display it. We must read whatever data is available 
  223.                  completely, as we are running in edge-triggered mode 
  224.                  and won't get a notification again for the same 
  225.                  data. */  
  226.               int done = 0;  
  227.   
  228.               while (1)  
  229.                 {  
  230.                   ssize_t count;  
  231.                   char buf[512];  
  232.   
  233.                   count = read (events[i].data.fd, buf, sizeof(buf));  
  234.                   if (count == -1)  
  235.                     {  
  236.                       /* If errno == EAGAIN, that means we have read all 
  237.                          data. So go back to the main loop. */  
  238.                       if (errno != EAGAIN)  
  239.                         {  
  240.                           perror ("read");  
  241.                           done = 1;  
  242.                         }  
  243.                       break;  
  244.                     }  
  245.                   else if (count == 0)  
  246.                     {  
  247.                       /* End of file. The remote has closed the 
  248.                          connection. */  
  249.                       done = 1;  
  250.                       break;  
  251.                     }  
  252.   
  253.                   / Write the buffer to standard output /  
  254.                   s = write (1, buf, count);  
  255.                   if (s == -1)  
  256.                     {  
  257.                       perror ("write");  
  258.                       abort ();  
  259.                     }  
  260.                 }  
  261.   
  262.               if (done)  
  263.                 {  
  264.                   printf ("Closed connection on descriptor %dn",  
  265.                           events[i].data.fd);  
  266.   
  267.                   /* Closing the descriptor will make epoll remove it 
  268.                      from the set of descriptors which are monitored. */  
  269.                   close (events[i].data.fd);  
  270.                 }  
  271.             }  
  272.         }  
  273.     }  
  274.   
  275.   free (events);  
  276.   
  277.   close (sfd);  
  278.   
  279.   return EXIT_SUCCESS;  
  280. }  

微信公众号【Java技术江湖】一位阿里 Java 工程师的技术小站。(关注公众号后回复”Java“便可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送做者原创的Java学习指南、Java程序员面试指南等干货资源)

相关文章
相关标签/搜索