poll与epoll

poll


poll函数接口

#include <poll.h>

int poll(struct pollfd *fds,nfds_t nfds,int timeout);
//参数1 结构体指针(结构体数组的首地址)  
//参数2 数组长度
//参数3 poll函数的超时时间(同select)
//返回值小于0,出错,等于0,poll函数等待超时
//大于0,表示poll由于监听的文件描述符就绪返回。

struct pollfd{
    int fd;//文件描述符
    short events;//输入参数 监听的事件集合(常用事件 POLLIN POLLOUT)
    short revents;//输出参数 返回的事件集合
}; 


poll的优点(同select比较)

不同与select使⽤三个位图来表⽰三个fdset的⽅式,poll使⽤⼀个pollfd的指针实现

  • pollfd结构包含了要监视的event和发⽣的event,不再使⽤select“参数-值”传递的⽅式. 接⼝使⽤⽐select更⽅便.
  • poll并没有最⼤数量限制 (但是数量过⼤后性能也是会下降)

poll的缺点

  • 和select函数⼀样,poll返回后,需要轮询pollfd来获取就绪的描述符.
  • 每次调⽤poll都需要把⼤量的pollfd结构从⽤户态拷⻉到内核中.
  • 同时连接的⼤量客户端在⼀时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增⻓,其效率也会线性下降

//使用epoll监控标准输入

#include <stdio.h>
#include <unistd.h>
#include <poll.h>

int main(){

    struct pollfd fds;
    fds.fd = 0;//0标准输入
    fds.events = POLLIN;

    while(1){
        int ret = poll(&fds,1,-1);//-1 永久阻塞
        if(ret<0){
            perror("poll");
            return 1;
        }
        char buf[1024] = {0};
        ssize_t read_size = read(0,buf,sizeof(buf)-1);
        if(read_size < 0){
            perror("read");
            return 1;
        }
        if(read_size == 0){
            printf("read done\n");
            return 0;
        }
        buf[read_size] = '\0';

        printf("rep = %s\n",buf);
    }

    return 0;
}

epoll


epoll的相关函数


创建一个epoll的句柄(验证其为文件描述符的方式:连接服务器时,第一个client为5,不是4),既然是文件描述符,用完要关闭。

int epoll_create(int size);
//size为任意值,无意义

epoll的事件注册函数

int epoll_ctl (int epfd,int op,int fd,struct epoll_event *event);
//参数1 epoll_create() 的返回值(句柄)
//参数2 表示处理事件的方式(3种)
//参数3 需要监听的文件描述符
//参数4结构体指针(操作方式)

参数2的三种方法

  • EPOLL_CTL_ADD 注册新的fd到epfd;
  • EPOLL_CTL_MOD 修改已经注册的fd的监听事件;
  • EPOLL_CTL_DEL 从epfd中删除一个fd;

    struct epoll_event
    这里写图片描述

epoll_event结构体里边有两个参数,一个events(相当于位图)其常用操作有EPOLLIN,EPOLLOUT。第二个参数为一个联合,其能更好的操作一个未知甚至更复杂的结构(含有一个void*的指针)。

events常见操作:

  • EPOLLIN : 表⽰对应的⽂件描述符可以读 (包括对端SOCKET正常关闭);
  • EPOLLOUT : 表⽰对应的⽂件描述符可以写;
  • EPOLLPRI : 表⽰对应的⽂件描述符有紧急的数据可读 (这⾥应该表⽰有带外数据到来);
  • EPOLLERR : 表⽰对应的⽂件描述符发⽣错误;
  • EPOLLHUP : 表⽰对应的⽂件描述符被挂断;
  • EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于⽔平触发(Level Triggered)来说的.(默认为LT模式1)
  • EPOLLONESHOT:只监听⼀次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加⼊到EPOLL队列⾥


int epoll_wait(int epfd,struct epoll_event *event,int maxevents,int timeout);

//参数1 epoll句柄
//参数2 结构体数组首地址(同epoll_ctl的 *event)
//参数3 数组的大小
//参数4 超时时间

参数events是分配好的epoll_event结构体数组.
epoll将会把发⽣的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在⽤户态中分配内存).
maxevents告之内核这个events有多⼤,这个 maxevents的值不能⼤于创建epoll_create()时的size.
参数timeout是超时时间 (毫秒,0会⽴即返回,-1是永久阻塞).
如果函数调⽤成功,返回对应I/O上已准备好的⽂件描述符数目,如返回0表⽰已超时, 返回⼩于0表⽰函数失败


epoll的使用场景

对于多连接, 且多连接中只有⼀部分连接⽐较活跃时, ⽐较适合使⽤epoll


epoll的工作原理(底层红黑树存储,就绪存放于链表)

  • 当某⼀进程调⽤epoll_create⽅法时,Linux内核会创建⼀个eventpoll结构体,这个结构体中有两个成员与epoll的使⽤⽅式密切相关
  • 每⼀个epoll对象都有⼀个独⽴的eventpoll结构体,⽤于存放通过epoll_ctl⽅法向epoll对象中添加进来的事件.
  • 这些事件都会挂载在红⿊树中,如此,重复添加的事件就可以通过红⿊树⽽⾼效的识别出来(红⿊树的插⼊时间效率是lgn,其中n为树的⾼度).
  • ⽽所有添加到epoll中的事件都会与设备(网卡)驱动程序建⽴回调关系,也就是说,当响应的事件发⽣时会调⽤这个回调⽅法.
  • 这个回调⽅法在内核中叫eppollcallback,它会将发⽣的事件添加到rdlist双链表中.
    在epoll中,对于每⼀个事件,都会建⽴⼀个epitem结构体

  • 当调⽤epoll_wait检查是否有事件发⽣时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可.

  • 如果rdlist不为空,则把发⽣的事件复制到⽤户态,同时将事件数量返回给⽤户. 这个操作的时间复杂度是O(1)

