欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

学习使用epoll

程序员文章站 2024-02-01 16:34:58
...

epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

 

 

一、epoll的优点

支持一个进程打开大数目的socket描述符

IO效率不随FD数目增加而线性下降

 

二、epoll的使用

epoll2种工作方式:LTET。   

LTlevel triggered,水平触发是缺省的工作方式,并且同时支持blockno-block socket.在这种做法中,

内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任操作,

内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型

的代表。   

 

ET edge-triggered,边缘触发是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未

就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文

件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在

发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是

请注意,如果一直不对这个fdIO操作(从而导致它再次变成未就绪),内核不会发送更多的通知

only once

 

epoll相关的系统调用有3epoll_create, epoll_ctlepoll_wait。在头文件<sys/epoll.h>

学习使用epoll
            
    
    博客分类: linux epolllinux 

 

1. int epoll_create(int size);

创建一个epoll句柄,即图中的epfd, 用来监听事件, size用来告诉内核这个监听的数目一共有多大。

这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽

 

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

参数op是操作类型, 使用这个方法完成3种操作: 

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

(1) 注册新事件

struct epoll_event ev;
ev.data.fd = fd;
ev.events = EPOLLIN;
epoll_cntl(epfd, EPOOL_CTL_ADD, fd, &ev);
 

 

 

(2) 修改监听事件

 

struct epoll_event *a_event = get_a_event() 
struct epoll_event ev;
ev.data.fd = a_event->data.fd;
ev.events = a_event->events | EPOLLOUT;
epoll_cntl(epfd, EPOOL_CTL_MOD, a_event->data.fd, &ev);
 

 

(3) 删除事件

 

struct epoll_event *a_event = get_a_event() 
struct epoll_event ev;
ev.data.fd = a_event->data.fd;
epoll_cntl(epfd, EPOOL_CTL_DEL, a_event->data.fd, &ev);
 

 

3种操作都使用了一个ev变量, 这个变量用来关联fd和它的监听事件, 是临时的, 可以反复使用.

ev是一个struct epoll_event结构体, 结构如下:

 

  1. typedef union epoll_data {  
  2.    void *ptr;  
  3.    int fd;  
  4.    __uint32_t u32;  
  5.    __uint64_t u64;  
  6. } epoll_data_t;  
  7.   
  8. struct epoll_event {  
  9.    __uint32_t events; /* Epoll events */  
  10.    epoll_data_t data; /* User data variable */  
  11. };  

 

events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

注意多个socket可以设置不同的触发模式
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里


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

等待事件的产生, 把产生的事件存放到events数组里, 如图中所示, 调用epoll_wait后, 

fd 1和 fd 3和fd k产生了事件, 把它们分别存放到events[0], events[1], events[2]
参数epfdepoll_create()函数返回的epoll句柄。

参数eventsstruct epoll_event结构指针,用来从内核得到事件的集合

参数 maxevents内核这个events有多大

参数 timeout: 等待时的超时时间,以毫秒为单位。

返回值:成功时,返回需要处理的事件数目。调用失败时,返回0,表示等待超时。

 

 

三 epoll实例 -- 模拟HTTP服务器 

 

 

#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/epoll.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <errno.h> 

#define MAX_EVENTS 10
#define PORT 8080

//设置socket连接为非阻塞模式
void setnonblocking(int sockfd) {
    int opts;

    opts = fcntl(sockfd, F_GETFL);
    if(opts < 0) {
        perror("fcntl(F_GETFL)\n");
        exit(1);
    }
    opts = (opts | O_NONBLOCK);
    if(fcntl(sockfd, F_SETFL, opts) < 0) {
        perror("fcntl(F_SETFL)\n");
        exit(1);
    }
}

int main(){
    struct epoll_event ev, events[MAX_EVENTS];
    int addrlen, listenfd, conn_sock, nfds, epfd, fd, i, nread, n;
    struct sockaddr_in local, remote;
    char buf[BUFSIZ];

    //创建listen socket
    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("sockfd\n");
        exit(1);
    }
    bzero(&local, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = htonl(INADDR_ANY);;
    local.sin_port = htons(PORT);
    if( bind(listenfd, (struct sockaddr *) &local, sizeof(local)) < 0) {
        perror("bind\n");
        exit(1);
    }
    listen(listenfd, 20);

    epfd = epoll_create(MAX_EVENTS);
    if (epfd == -1) {
        perror("epoll_create");
        exit(EXIT_FAILURE);
    }

    ev.events = EPOLLIN;
    ev.data.fd = listenfd;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) {
        perror("epoll_ctl: listen_sock");
        exit(EXIT_FAILURE);
    }

    for (;;) {
        nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_pwait");
            exit(EXIT_FAILURE);
        }

        for (i = 0; i < nfds; ++i) {
            fd = events[i].data.fd;
            if (fd == listenfd) {
                conn_sock = accept(listenfd,
                        (struct sockaddr *) &remote, &addrlen);
                if (conn_sock == -1) {
                    perror("accept");
                    exit(EXIT_FAILURE);
                }
                setnonblocking(conn_sock);
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = conn_sock;
                if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sock,
                            &ev) == -1) {
                    perror("epoll_ctl: add");
                    exit(EXIT_FAILURE);
                }
                continue;
            }  
            if (events[i].events & EPOLLIN) {
                n = 0;
                while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) {
                    n += nread;
                }
                buf[n] = '\0';

                ev.data.fd = fd;
                ev.events = events[i].events | EPOLLOUT;
                if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1) {
                    perror("epoll_ctl: mod");
                }
            }
            if (events[i].events & EPOLLOUT) {
                sprintf(buf, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\nHello World", 11);
                n = strlen(buf);
                if (write(fd, buf, n) < n) {
                    perror("write");
                }
                close(fd);
            }
        }
    }

    return 0;
}
 

运行程序后, 打开浏览器:

学习使用epoll
            
    
    博客分类: linux epolllinux 

 

  • 学习使用epoll
            
    
    博客分类: linux epolllinux 
  • 大小: 27.6 KB
  • 学习使用epoll
            
    
    博客分类: linux epolllinux 
  • 大小: 9 KB
相关标签: epoll linux