文档章节

epoll 示例

for。
 for。
发布于 2016/07/04 21:39
字数 1010
阅读 55
收藏 0
点赞 0
评论 0

epoll 有水平触发 Level-triggered(LT) 和边沿触发 edge-triggered(ET) 两种模式。

假设有如下过程:

  1. 注册 pipe 文件描述符的读端(rfd)到 epoll 上
  2. 在 pipe 的写端写入 2KB 的数据
  3. 因为 rfd 文件描述符已经准备好读,epoll_wait 返回
  4. 从 rfd 中读取 1KB 数据
  5. 程序再次运行到 epoll_wait

如果为边沿触发(ET)模式,程序将会阻塞在 epoll_wait 上,即使有剩余数据也不会返回,意味着读不到剩余的 1KB 数据。但如果采用水平触发(LT),epoll_wait 函数将会再次返回。

如果采用边沿触发(ET)模式,建议:

  1. 使用非阻塞文件描述符
  2. 调用 read 或 write 直到 errno==EAGAIN

当 close 文件描述符时,该描述符会被自动的从 epoll 监听集中移除。

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_wait 使用该结构体数组返回就绪的文件描述符集合,这样就不用遍历所有的文件描述符。

源码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>

#define MAXEVENTS 64

/* 设置 socket 为非阻塞,必须先获取再设置 */
static int make_socket_non_blocking(int sfd)
{
    int flags, s;

    flags = fcntl(sfd, F_GETFL, 0);
    if (flags == -1) {
        perror ("fcntl");
        return -1;
    }

    flags |= O_NONBLOCK;
    s = fcntl(sfd, F_SETFL, flags);
    if (s == -1) {
        perror("fcntl");
        return -1;
    }

    return 0;
}

/* 创建并绑定 socket */
static int create_and_bind(char *port)
{
    struct addrinfo     hints;
    struct addrinfo     *result, *rp;
    int                 s, sfd;

    memset(&hints, 0, sizeof (struct addrinfo));
    hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
    hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
    hints.ai_flags = AI_PASSIVE;     /* All interfaces */

    s = getaddrinfo(NULL, port, &hints, &result);
    if (s != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror (s));
        return -1;
    }

    for (rp = result; rp != NULL; rp = rp->ai_next) {
        /* 创建 socket */
        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sfd == -1) {
            continue;
        }

        /* 绑定 socket 到地址 */
        s = bind(sfd, rp->ai_addr, rp->ai_addrlen);
        if (s == 0) {
            /* We managed to bind successfully! */
            break;
        }

        close(sfd);
    }

    if (rp == NULL) {
        fprintf (stderr, "Could not bind\n");
        return -1;
    }

    freeaddrinfo(result);

    return sfd;
}

