I/O多路复用——epoll函数

1 select的低效率

  select/poll函数效率比较低,主要有以下两个原因:

  (1)调用select函数后需要对所有文件描述符进行循环查找

  (2)每次调用select函数时都需要向该函数传递监视对象信息

  在这两个原因中,第二个原因是主要原因:每次调用select函数时,应用程序都要将所有文件描述符传递给操作系统,这给程序带来很大的负担。在高并发的环境下,无论怎样优化应用程序的代码,都无法完成应用的服务。  

  所以,select与poll并不适合以Web服务器端开发为主流的现代开发环境,只在要求满足以下两个条件是适用:

  (1)服务器端接入者少

  (2)程序要求兼容性

2 Linux的epoll机制

  由上一节,我们需要一种类似于select的机制来完成高并发的服务器。需要有以下两个特点:

  (1)应用程序仅向操作系统传递1次监视对象

  (2)监视范围或内容发生变化是,操作系统只通知发生变化的事项给应用程序

  幸运的是,的确存在这样的机制。Linux的支持方式是epoll,Windows的支持方式是IOCP。

3 epoll函数原型  

  epoll操作由三个函数组成:  

#include <sys/epoll.h>
int epoll_create(int size);            //成功时返回epoll文件描述符,失败时返回-1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);            //成功时返回0,失败时返回-1
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);            //成功时返回发生事件的文件描述数,失败时返回-1

  (1)epoll_create:创建保存epoll文件描述符的空间。

  调用epoll_create函数时创建的文件描述符保存空间称为“epoll例程”。但要注意:size参数只是应用程序向操作系统提的建议,操作系统并不一定会生成一个大小为size的epoll例程。

  (2)epoll_ctl:向空间注册并注销文件描述符。

  参数epfd指定注册监视对象的epoll例程的文件描述符,op指定监视对象的添加、删除或更改等操作,有以下两种常量:

    1)EPOLL_CTL_ADD:将文件描述符注册到epoll例程

    2)EPOLL_CTL_DEL:从epoll例程中删除文件描述符

    3)EPOLL_CTL_MOD:更改注册的文件描述符的关注事件发生情况

  fd指定需要注册的监视对象文件描述符,event指定监视对象的事件类型。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;

  epoll_event的成员events中可以保存的常量及所指的事件类型有以下:

    1)EPOLLIN:需要读取数据的情况

    2) EPOLLOUT:输出缓冲为空,可以立即发送数据的情况  

    3) EPOLLPRI:收到OOBO数据的情况

    4) EPOLLRDHUP:断开连接或半关闭的情况,这在边缘触发方式下非常有用

    5) EPOLLERR:发生错误的情况

    6) EPOLLET:以边缘触发的方式得到事件通知

    7) EPOLLONESHOT:发生一次事件后,相应文件描述符不再收到事件通知。因此需要向epoll_ctl函数的第二个参数EPOLL_CTL_MOD,再次设置事件。

(3)epoll_wait:与select函数类似,等待文件描述符发生变化。操作系统返回epoll_event类型的结构体通知监视对象的变化。timeout函数是为毫秒为单位的等待时间,传递-1时,一直等待直到事件发生。声明足够大的epoll_event结构体数组后,传递给epoll_wait函数时,发生变化的文件符信息将被填入该数组。因此,不需要像select函数那样针对所有文件符进行循环。

4 基于epoll的echo服务器代码:

#define BUF_SIZE 1024
#define EPOLL_SIZE 50
void error_handling(char *buf);

int main(int argc, char *argv[])
{
    int listenfd, connfd;
    struct sockaddr_in serv_addr;
    socklen_t socklen;
    char buf[BUF_SIZE];

    int epfd, event_cnt;
    struct epoll_event *ep_events;
    struct epoll_event event;

    if (argc != 2)
    {
        printf("Usage: echo <port>\n");
        exit(1);
    }

    listenfd = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_addr, 0, sizeof(serv_addr);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));

    if (bind(listenfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
        error_handling("bind() error\n");
    if (listen(serv_addr, 5) == -1)
        error_handling("listen() error\n");

    epfd = epoll_create(EPOLL_SIZE);
    ep_events = malloc(sizeof(epoll_event)*EPOLL_SIZE);

    event.event = EPOLLIN;
    event.data.fd = listenfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &event);

    for (;;)
    {
        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
        if (event_cnt == -1)
            error_handling("epoll_wait() error\n");
        for (int i = 0; i < event_cnt; ++i)
        {
            if (ep_events[i].data.fd == listenfd)
            {
                connfd = accept(listenfd, NULL, NULL);
                event.events = EPOLLIN;
                event.data.fd = connfd;
                epoll_ctl(pefd, EPOLL_CTL_ADD, connfd, &event);
                printf("connect another client\n");
            }
            else
            {
                int nread = read(ep_events[i].dada.fd, buf, BUF_SIZE);
                if (nread == 0)
                {
                    close(ep_events.data.fd);
                    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events.data.fd, NULL);
                    printf("disconnect with a client\n");
                }
                else
                {
                    write(ep_events[i].data.fd, buf, nread);
                }
            }
        }
    }
    close(listenfd);
    close(epfd);
    return 0;
}