epoll的工作方式

  • 假如有一个文件描述符就绪,携带1K的数据,epoll_wait返回,假设开始读数据,一次只能读0.5K的数据,那么缓冲区还留下0.5K,那么此时epoll_wait立刻再次返回,告诉服务器该文件描述符就绪,让其读完剩余的0.5K数据,这样的操作为水平触发。
  • 边缘触发则是如上例如果1K的字节只读了0.5K,epoll_wait不会立刻返回,而是等到下次这个文件就绪后,返回,此时,服务器才有可能读取上次的缓存。故ET模式强制我们将缓冲区的数据一次性全部读取。所以只能是非阻塞读写。

水平触发(LT)

  • 支持阻塞和非阻塞读写

边缘触发(ET)(效率高)

  • 只支持非阻塞读写(ET(边缘触发)数据就绪只会通知⼀次,也就是说,如果要使⽤ET模式,当数据就绪时,需要⼀直read,直到出错或完成为⽌.但倘若当前fd为阻塞(默认),那么在当读完缓冲区的数据时,如果对端并没有关闭写端,那么该read函数会⼀直阻塞。)(想象僵持状态)

epoll的优点(同select比较)

  • ⽂件描述符数目⽆上限: 通过epoll_ctl()来注册⼀个⽂件描述符, 内核中使⽤红⿊树的数据结构来管理所有需要监控的⽂件描述符.
  • 基于事件的就绪通知⽅式: ⼀旦被监听的某个⽂件描述符就绪, 内核会采⽤类似于callback的回调机制, 迅速激活这个⽂件描述符. 这样随着⽂件描述符数量的增加, 也不会影响判定就绪的性能;
  • 维护就绪队列: 当⽂件描述符就绪, 就会被放到内核中的⼀个就绪队列中. 这样调⽤epoll_wait获取就绪⽂件描述符的时候, 只要取队列中的元素即可, 操作的时间复杂度是O(1);
  • 从接口使用的角度上讲:不必每次都重新设置要监控的文件描述符,使接口使用方便,也能够避免用户态和内核态之间,来回的拷贝文件描述符结构。

LT

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

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
typedef struct epoll_event epoll_event;


void ProcessListenScok(int epoll_fd, int listen_sock){
    sockaddr_in peer;
    socklen_t len;
    int new_sock = accept(listen_sock,(sockaddr*)&peer,&len);
    if(new_sock <0 ){
        perror("accept");
        return;
    }
    //把 new_sock 加入到epoll之中

    epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = new_sock;
    int ret = epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&event);
    if(ret < 0){
        perror("epoll_ctl ADD");
        return;
    }
    printf("[client %d] connected\n",new_sock);
    return;
}

int ServerInit(const char* ip,short port){
    int listen_sock = socket(AF_INET,SOCK_STREAM,0);
    if(listen_sock < 0){
        perror("socket");
        return -1;
    }
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(ip);
    addr.sin_port = htons(port);
    int ret = bind(listen_sock,(sockaddr*)&addr,sizeof(addr));
    if(ret < 0 ){
        perror("bind");
        return -1;
    }
    ret = listen(listen_sock,10);
    if(ret < 0){
        perror("listen");
        return -1;
    }
    return listen_sock;
}


void ProcessNewSock(int epoll_fd,int new_sock){
    char buf[1024] = {0};
    ssize_t read_size = read(new_sock,buf,sizeof(buf)-1);
    if(read_size < 0){
        perror("read");
        return;
    }
    //读到返回值为0,对端关闭了文件描述符。
    //本端也应该关闭文件描述符,并且把文件描述符从epoll之中删除掉
    if(read_size == 0){
        close(new_sock);
        epoll_ctl(epoll_fd,EPOLL_CTL_DEL,new_sock,NULL);
        printf("[client %d] disconnected\n",new_sock);
        return;
    }
    buf[read_size] = '\0';
    printf("[client %d]say:%s\n",new_sock,buf);
    write(new_sock,buf,strlen(buf));
    return;
}



int main(int argc,char* argv[]){
    if(argc != 3){
        printf("Usage: ./server_epoll  [ip] [port]\n");
        return 1;
    }
    int listen_sock = ServerInit(argv[1],atoi(argv[2]));
    if( listen_sock < 0){
        perror("ServerInit");
        return 1;
    }

    //创建并初始化epoll;
    int epoll_fd = epoll_create(9);
    if(epoll_fd < 0){
        perror("epoll_create");
        return 1;
    }
    //把listen_sock放置到epoll
    epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = listen_sock;
    int ret = epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&event);
    if(ret < 0){
        perror("epoll_ctl");
        return -1;
    }
    printf("ServerInit OK!\n");

    while(1){
        epoll_event output_event[100];
        int nfds = epoll_wait(epoll_fd,output_event,100,-1);
        if(nfds < 0){
            perror("epoll_wait");
            continue;
        }
        //output_event[]里边保存着准备好的文件描述符
        int i = 0;
        for(;i < nfds;++i ){
            if(listen_sock == output_event[i].data.fd){//读
                ProcessListenScok(epoll_fd,listen_sock);
            }
            else{
                ProcessNewSock(epoll_fd,output_event[i].data.fd);//增加
            }
        }//for
    }//while


    return 0;
}

ET

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值