网络编程—epoll

一、原理

epoll使用mmap减少复制开销。 并且epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。

二、最大连接数

虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接,2G内存的机器可以打开20万左右的连接

三、效率

因为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的线性下降的性能问题,但是所有socket都很活跃的情况下,可能会有性能问题。

四、消息传递

epoll通过内核和用户空间共享一块内存来实现的,内核于用户空间mmap同一块内存。

五、EPOLL模型的工作模式
   (1) LT模式

   LT:level triggered,这是缺省的工作方式,同时支持block和no-block socket,在这种模式中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。

   (2) ET模式

   LT:edge-triggered,这是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核就通过epoll告诉你,然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作而导致那个文件描述符不再是就绪状态(比如你在发送,接收或是接受请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核就不会发送更多的通知(only once)。不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。

六、EPOLL模型的使用方法
   epoll用到的所有函数都是在头文件sys/epoll.h中声明的,

1.使用的数据结构: epoll_data、epoll_data_t、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 */

   };

   epoll_event 结构体的events字段是被触发的事件,可能的取值为:

   EPOLLIN:      表示对应的文件描述符可以读;

   EPOLLOUT:     表示对应的文件描述符可以写;

   EPOLLPRI:     表示对应的文件描述符有紧急的数据可读;

   EPOLLERR:     表示对应的文件描述符发生错误;

   EPOLLHUP:     表示对应的文件描述符被挂断;

   EPOLLET:      表示对应的文件描述符有事件发生;

   联合体epoll_data用来保存触发事件的某个文件描述符相关的数据。例如一个client连接到服务器,服务器通过调用accept函数可以得到于这个client对应的socket文件描述符,可以把这文件描述符赋给epoll_data的fd字段,以便后面的读写操作在这个文件描述符上进行。

2.使用的函数:

   (1)epoll_create

   函数声明:intepoll_create(int size)

   函数说明:该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围,告诉内核要监听的数目。

   (2) epoll_ctl函数

   函数声明:intepoll_ctl(int epfd,int op, int fd, struct epoll_event *event)

   函数说明:该函数用于控制某个文件描述符上的事件,可以注册事件、修改事件、删除事件。

      epfd:由 epoll_create 生成的epoll专用的文件描述符;

      op:要进行的操作,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修改、EPOLL_CTL_DEL      删除;

      fd:关联的文件描述符;

      event:指向epoll_event的指针;

   如果调用成功则返回0,不成功则返回-1。

   (3) epoll_wait函数

   函数声明:int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout)

   函数说明:该函数用于轮询I/O事件的发生。

   epfd:由epoll_create 生成的epoll专用的文件描述符;

   epoll_event:用于回传代处理事件的数组;

   maxevents:每次能处理的事件数;

   timeout:等待I/O事件发生的超时值;

   返回发生事件数。

   调用流程:

   1.通过create_epoll(int maxfds)来创建一个epoll的句柄,其中maxfds为你的epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的所有操作都将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。

   2.在网络主循环里面,调用epoll_wait(int epfd, epoll_event events, int max_events,int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写。基本的语法为:

   nfds = epoll_wait(kdpfd, events, maxevents, -1); 

   其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait函数操作成功之后,events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout参数指示 epoll_wait的超时条件,为0时表示马上返回;为-1时表示函数会一直等下去直到有事件返回;为任意正整数时表示等这么长的时间,如果一直没有事件,则会返回。一般如果网络主循环是单线程,用-1来等待可以保证效率,如果是和主循环在同一个线程,可以用0来保证主循环的效率。

3.epoll_wait返回之后,需要进入一个循环,遍历所有的事件。


七、EPOLL模型的一个实例
#include

#include  

#include

#include  

#include

#include  

#include  

#include

 

#define MAXLINE 10 

#define OPEN_MAX 100

#define LISTENQ 20

#define SERV_PORT 5555 

#define INFTIM 1000

 

void setnonblocking(int sock)

{

   int opts;

   opts = fcntl(sock, F_GETFL);

   if(opts < 0)

   {

      perror("fcntl(sock, GETFL)");

      exit(1);

   }

   opts = opts | O_NONBLOCK;

   if(fcntl(sock, F_SETFL, opts) < 0)

   {

      perror("fcntl(sock,SETFL,opts)");

      exit(1);

   }

}

int main()

{

   int i, maxi, listenfd, connfd, sockfd, epfd, nfds; 

   ssize_t n; 

   char line[MAXLINE];

   socklen_t clilen;

   //声明epoll_event结构体的变量, ev用于注册事件, events数组用于回传要处理的事件

   struct epoll_event ev,events[20];

   //生成用于处理accept的epoll专用的文件描述符, 指定生成描述符的最大范围为256 

   epfd = epoll_create(256);

   struct sockaddr_in clientaddr; 

   struct sockaddr_in serveraddr;

   listenfd = socket(AF_INET, SOCK_STREAM, 0);

 

   setnonblocking(listenfd);       //把用于监听的socket设置为非阻塞方式

   ev.data.fd = listenfd;          //设置与要处理的事件相关的文件描述符

   ev.events = EPOLLIN | EPOLLET;  //设置要处理的事件类型

   epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);     //注册epoll事件

   bzero(&serveraddr, sizeof(serveraddr)); 

   serveraddr.sin_family = AF_INET;

   char *local_addr = "200.200.200.204";

   inet_aton(local_addr, &(serveraddr.sin_addr));

   serveraddr.sin_port = htons(SERV_PORT);  //或者htons(SERV_PORT);

   bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));

   listen(listenfd, LISTENQ);

   maxi = 0;

   for( ; ; )

   { 

      nfds = epoll_wait(epfd, events, 20, 500); //等待epoll事件的发生

      for(i = 0; i < nfds; ++i)                 //处理所发生的所有事件

      {

         if(events[i].data.fd == listenfd)      //监听事件

         {

            connfd = accept(listenfd, (sockaddr *)&clientaddr, &clilen); 

            if(connfd < 0)

            {

               perror("connfd<0");

               exit(1);

            }

            setnonblocking(connfd);           //把客户端的socket设置为非阻塞方式

            char *str = inet_ntoa(clientaddr.sin_addr);

            std::cout << "connect from " << str  <

            ev.data.fd=connfd;                //设置用于读操作的文件描述符

            ev.events=EPOLLIN | EPOLLET;      //设置用于注测的读操作事件

            epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);

            //注册ev事件

         }

         else if(events[i].events&EPOLLIN)      //读事件

         {

            if ( (sockfd = events[i].data.fd) < 0)

            {

               continue;

            }

            if ( (n = read(sockfd, line, MAXLINE)) < 0) // 这里和IOCP不同

            {

               if (errno == ECONNRESET)

               {

                  close(sockfd);

                  events[i].data.fd = -1; 

               }

               else

               {

                  std::cout<<"readline error"<

               }

            }

            else if (n == 0)

            {

               close(sockfd); 

               events[i].data.fd = -1; 

            }

            ev.data.fd=sockfd;              //设置用于写操作的文件描述符

            ev.events=EPOLLOUT | EPOLLET;   //设置用于注测的写操作事件 

            //修改sockfd上要处理的事件为EPOLLOUT

            epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);

         } 

         else if(events[i].events&EPOLLOUT)//写事件

         {

            sockfd = events[i].data.fd;

            write(sockfd, line, n);

            ev.data.fd = sockfd;               //设置用于读操作的文件描述符

            ev.events = EPOLLIN | EPOLLET;     //设置用于注册的读操作事件

            //修改sockfd上要处理的事件为EPOLIN

            epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);

         } 

      }

   }

}

 


 

你可能感兴趣的:(C++)