高级IO——I/O多路转接之epoll

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

根据man手册的说法,epoll是为处理大批量句柄而作了改进的poll。

epoll是在205044内核中被引进的,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。

 

  • epoll的相关系统调用

epoll有三个相关的系统调用。

epoll_create

调用epoll_create,创建epoll模型。

自从Linux2.6.8之后,size参数是被忽略的。但是为了确保可移植性,我们建议将size的参数填写进去。通常size的值需要填一个很大的数。

用完之后,必须调用close()关闭。

epoll_ctl

epoll_create向epoll模型注册文件描述符对应的事件。它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

第一个参数是epoll_create()的返回值。

第二个参数表示动作,用三个宏来表示:

        EPOLL_CTL_ADD:注册新的fd到epfd中;

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

        EPOLL_CTL_DEL:从epfd中删除一个fd。

第三个参数表示需要监听的fd。

第四个参数是告诉内核需要监听什么事,struct  epoll_event的结构如下:

events可以是以下几个宏的集合:

    EPOLLIN:表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

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

    EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

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

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

    EPOLLET:将EPOLL设为边缘触发模式,这是相对于水平触发来说的;

    EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次将这个socket加入到EPOLL队列里。

epoll_wait

收集在epoll监控的事件中已经发送的事件。

参数events是分配好的epoll_event结构体数组。

epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责将数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。

maxevents告知内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size。

参数timeout是超时时间,单位是毫秒。当设置为0时会立即返回,-1时会永久阻塞。

如果函数调用成功,返回对应I/O上已经准备好的文件描述符数目。若返回0表示已超时,返回小于0表示函数失败。

 

 

  • epoll的工作原理
  • epoll的优点

(1)文件描述符数目无上限:通过epoll_ctl( )来注册一个文件描述符,内核中使用红黑树的数据结构来管理所有需要监控的文件描述符;

(2)基于事件的就绪通知方式:一旦被监听的某个文件描述符就绪,内核会采用类似于callback的回调机制,迅速激活这个文件描述符,这样随着文件描述符数量的增加,也不会影响判定就绪的性能;

(3)维护就绪队列:当文件描述符就绪,就会被放到内核中的一个就绪队列中。这样调用epoll_wait获取就绪文件描述符的时候,只要取队列中的元素即可,操作的时间复杂度是O(1)。

 

  • epoll工作方式

epoll有两种工作方式:水平触发(LT)和边缘触发(ET)。

水平触发Level  Triggered工作模式:

epoll默认状态下就是LT工作模式。

当epoll检测到socket上事件就绪的时候,可以不立刻进行处理,或者只处理一部分。

对于读操作:只要缓冲内容不为空,LT模式返回读就绪;

对于写操作:只要缓冲区还不满,LT模式会返回写就绪。

LT支持阻塞读写和非阻塞读写。

 

边缘触发Edge  Triggered工作模式:

当epoll检测到socket上事件就绪时,必须立刻处理。也就是说,ET模式下,文件描述符上的事件就绪后,只有一次处理机会。

ET的性能比LT性能更高。

ET只支持非阻塞的读写。

 

select和poll其实也是工作在LT模式下。epoll既可以支持LT,也可以支持ET。

 

 

  • epoll的使用场景

epoll的高性能,是有一定的特定场景的。如果场景选择的不适宜,epoll的性能可能适得其反。

对于多连接,且多连接中只有一部分连接比较活跃时,比较适合使用epoll。

如果只是系统内部,服务器和服务器之间进行通信,只有少数的几个连接,这种情况下用epoll就并不合适。具体要根据需求和场景特点来决定使用哪种IO模型。

 

 

  • epoll服务器

服务器端:

#include <stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<errno.h>
#include<fcntl.h>

void SetNoBlock(int fd){
    int fl = fcntl(fd,F_GETFL);
    if(fl < 0){
        perror("fcntl");
        return;
    }
    fcntl(fd,F_SETFL,fl | O_NONBLOCK);
}

void ProcessConnect(int listen_fd,int epoll_fd){
    struct sockaddr_in client_addr;
    socklen_t len = sizeof(client_addr);
    int connect_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&len);
    if(connect_fd < 0){
        perror("accept");
        return;
    }
    printf("client %s:%d connect\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
    SetNoBlock(connect_fd);
    struct epoll_event ev;
    ev.data.fd = connect_fd;
    ev.events = EPOLLIN | EPOLLET;
    int ret = epoll_ctl(epoll_fd,EPOLL_CTL_ADD,connect_fd,&ev);
    if(ret < 0){
        perror("epoll_ctl");
        return;
    }
    return;
}

