欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

IO多路复用之epoll

程序员文章站 2022-06-14 11:07:09
...

epoll同select,poll等,都是IO多路复用的一种方式,但是性能上远超前面两个。
以select为例,select监听多个文件描述符的时候,当其中某些文件描述符有事件发生,select需要我们自己去轮询,也就是从头到尾扫描所有文件描述符(下面简称fd),看是哪一个被置位,这种情况下效率是比较低的。
并且,select和poll的实现均存在拷贝的过程,也就是执行的时候,需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大,会使得用户空间和内核空间在拷贝时复制开销大。
但是epoll就解决了这个问题,在用户空间和内核空间中,存在一个共享内存区,有事件发生的fd会进入到就绪链表中,通过epoll_wait的返回值,可以确定有事件发生的所有fd数量,我们只需要遍历这些fd并进行数据交互即可,省去了轮询的时间花费。
然后,epoll没有最大并发连接的限制,上限是最大可以打开文件的数目(一般远大于2048),一般跟系统内存关系很大。linux下具体数目可以cat /proc/sys/fs/file-max查看。
IO多路复用之epoll

下面看一下epoll的主要接口:
IO多路复用之epoll
IO多路复用之epoll
IO多路复用之epoll
下面写一个epoll的简易服务器模型:

#include <iostream>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <netinet/in.h> 
#include <sys/epoll.h>

using namespace std;


int main(){
    int listenfd;  
    listenfd = socket(AF_INET,SOCK_STREAM,0); 

    struct sockaddr_in servaddr;
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family = AF_INET;  
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
 
    servaddr.sin_port = htons(5000); 

    if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0){ 
        perror("bind");
        close(listenfd);
        return -1; 
    }   
    cout<<"绑定端口成功"<<endl; 

    if (listen(listenfd,5) != 0){ 
        perror("listen");
        close(listenfd);
        return -1;
    }
    cout<<"开监听"<<endl<<endl;


    int epfd = epoll_create(5);  //参数为可容纳的fd数量
    if(epfd == -1) {
       perror("epfd");
       exit(-1);
    }

    struct epoll_event ev; //epoll_event结构体,包含events和data
    struct epoll_event Events[5];  //用于epoll_wait,共享内存区,存放fd

    ev.events = EPOLLIN;  //监听可读的fd
    ev.data.fd = listenfd;  //data是个联合体,用到它的第二个参数-fd

    int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);  //add一个fd

    if(ret < 0){
       perror("epoll_ctl");
       exit(-1);
    }
    while(1) {
        ret = epoll_wait(epfd, Events, 5, -1);  //最后一个参数-1表示阻塞
        if (ret == -1) {   //ret 返回所有 有数据来的fd个数
           perror("epoll_wait");
           exit(-1);
        }

        for (int i = 0; i < ret; ++i){
            if (Events[i].data.fd == listenfd){
               //有客户端发起连接
               int clientfd = accept(listenfd, 0, 0); //把连接socket放进去   
               if (clientfd < 0) {
                  perror("accept");
                  exit(-1);
               }
               cout << "客户端socket = " << clientfd << "已连接上。" << endl;

               //为新的fd注册事件,下次有数据来 它就会自己通知进程
               ev.data.fd = clientfd;
               ev.events = EPOLLIN;
               int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);  //add一个fd
               if (ret < 0){
                  perror("epoll_ctl");
                  exit(-1);
               }
            }
            
            else{
                if (Events[i].events & EPOLLIN) {
                  //如果事件可读
                  char strbuffer[32];
                  memset(strbuffer,0,sizeof(strbuffer));

                  ret = recv(Events[i].data.fd, strbuffer, sizeof(strbuffer), 0);

                  if (ret == 0) {
                     cout<<" 客户端" << Events[i].data.fd << "已经退出!" <<endl;
                     //注销事件
                     ev.data.fd = Events[i].data.fd;
                     ev.events = EPOLLIN;
                     int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, Events[i].data.fd, &ev);  //注销一个fd
                     if(ret < 0) {
                        perror("epoll_ctl");
                     }
                     continue;
                  }

                  else if (ret > 0) {
                     cout<<"收到客户端"<<Events[i].data.fd << ": "<<strbuffer<<endl;

                     //发送数据
                     memset(strbuffer, 0, sizeof(strbuffer));
                     strcpy(strbuffer, "我收到了!");

                     if (send(Events[i].data.fd, strbuffer, strlen(strbuffer), 0) < 0) {
                         perror("send");
                         continue;
                     }
                     cout << "发送: "<< strbuffer << endl << endl;
                     memset(strbuffer,0,sizeof(strbuffer));
                 }
              }
           }
        }
     }
     return 0;
}

IO多路复用之epoll