Linux I/O复用——epoll

1、epoll()

epoll()是Linux特有的I/O复用函数,它的实现与使用上和select()、poll()、有很大差异。

epoll()用一组函数来完成任务,而不是单个函数;其次,epoll()把文件描述放到内核事件表中,只需一个额外的文件描述符,来标识内核中唯一的这个事件表。

需要使用的API:

int epoll_create(int size);

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

需要使用的结构体信息:

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 */
};

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

2、epoll_wait()

关键:对epoll_wait()函数的核心理解

  (1)、返回值:事件表中就绪客户端的个数;

  (2)、参数events:将事件表中的就绪客户端的信息放到了events数组中。

3、epoll()的核心思想

  :就是创建一个内核事件表,存放所监听客户端的套接字和当前的事件,在利用epoll_wait()函数查找就绪的套接字,最后经过增加、删除、修改利用epoll_ctl()函数进行;当然了,这其中还有一批搭配使用的宏;

3、代码实现

(1)、utili.h

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

#include<sys/select.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT  8787
#define LISTEN_QUEUE 5
#define SIZE 10
#define BUFFER_SIZE 256

#include<poll.h>
#define OPEN_MAX 1000

#include<sys/epoll.h>
#define FDSIZE      1000
#define EPOLLEVENTS 100

(2)、ser.c

#include"../utili.h"

static int socket_bind(const char *ip, int port){
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addrSer;
    addrSer.sin_family = AF_INET;
    //addrSer.sin_addr.s_addr = inet_addr(ip);
    inet_pton(AF_INET, ip, &addrSer.sin_addr);
    addrSer.sin_port = htons(port);
    bind(listenfd, (struct sockaddr*)&addrSer, sizeof(struct sockaddr));
    return listenfd;
}

static void add_event(int epollfd, int fd, int state){
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
}

static void delete_event(int epollfd, int fd, int state){
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev);
}
static void modify_event(int epollfd, int fd, int state){
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
}

static void handle_accept(int epollfd, int listenfd){
    int clifd;
    struct sockaddr_in addrCli;
    socklen_t len = sizeof(struct sockaddr);
    clifd = accept(listenfd, (struct sockaddr*)&addrCli, &len);
    if(clifd != -1){
        add_event(epollfd, clifd, EPOLLIN);
    }
}

static void do_read(int epollfd,  int fd, char *buf){
    int nread = read(fd, buf, BUFFER_SIZE);
    if(nread == -1){
        close(fd);
        delete_event(epollfd, fd, EPOLLIN);
    }else{
        printf("read msg:>%s\n",buf);
        modify_event(epollfd, fd, EPOLLOUT);
    }
}
static void do_write(int epollfd, int fd, char *buf){
    int nwrite = write(fd, buf, strlen(buf)+1);
    if(nwrite == -1){
        close(fd);
        delete_event(epollfd, fd, EPOLLOUT);
    } else{
        modify_event(epollfd, fd , EPOLLIN);
    }
    memset(buf, 0, BUFFER_SIZE);

}

static void handle_events(int epollfd, struct epoll_event *events, int num,
                            int listenfd, char *buf){
    int i;
    int fd;
    for(i=0; i<num; ++i){
        fd = events[i].data.fd;
        if((fd==listenfd) && (events[i].events&EPOLLIN)) //根据其结果分别进入三种状态
            handle_accept(epollfd, listenfd);  //申请与服务器连接
        else if(events[i].events & EPOLLIN)
            do_read(epollfd, fd, buf);  //只读
        else if(events[i].events & EPOLLOUT)        
            do_write(epollfd, fd, buf);  //只写
    }
}

static void do_epoll(int listenfd){
    int ret;
    char buffer[BUFFER_SIZE];
    struct epoll_event events[EPOLLEVENTS];
    int epollfd = epoll_create(FDSIZE);
    add_event(epollfd, listenfd, EPOLLIN);
    for(;;){
        //select poll
        ret = epoll_wait(epollfd, events, EPOLLEVENTS, -1);
        handle_events(epollfd, events, ret, listenfd, buffer);
    }
    close(epollfd);
}