ssize_t NoBlockRead(int fd,char* buf,int size){
    (void) size;
    ssize_t total_size = 0;
    for(;;){
        ssize_t cur_size = read(fd,buf+total_size,1024);
        total_size += cur_size;
        if(cur_size < 1024 || errno == EAGAIN){
            break;
        }
    }
    buf[total_size] = '\0';
    return total_size;
}

void ProcessRequest(int connect_fd,int epoll_fd){
    char buf[1024] = {0};
    ssize_t read_size = NoBlockRead(connect_fd,buf,sizeof(buf)-1);
    if(read_size < 0){
        perror("read");
        return;
    }
    if(read_size == 0){
        close(connect_fd);
        epoll_ctl(epoll_fd,EPOLL_CTL_DEL,connect_fd,NULL);
        printf("client echo: stop...\n");
        return;
    }
    printf("client say: %s",buf);
    write(connect_fd,buf,strlen(buf));
}

int main(int argc,char* argv[]){
    if(argc != 3){
        printf("Usage: ./server [ip] [port]\n");
        return 1;
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    addr.sin_port = htons(atoi(argv[2]));

    int listen_fd = socket(AF_INET,SOCK_STREAM,0);
    if(listen_fd < 0){
        perror("socket");
        return 1;
    }
    int ret = bind(listen_fd,(struct sockaddr*)&addr,sizeof(addr));
    if(ret < 0){
        perror("bind");
        return 1;
    }
    ret = listen(listen_fd,5);
    if(ret < 0){
        perror("listen");
        return 1;
    }
    int epoll_fd = epoll_create(10);
    if(epoll_fd < 0){
        perror("epoll_create");
        return 1;
    }
    SetNoBlock(listen_fd);
    struct epoll_event event;
    event.events = EPOLLIN | EPOLLET;
    event.data.fd = listen_fd;
    ret = epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_fd,&event);
    if(ret < 0){
        perror("epoll_ctl");
        return 1;
    }
    for(;;){
        struct epoll_event events[10];
        int size = epoll_wait(epoll_fd,events,sizeof(events)/sizeof(events[0]),-1);
        if(size < 0){
            perror("epoll_wait");
            continue;
        }
        if(size == 0){
            printf("epoll timeout...\n");
            continue;
        }
        int i = 0;
        for(;i < size;++i){
            if(!(events[i].events & EPOLLIN))
                continue;
            if(events[i].data.fd == listen_fd)
                ProcessConnect(listen_fd,epoll_fd);
            else
                ProcessRequest(events[i].data.fd,epoll_fd);
        }
    }
    return 0;
}

客户端:

#include <stdio.h>  
#include<sys/socket.h>  
#include<netinet/in.h>  
#include<unistd.h>  
#include<string.h>  
#include<arpa/inet.h>
#include<sys/types.h>  
#include<stdlib.h>  
#include<string.h>  
  
#define MAX 128  
  
int main(int argc,char* argv[]){  
    if(argc != 3){  
            printf("Usage:%s [ip] [port]\n",argv[0]);  
            return 1;  
        }  
    int sock = socket(AF_INET,SOCK_STREAM,0);  
    if(sock < 0){  
            printf("socket error!\n");  
            return 2;  
        }  
  
    struct sockaddr_in server;  
    server.sin_family = AF_INET;  
    server.sin_port = htons(atoi(argv[2]));  
    server.sin_addr.s_addr = inet_addr(argv[1]);  
  
    if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0){  
            printf("connect error!\n");  
            return 3;  
        }  
  
    char buf[MAX];  
    while(1){  
            printf("please Enter# ");  
            fflush(stdout);  
            read(0,buf,sizeof(buf)-1);  
            /*if(s > 0){  
                    buf[s-1] = 0;  
                    if(strcmp("quit",buf) == 0){  
                                printf("client quit!\n");  
                                break;  
                            }*/  
            ssize_t write_size = write(sock,buf,strlen(buf));
            if(write_size < 0){
                perror("write");
                continue;
            }
            ssize_t s = read(sock,buf,sizeof(buf)-1);
            if(s < 0){
                perror("read");
                continue;
            }
            if(s == 0){
                printf("server close!\n");
                break;
            }
            //buf[s] = 0;  
            printf("server Echo# %s\n",buf); 
    }  
    close(sock);  
    return 0;  
}

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值