int main(int argc, char *argv[])
{
    int     sfd, efd, s;
    struct epoll_event event;
    struct epoll_event *events;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s [port]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* 创建并绑定 socket */
    sfd = create_and_bind(argv[1]);
    if (sfd == -1) {
        abort();
    }

    /* 设置 socket 为非阻塞 */
    s = make_socket_non_blocking(sfd);
    if (s == -1) {
        abort();
    }

    /* 监听 socket */
    s = listen(sfd, SOMAXCONN);
    if (s == -1) {
        perror("listen");
        abort();
    }

    efd = epoll_create1(0);
    if (efd == -1) {
        perror("epoll_create");
        abort();
    }

    event.data.fd = sfd;
    event.events = EPOLLIN | EPOLLET;       // 读事件 | 边沿触发
    // 添加 sfd(监听 socket) 到 epoll
    s = epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event);
    if (s == -1) {
        perror("epoll_ctl");
        abort();
    }

    /* Buffer where events are returned */
    events = calloc(MAXEVENTS, sizeof(event));

    /* The event loop */
    while (1) {

        int n, i;

        if ((n = epoll_wait(efd, events, MAXEVENTS, -1)) == -1) {
            perror("epoll_wait");
            abort();
        }

        for (i = 0; i < n; i++) {
            if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) ||
                    (!(events[i].events & EPOLLIN))) {
                /* socket 出错 | socket 挂起 | socket 读未准备好
                 * 为什么 socket 读为准备好也有问题? */
                fprintf(stderr, "epoll error\n");
                close(events[i].data.fd);
                continue;
            } else if (sfd == events[i].data.fd) {
                /* 监听 socket 事件通知,意味着一个或者多个连接到来 */
                while (1) {
                    struct sockaddr in_addr;
                    socklen_t       in_len;
                    int             infd;
                    char            hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

                    in_len = sizeof(in_addr);
                    infd = accept(sfd, &in_addr, &in_len);
                    if (infd == -1) {
                        if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
                            /* 所有连接请求都已处理完成 */
                            break;
                        } else {
                            perror ("accept");
                            break;
                        }
                    }

                    s = getnameinfo(&in_addr, in_len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
                            NI_NUMERICHOST | NI_NUMERICSERV);
                    if (s == 0) {
                        printf("Accepted connection on descriptor %d "
                                "(host=%s, port=%s)\n", infd, hbuf, sbuf);
                    }

                    /* 将 socket 转为非阻塞 */
                    s = make_socket_non_blocking(infd);
                    if (s == -1) {
                        abort();
                    }

                    /* 将新的连接放入 epoll 中 */
                    event.data.fd = infd;
                    event.events = EPOLLIN | EPOLLET;       // 读事件 | 边沿触发
                    s = epoll_ctl(efd, EPOLL_CTL_ADD, infd, &event);
                    if (s == -1) {
                        perror ("epoll_ctl");
                        abort();
                    }
                }
                continue;
            } else {
                int done = 0;

                /* socket 已经准备好读。在 EPOLLET 边沿触发模式下,必须将缓冲区中的数据都读取完,
                 * 否则剩下的数据等不到通知 */
                while (1) {
                    ssize_t count;
                    char buf[1024];

                    count = read(events[i].data.fd, buf, sizeof(buf));
                    if (count == -1) {
                        /* errno == EAGAIN 意味着所有数据都已读取完成,此时可以跳出循环 */
                        if (errno != EAGAIN) {
                            perror ("read");
                            done = 1;
                        }
                        break;
                    } else if (count == 0) {
                        /* count == 0,意味着对方关闭连接 */
                        done = 1;
                        break;
                    }

                    /* 将缓冲区中的数据写入 socket */
                    s = write(events[i].data.fd, buf, count);
                    if (s == -1) {
                        perror("write");
                        abort();
                    }
                }

                if (done) {
                    printf("Closed connection on descriptor %d\n", events[i].data.fd);
                    /* 将 socket 关闭时,同时也会将该 socket 从 epoll 监听中移除 */
                    close(events[i].data.fd);
                }
            }
        }
    }

    free(events);

    close(sfd);

    return EXIT_SUCCESS;
}

参考资料

1.http://www.oschina.net/translate/how-to-use-epoll-a-complete-example-in-c

2.man epoll

© 著作权归作者所有

共有 人打赏支持
for。

for。

粉丝 80
博文 47
码字总数 18257
作品 0
深圳
程序员
epoll相关资料整理

http://www.cppblog.com/converse/archive/2008/10/13/63928.html epoll相关资料整理 学习epoll有一段时间了,最近终于有一个服务器采用了epoll模型,从中积累了一些epoll的资料.个人感觉目前可...

晨曦之光
2012/03/09
98
0
Linux下I/O多路复用select, poll, epoll 三种模型的Python使用

Linux下I/O多路复用select, poll, epoll 三种模型 select, poll, epoll本质上都是同步的I/O,因为它们都是在读写事件就绪后自己负责进行读写,这个读写的过程是阻塞的。 select, poll, epol...

首席贱人
2016/05/22
880
0
小谈 accpet_mutex_delay 参数

之前发现nginx的进程在任务分配方面非常的不平均,即某个进程一旦忙起来会忙很久,而空闲的线程却一直空闲,跟下代码发现epollwait中默认的timer是500ms(没有任务的情况下),可以通过减少acc...

zhegaozhouji
2016/07/21
0
0
事件库之Redis自己的事件模型-ae

Redis自己的事件模型 ae 1.Redis的事件模型库 大家到网上Google“Redis libevent”就可以搜到Redis为什么没有选择libevent以及libev为其事件模型库,而是自己写了一个事件模型。从代码中可以...

C_Z
2013/09/13
0
11
如何像nginx一样高效的发送大文件

在使用epoll进行非阻塞编程时,常常会需要发送大文件(例如1G大小的文件),新手最直观的想法是读取整个文件,然后把1G的文件write到socket中。事实上,这么做是不会成功的。 调用write时,w...

dong
2016/07/20
0
0
负载均衡通讯转发分发器G5更新日志v1.1.0

