I/O复用------epoll

<1> 内核事件表
epoll是Linux特有的I/O复用函数,在实现和使用上与poll和selec有所不同。

  • 首先,epoll使用一组函数来完成任务,而非单个函数
  • epoll把用户关心的文件描述符上的事件放在内核的一个事件表中,从而无需像select和poll那样每次调用都要从新传入描述符集或事件集。

1) epoll_create
创建一个额外的文件描述符,来唯一标识内核的事件表。

#include <sys/epoll.h>
 int epoll_create(int size);
 // 返回值作为其它所有epoll_*函数的第一个参数,用以指明要访问的内核事件表。
 // size参数现在并不起作用,只是给内核一个提示,事件表可能需要多大

2) epoll_ctl
用来操作内核事件表。

#include <sys/epoll.h>
int epoll_ctl(int epollfd, int op, struct epoll_event* event);
// epollfd   调用epoll_create()函数返回的内核事件表描述符
// fd        要操作的文件的描述符
// op        操作类型(EPOLL_CTL_ADD(添加)、 EPOLL_CTL_MOD(修改)、 EPOLL_CTL_DEL(删除))
// event     指定参数事件
// 调用成功返回 0  失败返回 -1

<2> epoll_wait函数
在一段超时时间内,等待一组文件描述符上的事件

int epoll_wait(int epollfd, struct epoll_event* events, int maxevents, int timeout);
// maxevents   表示最多可监听数
// 函数的执行过程: 
//    epoll_wait()函数若监听到了事件,就将所有就绪的事件从内核事件表(由epollfd指定)中复制到events指向的数组中,此数组只用作输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样既要用于用户注册,又要用于输出内核检测到的就绪事件

<3> LT 与 ET 模式
(epoll对文件描述符的操作模式,LT(电平触发)、ET(边沿触发))。

  • LT 模式是默认的工作模式,此模式下,epoll相当于一个效率较高的poll
  • ET 模式: 当往epoll内核事件表中注册一个文件描述符上的EPOLLET事件时,epoll将以ET模式来操作该文件描述符。

两者的对比在于:
对于一个文件描述符,当采用LT模式工作时,当epoll_wait检测到其上有事件发生时并将此事件通知应用程序之后,应用程序可以不立即处理,那么当程序下一次调用 epoll_wait时,epoll_wait会再次向应用程序通知此事。但当使用ET模式时,应用程序必须立即处理,因为后续epoll_wait调用将不会再次通知此事件

<4> 例子: 使用ET/LT模式接收数据

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

#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10

int setNonblocking(int fd){//设置文件描述符为非阻塞
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLCOK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

void addfd(int epollfd, int fd, bool enable_et){
//向内核时间表添加感兴趣的事件
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN;
    if(enable_et){
        event.events |= EPOLLET;
    }
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setNonblocking(fd);
}

void LT(int epollfd, int listenfd, int num, epoll_event* events){
    char buf[BUFFER_SIZE];
    for(itn i = 0; i < num; i++){
        int sockfd = events[i].data.fd;
        if(sockfd == listenfd){
            struct sockaddr_in client;
            socklen_t client_len = sizeof(client);
            int connfd = accept(listenfd, (struct sockaddr*)&client, &client_len);
            addfd(epollfd, fd, false);
        }else if(events[i].events & EPOLLIN){
        //由于使用的时LT模式,所以即使在此处只读取BUFFER_SIZE-1个字节的内容,由于会重复通知,我们最终也一样可以读取完所有内容
            memset(buf, '\0', BUFFER_SIZE);
            int ret = recv(fd, buf, BUFFER_SIZE-1, 0);
            if(ret <= 0){
                close(fd);
                break;
            }
            printf("get %d bytes datas from fd %d: %s\n", ret, sockfd, buf);
        }else{
            printf("something else happend.\n");
        }
    }
}

void ET(int epollfd, int listenfd, int num, epoll_event* events){
    char buf[BUFFER_SIZE];
    for(int i = 0; i < num; i++){
        int sockfd = events[i].data.fd;
        if(sockfd == listenfd){
            struct sockaddr_in client;
            socklen_t client_len;
            int connfd = accept(listenfd, (struct sockaddr*)&client, &client_len);
            addrfd(epollfd, fd, true);
        }else if(event[i].events & EPOLLIN){
        //使用ET模式,必须一次将所有的数据全部读出
            while(1){
                memset(buf,'\0', BUFFER_SIZE);
                int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
                if(ret < 0){
                    if(errno == EAGAIN || errno == EWOULDBLOCK){
                        printf("read later!\n");
                        break;
                    }
                    close(sockfd);
                    break;
                }else if(ret == 0){
                        close(sockfd);
                }else{
                        printf("get %d bytes data from fd %d: %s\n", ret, sockfd, buf);
                }
            }//end while
        }else{
            printf("something else happend.\n");
        }
    }
}

int main(int argc, char *argv[])
{
    if(argc != 3){
        printf("Usage: %s IP_ADDRESS PORT_NUMBER\n", basename(argv[0]));
        return 1;
    }
    const char *ip = argv[1];
    int port = atoi(argv[2]);

    struct sockaddr_in address;
    bzero(address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.port = htons(port);

    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenfd >= 0);

    int ret = bind(sockfd, (struct sockaddr*)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(listenfd, 5);
    assert(ret != -1);

    epoll_event event[MAX_EVENT_NUMBER];
    int epollfd = epoll_create(5);
    assert(epollfd != -1);
    addfd(epollfd, listenfd, true);
    while(1){
        ret = epoll_wait(epollfd, event, MAX_EVENT_NUMBER, -1);
        if(ret < 0){
            printf("epoll failure.\n");
            break;
        }
        LT(epollfd, listenfd, ret, &event);
       //ET(epollfd, listenfd, ret, &event);
    }
    close(sockfd);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值