int main(void){
    int listenfd;
    listenfd = socket_bind(SERVER_IP, SERVER_PORT);
    listen(listenfd, LISTEN_QUEUE);
    do_epoll(listenfd);
    return 0;
}

(3)、cli.c

#include"../utili.h"

static void add_event(int epollfd, int fd, int state){
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
}

static void delete_event(int epollfd, int fd, int state){
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd; 
    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev);
}

static void modify_event(int epollfd, int fd, int state){
    struct epoll_event ev; 
    ev.events = state;
    ev.data.fd = fd; 
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
}

static void do_read(int epollfd,  int fd, int sockfd, char *buf){
    int nread = read(fd, buf, BUFFER_SIZE);
    if(nread == -1) {
        close(fd);
        delete_event(epollfd, fd, EPOLLIN);
    }else{
        if(fd == STDIN_FILENO)
            add_event(epollfd, fd, EPOLLIN);
        else{
            delete_event(epollfd, fd, EPOLLIN);
            add_event(epollfd, STDOUT_FILENO, EPOLLOUT);
        }
    }
    printf("Ser :>%s", buf);
}

static void do_write(int epollfd, int fd, int sockfd, char *buf){
    int nwrite = write(fd, buf, strlen(buf)+1);
    if(nwrite == -1){
        perror("write");
        close(fd);
    }else{
        if(fd == STDOUT_FILENO){
            delete_event(epollfd, fd, EPOLLOUT);
        }else{
            modify_event(epollfd, fd, EPOLLIN);
        }
    }    
    memset(buf, 0, BUFFER_SIZE);
}

static void handle_events(int epollfd, struct epoll_event *events, int num,
                            int sockfd, char *buf){
    int i;
    int fd;
    for(i=0; i<num; ++i){
        fd = events[i].data.fd;
        if(events[i].events & EPOLLIN)
            do_read(epollfd, fd, sockfd, buf);
        else if(events[i].events, fd, sockfd, buf)
            do_write(epollfd, fd, sockfd, buf);
    }
}
static void handle_connection(int sockfd){
    struct epoll_event events[EPOLLEVENTS];
    int epollfd = epoll_create(FDSIZE);
    add_event(epollfd, STDIN_FILENO, EPOLLIN);

    int ret;
    char buffer[BUFFER_SIZE];
    for(;;){
        ret = epoll_wait(epollfd, events, EPOLLEVENTS, -1);
        handle_events(epollfd, events, ret, sockfd, buffer);
    }
    close(epollfd);
}

int main(void){
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addrSer;
    addrSer.sin_family = AF_INET;
    addrSer.sin_port = htons(SERVER_PORT);
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);
    connect(sockfd, (struct sockaddr*)&addrSer, sizeof(struct sockaddr));

    handle_connection(sockfd);
    close(sockfd);
    return 0;
}

运行结果

服务器端就是等待客户端的使用;

客户端1

客户端2

利用epoll()函数,不用轮询每个套接字,效率更高效一些;

时间: 2024-08-01 03:56:11

Linux I/O复用——epoll的相关文章

从linux源码看epoll

