精华内容
下载资源
问答
  • 创建一个epoll的句柄,size用来告诉32313133353236313431303231363533e58685e5aeb931333361313964内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当...

    展开全部

    epoll的接口非常简单,一共就三个函数:

    1. int epoll_create(int size);

    创建一个epoll的句柄,size用来告诉32313133353236313431303231363533e58685e5aeb931333361313964内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

    2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

    epoll的事件注册函数,即注册要监听的事件类型。

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

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

    EPOLL_CTL_ADD:注册新的fd到epfd中;

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

    EPOLL_CTL_DEL:从epfd中删除一个fd;

    第三个参数是需要监听的fd,

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

    struct epoll_event {

    __uint32_t events;

    epoll_data_t data;

    };

    typedef union epoll_data {

    void *ptr;

    int fd;

    __uint32_t u32;

    __uint64_t u64;

    } epoll_data_t;

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

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

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

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

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

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

    EPOLLET: 将 EPOLL设为边缘触发(Edge Triggered)模式(默认为水平触发),这是相对于水平触发(Level Triggered)来说的。

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

    3. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

    等待事件的产生。参数events 用来从内核得到事件的集合,maxevents 告之内核这个events 有多大,这个maxevents 的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

    4. EPOLL事件有两种模型:

    Edge Triggered (ET) 边缘触发 只有数据到来,才触发,不管缓存区中是否还有数据。

    Level Triggered (LT) 水平触发 只要有数据都会触发。

    假如有这样一个例子:

    1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符

    2. 这个时候从管道的另一端被写入了2KB的数据

    3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作

    4. 然后我们读取了1KB的数据

    5. 调用epoll_wait(2)......

    Edge Triggered 工作模式:

    如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4 步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5 步调用epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。

    i 基于非阻塞文件句柄

    ii 只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN 才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。Level Triggered 工作模式相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即使使用ET模式的epoll,在收到多个chunk 的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。

    然后详细解释ET, LT:

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

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

    在许多测试中我们会看到如果没有大量的idle -connection 或者deadconnection,epoll 的效率并不会比select/poll 高很多,但是当我们遇到大量的idleconnection(例如WAN 环境中存在大量的慢速连接),就会发现epoll 的效率大大高于select/poll。(未测试)

    另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取:

    while(rs) {

    buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);

    if(buflen < 0) {

    // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读

    // 在这里就当作是该次事件已处理处.

    if(errno == EAGAIN) {

    break;

    } else {

    return;

    }

    } else if(buflen == 0) {

    // 这里表示对端的socket已正常关闭.

    }

    if(buflen == sizeof(buf)) {

    rs = 1; // 需要再次读取

    } else {

    rs = 0;

    }

    }

    还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回-1 表示出错。在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send()内部,但暂没有更好的办法.

    ssize_t socket_send(int sockfd, const char* buffer, size_t buflen) {

    ssize_t tmp;

    size_t total = buflen;

    const char *p = buffer;

    while(1) {

    tmp = send(sockfd, p, total, 0);

    if(tmp < 0) {

    // 当send收到信号时,可以继续写,但这里返回-1.

    if(errno == EINTR)

    return -1;

    // 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满,

    // 在这里做延时后再重试.

    if(errno == EAGAIN) {

    usleep(1000);

    continue;

    }

    return -1;

    }

    if((size_t)tmp == total)

    return buflen;

    total -= tmp;

    p += tmp;

    }

    return tmp;

    }

    本回答由提问者推荐

    已赞过

    已踩过<

    你对这个回答的评价是?

    评论

    收起

    展开全文
  • 1、epoll_create函数/*** @brief 该函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存* 放你想关注的socket fd上是否发生以及发生了什么事件。* @param size: size就是你在这个epoll fd上能关注...

    首先看一下epoll的几个函数的介绍。

    1、epoll_create函数

    /**

    * @brief 该函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存

    * 放你想关注的socket fd上是否发生以及发生了什么事件。

    * @param size: size就是你在这个epoll fd上能关注的最大socket fd数

    * @return 生成的文件描述符*/

    int epoll_create(int size);

    2、epoll_ctl函数

    /**

    * @brief 该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删

    * 除事件。

    * @param epfd : 由 epoll_create 生成的epoll专用的文件描述符

    * @param op : 要进行的操作例如注册事件,可能的取值

    * EPOLL_CTL_ADD 注册

    * EPOLL_CTL_MOD 修改

    * EPOLL_CTL_DEL 删除

    * @param fd : 关联的文件描述符

    * @param event : 指向epoll_event的指针

    * @return 0 if success, -1 if fail*/

    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

    其中用到的数据结构结构如下:

    typedef union epoll_data

    {void *ptr;intfd;

    __uint32_t u32;

    __uint64_t u64;

    } epoll_data_t;structepoll_event

    {

    __uint32_t events;/*Epoll events*/epoll_data_t data;/*User data variable*/};

    常用的事件类型:

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

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

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

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

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

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

    例:

    structepoll_event ev;//设置与要处理的事件相关的文件描述符

    ev.data.fd=listenfd;//设置要处理的事件类型

    ev.events=EPOLLIN|EPOLLET;//注册epoll事件

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

    3、epoll_wait函数

    /**

    * @brief 该函数用于轮询I/O事件的发生

    * @param epfd : 由epoll_create 生成的epoll专用的文件描述符

    * @param events : 用于回传代处理事件的数组

    * @param maxevents : 每次能处理的事件数

    * @param timeout : 等待I/O事件发生的超时值;-1相当于阻塞,0相当于非阻塞。一般用-1即可

    * @return if >=0, 返回发生事件数, if -1, 错误*/

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

    epoll_wait运行的原理:

    等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。并且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型。这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事件类型清空。这一步非常重要。

    好了,其实在epoll的使用中无非就用到了上面介绍的几个函数,下面贴一段用epoll实现的服务器代码:

    服务器代码

    1 #include

    2 #include

    3 #include

    4 #include

    5 #include

    6 #include

    7 #include

    8 #include

    9 #include

    10 #include

    11 #include

    12 #include

    13 #include

    14 #include

    15 #include

    16 #include

    17 #define MAXBUF 1024

    18 #define MAXEPOLLSIZE 10000

    19 /*

    20 setnonblocking - 设置句柄为非阻塞方式21 */

    22 int setnonblocking(intsockfd)23 {24 if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1)25 {26 return -1;27 }28 return 0;29 }30 /*

    31 handle_message - 处理每个 socket 上的消息收发32 */

    33 int handle_message(intnew_fd)34 {35 char buf[MAXBUF + 1];36 intlen;37 /*开始处理每个新连接上的数据收发*/

    38 bzero(buf, MAXBUF + 1);39 /*接收客户端的消息*/

    40 len = recv(new_fd, buf, MAXBUF, 0);41 if (len > 0)42 {43 printf("%d接收消息成功:'%s',共%d个字节的数据\n",44 new_fd, buf, len);45 }46 else

    47 {48 if (len < 0)49 printf50 ("消息接收失败!错误代码是%d,错误信息是'%s'\n",51 errno, strerror(errno));52 close(new_fd);53 return -1;54 }55 /*处理每个新连接上的数据收发结束*/

    56 returnlen;57 }58 int main(int argc, char **argv)59 {60 intlistener, new_fd, kdpfd, nfds, n, ret, curfds;61 socklen_t len;62 structsockaddr_in my_addr, their_addr;63 unsigned intmyport, lisnum;64 structepoll_event ev;65 structepoll_event events[MAXEPOLLSIZE];66 structrlimit rt;67 myport = 5000;68 lisnum = 2;69 /*设置每个进程允许打开的最大文件数*/

    70 rt.rlim_max = rt.rlim_cur =MAXEPOLLSIZE;71 if (setrlimit(RLIMIT_NOFILE, &rt) == -1)72 {73 perror("setrlimit");74 exit(1);75 }76 else

    77 {78 printf("设置系统资源参数成功!\n");79 }80 /*开启 socket 监听*/

    81 if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1)82 {83 perror("socket");84 exit(1);85 }86 else

    87 {88 printf("socket 创建成功!\n");89 }90 setnonblocking(listener);91 bzero(&my_addr, sizeof(my_addr));92 my_addr.sin_family =PF_INET;93 my_addr.sin_port =htons(myport);94 my_addr.sin_addr.s_addr =INADDR_ANY;95 if (bind(listener, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1)96 {97 perror("bind");98 exit(1);99 }100 else

    101 {102 printf("IP 地址和端口绑定成功\n");103 }104 if (listen(listener, lisnum) == -1)105 {106 perror("listen");107 exit(1);108 }109 else

    110 {111 printf("开启服务成功!\n");112 }113 /*创建 epoll 句柄,把监听 socket 加入到 epoll 集合里*/

    114 kdpfd =epoll_create(MAXEPOLLSIZE);115 len = sizeof(structsockaddr_in);116 ev.events = EPOLLIN |EPOLLET;117 ev.data.fd =listener;118 if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0)119 {120 fprintf(stderr, "epoll set insertion error: fd=%d\n", listener);121 return -1;122 }123 else

    124 {125 printf("监听 socket 加入 epoll 成功!\n");126 }127 curfds = 1;128 while (1)129 {130 /*等待有事件发生*/

    131 nfds = epoll_wait(kdpfd, events, curfds, -1);132 if (nfds == -1)133 {134 perror("epoll_wait");135 break;136 }137 /*处理所有事件*/

    138 for (n = 0; n < nfds; ++n)139 {140 if (events[n].data.fd ==listener)141 {142 new_fd = accept(listener, (struct sockaddr *) &their_addr,&len);143 if (new_fd < 0)144 {145 perror("accept");146 continue;147 }148 else

    149 {150 printf("有连接来自于: %d:%d, 分配的 socket 为:%d\n",151 inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);152 }153 setnonblocking(new_fd);154 ev.events = EPOLLIN |EPOLLET;155 ev.data.fd =new_fd;156 if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, new_fd, &ev) < 0)157 {158 fprintf(stderr, "把 socket '%d' 加入 epoll 失败!%s\n",159 new_fd, strerror(errno));160 return -1;161 }162 curfds++;163 }164 else

    165 {166 ret =handle_message(events[n].data.fd);167 if (ret < 1 && errno != 11)168 {169 epoll_ctl(kdpfd, EPOLL_CTL_DEL, events[n].data.fd,&ev);170 curfds--;171 }172 }173 }174 }175 close(listener);176 return 0;177 }

    epoll用到的所有函数都是在头文件sys/epoll.h中声明,有什么地方不明白或函数忘记了可以去看一下。

    epoll和select相比,最大不同在于:

    epoll返回时已经明确的知道哪个socket fd发生了事件,不用再一个个比对。这样就提高了效率。在select 中,要去判断每个socket, 通过if(FD_ISSET(socket, &集合));在 epoll 中,for(inti=0; i

    select的FD_SETSIZE是有限止的,而epoll是没有限止的只与系统资源有关。

    注:本文选自http://www.cnblogs.com/cipc/articles/2431042.html,

    如有侵犯您的权益,请邮件通知我,我将会在收到通知后尽快删除相关内容。

    展开全文
  • 展开全部epoll的接口...创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会...

    展开全部

    epoll的接口62616964757a686964616fe58685e5aeb931333363386165非常简单,一共就三个函数:

    1. int epoll_create(int size);

    创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

    2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

    epoll的事件注册函数,即注册要监听的事件类型。

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

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

    EPOLL_CTL_ADD:注册新的fd到epfd中;

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

    EPOLL_CTL_DEL:从epfd中删除一个fd;

    第三个参数是需要监听的fd,

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

    struct epoll_event {

    __uint32_t events;

    epoll_data_t data;

    };

    typedef union epoll_data {

    void *ptr;

    int fd;

    __uint32_t u32;

    __uint64_t u64;

    } epoll_data_t;

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

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

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

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

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

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

    EPOLLET: 将 EPOLL设为边缘触发(Edge Triggered)模式(默认为水平触发),这是相对于水平触发(Level Triggered)来说的。

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

    3. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

    等待事件的产生。参数events 用来从内核得到事件的集合,maxevents 告之内核这个events 有多大,这个maxevents 的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

    4. EPOLL事件有两种模型:

    Edge Triggered (ET) 边缘触发 只有数据到来,才触发,不管缓存区中是否还有数据。

    Level Triggered (LT) 水平触发 只要有数据都会触发。

    假如有这样一个例子:

    1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符

    2. 这个时候从管道的另一端被写入了2KB的数据

    3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作

    4. 然后我们读取了1KB的数据

    5. 调用epoll_wait(2)......

    Edge Triggered 工作模式:

    如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4 步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5 步调用epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。

    i 基于非阻塞文件句柄

    ii 只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN 才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。Level Triggered 工作模式相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即使使用ET模式的epoll,在收到多个chunk 的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。

    然后详细解释ET, LT:

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

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

    在许多测试中我们会看到如果没有大量的idle -connection 或者deadconnection,epoll 的效率并不会比select/poll 高很多,但是当我们遇到大量的idleconnection(例如WAN 环境中存在大量的慢速连接),就会发现epoll 的效率大大高于select/poll。(未测试)

    另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取:

    while(rs) {

    buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);

    if(buflen < 0) {

    // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读

    // 在这里就当作是该次事件已处理处.

    if(errno == EAGAIN) {

    break;

    } else {

    return;

    }

    } else if(buflen == 0) {

    // 这里表示对端的socket已正常关闭.

    }

    if(buflen == sizeof(buf)) {

    rs = 1; // 需要再次读取

    } else {

    rs = 0;

    }

    }

    还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回-1 表示出错。在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send()内部,但暂没有更好的办法.

    ssize_t socket_send(int sockfd, const char* buffer, size_t buflen) {

    ssize_t tmp;

    size_t total = buflen;

    const char *p = buffer;

    while(1) {

    tmp = send(sockfd, p, total, 0);

    if(tmp < 0) {

    // 当send收到信号时,可以继续写,但这里返回-1.

    if(errno == EINTR)

    return -1;

    // 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满,

    // 在这里做延时后再重试.

    if(errno == EAGAIN) {

    usleep(1000);

    continue;

    }

    return -1;

    }

    if((size_t)tmp == total)

    return buflen;

    total -= tmp;

    p += tmp;

    }

    return tmp;

    }

    已赞过

    已踩过<

    你对这个回答的评价是?

    评论

    收起

    展开全文
  • epoll监听文件_epoll

    2020-12-24 05:30:26
    poll和select相比最大...使用epoll的大体框架是1 创建epoll instalce,得到epoll文件描述符调用epoll_create可以在内核创建一个epoll instance,返回这个instance的文件描述符。size参数已经被忽略了。int epoll...

    poll和select相比最大优势在于可以管理更多的文件描述符。

    epoll和poll相比最大的有时是速度更快,减少了大量文件描述符从内核到用户态的拷贝。

    使用epoll的大体框架是

    1 创建epoll instalce,得到epoll的文件描述符

    调用epoll_create可以在内核创建一个epoll instance,返回这个instance的文件描述符。size参数已经被忽略了。

    int epoll_create(int size);

    int epoll_create1(int flags);

    epoll_create1的参数flags值得一说。它可以取0或者FD_CLOEXEC。如果设置了FD_CLOEXEC,那么epoll_create1返回的文件描述符就会被字段设置FD_CLOEXEC标记。于是当进程以后调用fork的时候,epoll_create1所返回的文件描述符在子进程里是被关闭的,用不了。

    例如

    int epollfd = epoll_create1(0);

    2 利用上面得到的epollfd,添加要监控的文件描述符

    struct epoll_event ev;

    ev.data.fd = fd;

    ev.events = EPOLLIN|EPOLLET;

    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) < 0) goto err;

    其中:

    第一个参数是epoll_create返回的fd

    第二个参数是要做的事情,包括:

    EPOLL_CTL_ADD

    EPOLL_CTL_MOD

    EPOLL_CTL_DEL

    第三个参数是要监控的fd

    第四个参数是struct epoll_event用来描述要监控哪些时间,以及当时间发生时传回哪些数据。

    EPOLLIN 表示要监控文件描述符的可读性

    epoll_event.data是一个union,除了设置fd,也可以当作void*来使用,执行自定义的数据。当文件描述符准备好时,epoll会把这个指针再“吐出来”。

    3 设置好要监控的fd和事件后,就可以等待事件发生了。

    struct epoll_event *evs;

    evs = calloc(64, sizeof(ev));

    while(1)

    {

    int n = epoll_wait(epollfd, evs, 64, -1);

    ...

    第一个参数是epoll_create返回的文件描述符

    第二个参数是提供给epoll_wait的struct epoll_event数组,epoll会把准备好的文件描述符和时间,以及之前ADD时自定义的数放到这个数组里。

    第三个参数是数组长度

    第四个参数是超时时间

    返回值n是此次检测到已就绪的文件描述符数量。之后可以依次取出evs[0]到evs[n-1]来处理。

    4 处理得到的struct epoll_event数组

    对于数组的每个成员,需要先检查文件描述符发生的什么事件,有没有错误产生。

    if(e->events & EPOLLERR || e->events & EPOLLHUP )

    ....

    epoll和poll都是检测文件描述符的缓冲区有没有数据,来判断文件描述符是否准备好了。

    但是epoll的事件触发方式更灵活,有边缘触发(ET)和水平触发(LT)两种。

    这两个概念和数字电路里的一样。

    边缘触发: 当状态从0->1的瞬间触发一次。即缓冲区从空变成有数据的时候返回。

    水平触发: 状态为1时始终触发。即只要缓冲区有数据,就会返回。

    epoll的默认行为是水平触发,此时的行为和poll一直,就是只要缓冲区有数据没读完,调用epoll就会立刻返回。

    边缘触发的性能更好,但是有个问题,就是只当缓冲期从空变成有数据的那一刻触发一次,之后再调用epoll,它就会阻塞,一直等待下一次缓冲区由空到非空的事件。

    因此如果需要采用性能更好的边缘触发模式,需要做到以下两点:

    1 只支持非阻塞文件描述符,也就是说必须设置NONBLOCK标记。

    int fl = 0;

    int r  = 0;

    if((fl = fcntl(fd, F_GETFL, 0)) < 0) goto err;

    if((r  = fcntl(fd, F_SETFL, fl|O_NONBLOCK)) < 0) goto err;

    2 必须确保每一次触发后,都要处理完缓冲区的全部数据。

    通常用一个while(1)来解决。

    由于已经将文件描述符设置为NONBLOCK了,因此可以用死循环。

    如果返回-1,需要需要判断如果errno是EAGAIN,则是正常的,说明缓冲区的数据处理完了。如果是其它的errno,就是真的出错了。

    while(1)

    {

    if((cnt = read(evs[i].data.fd, buf, BUFSIZ)) < 0)

    {

    if(errno != EAGAIN) close(evs[i].data.fd);

    break;

    }

    else if (*buf == EOF)

    {

    close(evs[i].data.fd);

    break;

    }

    ...

    ...

    例子:

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    int check_epoll_err(struct epoll_event* ee)

    {

    if(ee == NULL) return -1;

    if(

    ee->events & EPOLLERR ||

    ee->events & EPOLLHUP

    )

    {

    return -1;

    }

    return 1;

    }

    int no_block(int fd, char* errbuf)

    {

    int fl = 0;

    int r  = 0;

    if((fl = fcntl(fd, F_GETFL, 0)) < 0) goto err;

    if((r  = fcntl(fd, F_SETFL, fl|O_NONBLOCK)) < 0) goto err;

    return 0;

    err:

    strcpy(errbuf, strerror(errno));

    return errno * -1;

    }

    int do_accept(int sock, char* errbuf)

    {

    int ep = 0;

    int r = 0;

    struct epoll_event ev;

    struct epoll_event *evs;

    if(listen(sock, 1024) < 0) goto err;

    if((ep = epoll_create1(0)) < 0) goto err;

    ev.data.fd = sock;

    ev.events = EPOLLIN|EPOLLET;

    if(epoll_ctl(ep, EPOLL_CTL_ADD, sock, &ev) < 0) goto err;

    evs = calloc(64, sizeof(ev));

    int n, i;

    struct sockaddr_in caddr;

    socklen_t addrlen;

    int cfd;

    while(1)

    {

    n = epoll_wait(ep, evs, 64, -1);

    for(i=0; i

    {

    if(check_epoll_err(&(evs[i])) < 0)

    {

    close(evs[i].data.fd);

    continue;

    }

    else if(sock == evs[i].data.fd)

    {

    while(1) //loop try one non_block fd.

    {

    if((cfd = accept(sock, (struct sockaddr*)&caddr, &addrlen)) < 0)

    {

    if(errno == EAGAIN || errno == EWOULDBLOCK)

    break; // all connection accepted.

    else

    goto err;

    }

    printf("accept client: %s:%d\n", inet_ntoa(caddr.sin_addr), caddr.sin_port);

    // make client fd non_block.

    if((no_block(cfd, errbuf)) < 0) goto err;

    // add client into epoll.

    ev.data.fd = cfd;

    ev.events = EPOLLIN|EPOLLET;

    if(epoll_ctl(ep, EPOLL_CTL_ADD, cfd, &ev) < 0) goto err;

    continue;

    }

    puts("accept ok");

    }

    else

    {

    // client fd ready.

    if(evs[i].events & EPOLLIN == EPOLLIN)

    {

    int cnt;

    char buf[BUFSIZ];

    int finish = 0;

    char hello[10] = "hello: ";

    while(1)

    {

    if((cnt = read(evs[i].data.fd, buf, BUFSIZ)) < 0)

    {

    if(errno != EAGAIN) close(evs[i].data.fd);

    break;

    }

    else if (*buf == EOF)

    {

    close(evs[i].data.fd);

    break;

    }

    printf("from client: %s", buf);

    if(write(evs[i].data.fd, hello, strlen(hello)) < 0) goto err;

    if(write(evs[i].data.fd, buf, cnt) < 0) goto err;

    }

    }

    }

    }

    }

    return 0;

    err:

    strcpy(errbuf, strerror(errno));

    return errno * -1;

    }

    int do_listen(int port, char* errbuf)

    {

    int r = 0;

    int sock = -1;

    struct sockaddr_in servaddr;

    servaddr.sin_family = AF_INET;

    inet_pton(AF_INET, "0.0.0.0", &servaddr.sin_addr);

    servaddr.sin_port = htons(port);

    if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) goto err;

    else if((r = bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr))) != 0) goto err;

    int opt = 1;

    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

    // make client fd non_block.

    if((no_block(sock, errbuf)) < 0) goto err;

    return sock;

    err:

    strcpy(errbuf, strerror(errno));

    return errno * -1;

    }

    int main()

    {

    int port = 1111;

    int r = 0;

    char errbuf[BUFSIZ];

    int sock = do_listen(port, errbuf);

    if(sock < 0)

    {

    puts(errbuf);

    exit(errno);

    }

    r = do_accept(sock, errbuf);

    if(r < 0)

    {

    puts(errbuf);

    exit(errno);

    }

    return 0;

    }

    展开全文
  • epoll介绍epoll的行为与poll(2)相似,监视多个有IO事件的文件描述符。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态...
  • epoll - I/O event notification ...相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linu...
  • Epoll epoll - I/O event notification facility介绍通常来说,实现处理tcp请求,为一个连接一个线程,在高并发的场景,这种多线程模型与Epoll相比就显得相形见绌了。epoll是linux2.6内核的一个新的系统调用,epoll...
  • epoll_ctl() : 提供给用户态应用程序向epoll中添加、删除和修改感兴趣的事件,其中epfd就是通过epoll_create()系统调用获取的epoll对象文件描述符,op用于指明epoll如何操作event事件。 int epoll_wait(int epfd, ...
  • IO多路复用的意思是在一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其的进行读写操作。在 linux 中,和 epoll 类似的有 select 和 poll。网上很多文章着重关注 epoll 与 ...
  • epoll服务器一、概述epoll是Linux下多路复用IO接口select/poll的增强版本epoll能显著...而epoll只需要监听那些已经准备好的队列集合中的文件描述符,效率较高。二、epoll API头文件 #include <sys/epoll.h>1...
  • 前言多进程和多线程模型在实现中相对简单, 但其开销和CPU高度比较大, 一般不用多线程和多进程来实现... 但由于poll本质实现上也是轮询机制, 所以对于客户端的增加也会使效率降低.epoll是Linux下多路复用IO接口select...
  • 首先我们了解一下什么是I/O复用。...常见的I/O复用有以下三种:selectpollepoll为什么使用epoll?这个问题也可以理解为epoll相比于select和poll有什么缺点。首先我们来分析一下select。select函数...
  • 目录:IO 多路复用select、poll、epoll 对比epoll APIepoll 使用示例Handler 中的 epoll 源码分析IO 多路复用IO 多路复用是一种同步 IO 模型,实现一个线程可以监视多个文件句柄。一旦某个文件句柄就绪,就能够通知...
  • 简介: Go 经常会遇到文件变化监控的问题, 这样的问题听起来不太好实现, 那是你没有掌握其中的奥秘, 一旦你理解了其中的原理, 也是能轻松搞定的. 本文主要就是根据 fsnotify 源码, 带你一起认识文件监控的原理.文件...
  • 笔者准备介绍完epoll和NIO等知识点,然后写一篇Java网络IO模型的介绍,这样可以使Java网络IO的知识体系更加地完整和严谨。初学者也可以等看完IO模型介绍的博客之后,再回头看这些博客,会更加有收获。如果你顺利啃下...
  • 1.概述这篇文章分析一下linux中epoll的实现原理,主要为了增强自己对网络调用的理解。业界使用epoll的框架比较多,随便就能列出来很多,比如jdk的nio在linux下的实现,以及netty、redis等涉及到长链接网络请求的地方...
  • 一、epoll相关的数据结构  最重要的两个数据结构是红黑树和就绪链表,红黑树用于管理所有的文件描述符fd,就绪链表用于保存有事件发生的文件描述符。  当向系统中添加一个fd时,就创建一个epitem结构体。...
  • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里 关于epoll工作模式ET,LT LT(level triggered)是缺省的工作方式,并且同时支持...
  • I read the man page and went through this example for usage: https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/, but I can't figure a way to do what I am trying to do using epoll, can a...
  • 1、epoll诞生的原因问大家一个问题,如果要设计一款有着千万级别并发的系统,你的客户端和服务端的网络通信底层该怎么设计?我在上一篇文章(socket网络编程(三)——select多路复用问题)中有说到用select可以实现IO多...
  • 确实是后面的printf导致的,去掉后面的printf,增加计数器的判断, 可确认epoll_wait只返回了一次。但是为什么printf会影响到标准输入stdin?还是不理解。修改后符合预期代码如下,epoll_wait只返回一次。#include #...
  • 在linux2.5.44首次引入epoll,它设计的目的旨在取代既有的select、poll系统函数,让需要大量操作文件描述符的程序得以发挥更优异的性能(wikipedia example: 旧有的系统函数所花费的时间复杂度为O(n), epoll的时间...
  • 背景技术:epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本;它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。然而,边缘触发(ET)...
  • 1:我采用epollEPOLLET模式同时监听fd读写,客户端连接服务器之后只发送数据,按正常情况应该只会触发服务器端的EPOLLIN,但是实际情况却随机触发服务器端的EPOLLOUT。2:我目前测试如果将recv函数的recv_buf大小...
  • 本文纯属转载原创作者:小麦大大本文来自:https://blog.csdn.net/qq_35433716/article/details/85345907流 IO操作 阻塞流可以进行IO操作的内核对象文件、管道、套接字……流的入口:文件描述符(fd)所有对流的读写...
  • Epoll监听多个FD时,一直很好奇如果多个FD同时有事件触发,它是如何来进行调度的,调度的顺序是否和事件触发的顺序有关系?借助简单的代码来分析一下: 代码原理较简单:1. 主线程中创建两个eventfd描述符,加入...
  • epoll是event poll的缩写,用于I/O事件通知,可以监听多个文件描述符。其相关api有以下三个: epoll_create()或epoll_create1():创建一个epoll对象,并返回一个文件描述符指向这个epoll对象 epoll_...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 538
精华内容 215
关键字:

epoll监听文件