G5自从上周发布v1.0.0版以来受到了广大网友的热切关注,我根据网友的需求还会继续补充功能和修正缺陷,如果需求累积量大的话我基本上会保证每周一更。 本周版本更新至v1.1.0,主要做了如下更...

calvinwilliams
2014/04/13
0
0
对epoll(man文档中的代码示例)的疑问

下面是man文档中的代码示例: for (;;) { } 在调用epollctl函数的时候,为什么传入的是ev变量的指针?还有为什么遍历events数组的时候,会判断event[i].fd时否与listensock相等?listen_sock是...

Genus
2012/01/06
346
0
跨平台网络通信与服务器框架 acl 3.2.0 发布

acl 3.2.0 版本发布了,acl 是 one advanced C/C++ library 的简称,主要包括网络通信库以及服务器框架库等功能,支持 Linux/Windows/Solaris/FreeBsd/MacOS 平台;整个 acl 项目主要包含三个...

郑树新
2016/08/27
1K
4
Python 标准库 18.3 - select

select 模块一般有两个主要对象 —— 函数和 对象。 一般各平台都会有,而 是区分平台实现的,比如在 Linux 上他就是 ,在 Solaris 上叫做 。 和 实际都是访问系统调用,功能是等待 I/O 完成...

lionets
2015/07/31
0
0
Nginx/Tengine实现网址大小写转换

最近有一项目需要移植到Linux下,功能简单来讲就是FTP上传+HTTP下载。 由于Windows操作系统中,文件名是不区分大小写的,而Linux系统是大小写敏感,导致对应开发人员及程序都感冒了! 解决分...

kisops
2013/08/07
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

代码生成利器:IDEA 强大的 Live Templates

代码生成利器:IDEA 强大的 Live Templates

qwfys
13分钟前
0
0
spring boot使用通用mapper(tk.mapper) ,id自增和回显等问题

最近项目使用到tk.mapper设置id自增,数据库是mysql。在使用通用mapper主键生成过程中有一些问题,在总结一下。 1、UUID生成方式-字符串主键 在主键上增加注解 @Id @GeneratedValue...

北岩
16分钟前
0
0
告警系统邮件引擎、运行告警系统

告警系统邮件引擎 cd mail vim mail.py #!/usr/bin/env python#-*- coding: UTF-8 -*-import os,sysreload(sys)sys.setdefaultencoding('utf8')import getoptimport smtplibfr......

Zhouliang6
19分钟前
0
0
日常运维--rsync同步工具

rsync命令是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件。rsync使用所谓的“rsync算法”来使本地和远程两个主机之间的文件达到同步,这个算法只传送两个文件的不同部分,而...

chencheng-linux
23分钟前
0
0
Java工具类—随机数

Java中常用的生成随机数有Math.random()方法及java.util.Random类.但他们生成的随机数都是伪随机的. Math.radom()方法 在jdk1.8的Math类中可以看到,Math.random()方法实际上就是调用Random类...

PrivateO2
36分钟前
0
0
关于java内存模型、并发编程的好文

Java并发编程:volatile关键字解析    volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果。在...

DannyCoder
昨天
0
0
dubbo @Reference retries 重试次数 一个坑

在代码一中设置 成retries=0,也就是调用超时不用重试,结果DEBUG的时候总是重试,不是0吗,0就不用重试啊。为什么还是调用了多次呢? 结果在网上看到 这篇文章才明白 https://www.cnblogs....

奋斗的小牛
昨天
0
0
数据结构与算法3

要抓紧喽~~~~~~~放羊的孩纸回来喽 LowArray类和LowArrayApp类 程序将一个普通的Java数组封装在LowArray类中。类中的数组隐藏了起来,它是私有的,所以只有类自己的方法才能访问他。 LowArray...

沉迷于编程的小菜菜
昨天
0
0
spring boot应用测试框架介绍

一、spring boot应用测试存在的问题 官方提供的测试框架spring-boot-test-starter,虽然提供了很多功能(junit、spring test、assertj、hamcrest、mockito、jsonassert、jsonpath),但是在数...

yangjianzhou
昨天
0
0
rsync工具介绍/rsync通过ssh同步

rsync工具介绍 数据备份是必不可少,在Linux系统下数据备份的工具很多,其中重点介绍就是rsync工具,rsync不仅可以远程同步数据,还可以本地同步数据,且不会覆盖以前的数据在已经存在的数据...

Hi_Yolks
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部