从linux源码看epoll前言在linux的高性能网络编程中,绕不开的就是epoll.和select.poll等系统调用相比,epoll在需要监视大量文件描述符并且其中只有少数活跃的时候,表现出无可比拟的优势.epoll能让内核记住所关注的描述符,并在对应的描述符事件就绪的时候,在epoll的就绪链表中添加这些就绪元素,并唤醒对应的epoll等待进程.本文就是笔者在探究epoll源码过程中,对kernel将就绪描述符添加到epoll并唤醒对应进程的一次源码分析(基于linux-2.6.32内核

linux IO复用(epoll)小记

一.epoll简介 epoll是Linux内核为处理大批量文件描述符而作了改进的poll, 是Linux下多路复用IO接口select/poll的增强版本, 它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率.另一点原因就是获取事件的时候, 它无须遍历整个被侦听的描述符集, 只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了. 二.epoll的API函数 1. 句柄创建函数 int epoll_create(int size); 创建一个epoll的句柄

Linux I/O复用中select poll epoll模型的介绍及其优缺点的比较

关于I/O多路复用: I/O多路复用(又被称为"事件驱动"),首先要理解的是,操作系统为你提供了一个功能,当你的某个socket可读或者可写的时候,它可以给你一个通知.这样当配合非阻塞的socket使用时,只有当系统通知我哪个描述符可读了,我才去执行read操作,可以保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功.写操作类似.操作系统的这个功能通过select/poll/epoll之类的系统调用来实现,这些函数都可以同时监视多个描述符的读写就绪状况,这样,**多

Linux I/O复用中select poll epoll模型的介绍及其优缺点的比較

关于I/O多路复用: I/O多路复用(又被称为"事件驱动"),首先要理解的是.操作系统为你提供了一个功能.当你的某个socket可读或者可写的时候.它能够给你一个通知.这样当配合非堵塞的socket使用时,仅仅有当系统通知我哪个描写叙述符可读了,我才去运行read操作.能够保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功.写操作相似.操作系统的这个功能通过select/poll/epoll之类的系统调用来实现.这些函数都能够同一时候监视多个描写叙述符的读写就绪状况

linux多路IO复用中的select和epoll

select,epoll都是IO多路复用的机制.I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作.但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间. select调用的过程: (1)从用户空间拷贝fd_set到内核空间 (2)注册回调函数__poll

Linux I/O复用——select()

1.Linux I/O多路复用 之前:我们的处理是,每到来一个客户端,都为其开辟一个新的进/线程,对其进行一对一的服务,这是VIP的模式:在高并发情况下,将造成资源消耗过大. 现在,对应高并发:一个线程为多个客户服务: 同一个时刻,只能为一个客户服务(作用排队); 模型分析 此时就会产生select().poll().epoll()模式 2.select()模式 API函数: int select(int nfds, fd_set *readfds, fd_set *writefds, fd_s

linux下select/poll/epoll机制的比较

select.poll.epoll简介 epoll跟select都能提供多路I/O复用的解决方案.在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSIX所规定,一般操作系统均有实现 select: select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理.这样所带来的缺点是: 1. 单个进程可监视的fd数量被限制,即能监听端口的大小有限. 一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/fil

linux下select,poll,epoll的使用与重点分析

好久没用I/O复用了,感觉差不多都快忘完了,记得当初刚学I/O复用的时候花了好多时间,但是由于那会不太爱写博客,导致花很多时间搞明白的东西,依然很容易忘记.俗话说眼过千遍不如手过一遍,的确,在以后的学习中,无论知识的难易亦或是重要程度如何,我都会尽量义博客的形式记录下来,这样即能用博客来督促自己学习,也能加深对知识的理解俩全其美,好了废话不说了. I/O复用的基本概述 I/O复用技术主要是用来同时监听多个套接字描述符,使得我们的程序大幅度的提高性能,一般如下情况会用到I/O复用技术 (1)程序需

Linux下select&amp;poll&amp;epoll的实现原理(一)

最近简单看了一把Linux linux-3.10.25 kernel中select/poll/epoll这个几个IO事件检测API的实现.此处做一些记录.其基本的原理是相同的,流程如下 先依次调用fd对应的struct file.f_op->poll()方法(如果有提供实现的话),尝试检查每个提供待检测IO的fd是否已经有IO事件就绪 如果已经有IO事件就绪,则直接所收集到的IO事件返回,本次调用结束 如果暂时没有IO事件就绪,则根据所给定的超时参数,选择性地进入等待 如果超时参数指示不等待,则