void error_handling(char* buf)
{
    printf("%s\n", buf);
    exit(1);
}

 

时间: 2024-12-24 22:11:25

I/O多路复用——epoll函数的相关文章

I/O多路复用---epoll函数测试

参考文章来源: epoll使用详解(精髓) Epoll学习笔记 epoll是直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法. epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂. epol

epoll函数及三种I/O复用函数的对比

epoll函数 #include <sys/epoll.h>int epoll_create(int size)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout) Linux I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数

Linux-C网络编程之epoll函数

上文中说到如果从100的不同的地方取外卖,那么epoll相当于一部手机,当外卖到达后,送货员可以通知你,从而达到每去必得,少走很多路. 它是如何实现这些作用的呢? epoll的功能 epoll是select/poll的强化版,同是多路复用的函数,epoll有了很大的改进. 支持监听大数目的socket描述符* 一个进程内,select能打开的fd是有限制的,由宏FD_SETSIZE设置,默认值是1024.在某些时候,这个数值是远远不够用的.解决办法有两种,一是修改宏然后重新编译内核,但与此同时会

多路复用——epoll

1.基本知识 epoll是在2.6内核中提出的,是之前的select和poll的增强版本.相对于select和poll来说,epoll更加灵活,没有描述符限制.epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次. 2.epoll函数 epoll操作过程需要三个接口,分别如下: #include <sys/epoll.h>int epoll_create(int size);int epoll_ctl(in

IO多路复用--epoll详解

epoll 或者 kqueue 的原理是什么? [转自知乎] Epoll 引入简介 首先我们来定义流的概念,一个流可以是文件,socket,pipe等等可以进行I/O操作的内核对象. 不管是文件,还是套接字,还是管道,我们都可以把他们看作流.之后我们来讨论I/O的操作,通过read,我们可以从流中读入数据:通过write,我们可以往流写入数据.现在假定一个情形,我们需要从流中读数据,但是流中还没有数据,(典型的例子为,客户端要从socket读如数据,但是服务器还没有把数据传回来),这时候该怎么办

I/O多路复用——epoll

epoll是Linux特有的I/O复用函数,它在实现和使用上与select.poll有很大差异. epoll使用一组函数来完成任务,而不是单个函数. epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无需像select.poll那样每次调用都要重复传入文件描述符集或事件集. 但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表. epoll API epoll有epoll_create.epoll_ctl.epoll_wait三个系统调用. 1.epoll_

IO多路复用--epoll

epoll就是为了 处理大批量句柄而改进的poll,相比与select,poll最大的好处在于它不会随着坚挺fd的数目增长而效率降低.因为在内核中的select是采用轮询来处理的,轮询fd的数目越多,自然耗时越多,并且slelct的监听数目有限(虽然可以通过头文件来改变,但并不治本) 一.epoll的相关系统调用 epoll只有三个简单地接口 分别为epoll_creat,epoll_ctl,epoll_wait (1)int epoll_creat(int size) 创建一个epoll句柄,

内核源码IO多路复用EPOLL

1. 简介: 本文将介绍内核epoll实现的原理.基于kernel 2.6.32版本. 本文只描述epoll对其他fd的监听,由于epoll本身也是一种文件系统,也可以被监听,这一部分不在这里介绍. 2. 基础数据结构: epoll中主要数据结构有两个,一个是epoll_create创建的epoll_fd的结构体eventpoll,一个是事件源对应的epitem结构体. epollevent的数据结构及相应解释如下:这里注意的是eventpoll中其实有两个ready list,一个是常规的rd

Linux下使用epoll函数同时处理TCP请求和UDP请求的回射服务器

#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include