I/O复用:epoll函数

    epoll函数是Linux特有的I/O复用函数。它在实现和使用上与select、poll有很大的差异。首先,epoll函数使用一组函数来完成任务,而不是单个函数。其次,epoll把用户关心的文件符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都要重复传入文件描述符集或事件集。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。这个文件描述符使用如下的epoll_create函数来创建:

     #include<epoll.h>

     int  epoll_create(int size)

     size参数现在并不起作用,只是给内核一个提示,告诉它事件表需要多大,该函数返回的文件描述符将用作其他所有的epoll系统调用的第一个参数,以指定要访问的内核事件表。

     下面的函数用来操作epoll的内核事件表:

      #include<sys/epoll.h>

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

     fd参数是要操作的文件描述符,op参数指定操作类型。操作类型有如下3种:
     (1)EPOLL_CTL_ADD,往事件表中注册fd上的事件。

     (2)EPOLL_CTL_MOD,修改fd上的注册事件。

     (3)EPOLL_CTL_DEL,删除fd上的注册事件。

     event参数指定事件,它是epoll_event结构体指针类型。epoll_event的定义如下:
     struct epoll_event

     {

            __uint32_t events; /* epoll事件*/

           epoll_data_t  data;/*用户数据*/

     };

     其中events成员描述事件类型。epoll支持的事件类型和poll基本相同。表示epoll事件类型的宏是在poll对应的宏前加上“E”,比如epoll的数据可读事件是EPOLLIN。但epoll有两个额外的事件类型-------EPOLLET和EPOLLONESHOT。它们对于epoll的高效运作非常关键,我们将在后面讨论。data成员用于存储数据,其类型epoll_data_t定义如下:
     typedef union epoll_data

     {

              void *ptr;

              int fd;

              uint32_t  u32;

              uint64_t  u64;

     }epoll_data_t ;

     epoll_data_t 是一个联合体,其4个成员中使用最多的是fd,它指定事件所从属的目标文件描述符。ptr成员可用来指定与fd相关的用户数据。但由于epoll_data_t是一个联合体,我们不能同时使用其ptr成员和fd成员,因此,如果要将文件描述符和用户数据关联起来,以实现快速的数据访问,只能使用其他手段,比如放弃epoll_data_t的fd成员,而在ptr指向的用户数据中包含fd。

     epoll_ctl成功时返回0,失败时则返回-1并置errno。

epoll_wait函数:
     epoll系列系统的主要接口是epoll_wait函数。它在一段超时时间内等待一组文件描述符上的事件,其原型如下:
      #include<sys/epoll.h>

      int epoll_wait(int epfd,struct epoll_event* event,int maxevents,int timeout)

      该函数成功时返回就绪文件描述符的个数。timeout参回的含义与poll接口的timeout参数相同。maxevents参数指定最多监听多少个事件,它必须大于0。

       epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表(由epfd参数指定)中复制到它的第二个参数events指向的数组中。这个数组只能用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数既用于传入用户注册的事件,又用于输出内核检测到的就绪事件。这就极大地提高了应用程序索引就绪文件描述符的效率。

       我们同样给出一个简单的例子,回射程序:
服务器:

#include"../unp.h"
#include"utili.h"
#include<stdlib.h>

int sock_bind(const char *ip, short port)
{
    int fd;
    //创建套接字
    fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addrSer;
    addrSer.sin_family = AF_INET;
    addrSer.sin_port = htons(port);
    addrSer.sin_addr.s_addr = inet_addr(ip);
    socklen_t addrlen = sizeof(struct sockaddr);
    //绑定
    bind(fd, (struct sockaddr*)&addrSer, addrlen);
    return fd;
}

//客户端连接服务器
void handle_accept(int epollfd, int listenfd)
{
    struct sockaddr_in addrCli;
    int sockConn;
    socklen_t addrlen = sizeof(struct sockaddr);
    sockConn = accept(listenfd, (struct sockaddr*)&addrCli, &addrlen);
    if(sockConn == -1)
        perror("accept");
    else
    {
        printf("accept a new client:%s:%d\n",inet_ntoa(addrCli.sin_addr),addrCli.sin_port);
        add_event(epollfd, sockConn, EPOLLIN);
    }
}
//服务器读数据
void do_read(int epollfd, int fd, char *buf)
{
    int nread = read(fd, buf, 256);
    //当n等于0说明客户端断开连接
    if(nread <= 0)
    {
        printf("Client is Closed.\n");
        close(fd);
        //删除fd的注册事件
        delete_event(epollfd, fd, EPOLLIN);
    }
    printf("recv msg:>%s\n",buf);
    //将事件改变为写
    modify_event(epollfd, fd, EPOLLOUT);
}

//服务器向客户端写数据
void do_write(int epollfd, int fd, char *buf)
{
    int nwrite = write(fd, buf, strlen(buf)+1);
    //当nwrite为0时,表示服务器关闭
    if(nwrite <= 0)
    {
        printf("server is closed.\n");
        close(fd);
        delete_event(epollfd, fd, EPOLLOUT);
    }
    else
        //将事件改变为读
        modify_event(epollfd, fd, EPOLLIN);
}


void handle_events(int epollfd, epoll_event *events, int num, int listenfd, char *buf)
{
    int fd;
    for(int 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);
    }
}

void do_epoll(int listenfd)
{
    int epollfd;
    epoll_event events[1024];
    //创建文件描述符,唯一标识内核的事件表
    epollfd = epoll_create(FDSIZE);
    //增加listenfd的读事件
    add_event(epollfd,listenfd, EPOLLIN);
    int res;
    char buf[256];
    for(;;)
    {
        //返回就绪的文件描述符的个数
        //并将就绪的事件从内核事件表复制到events中
        res = epoll_wait(epollfd, events, 1024,-1);
        if(res == -1)
        {
            perror("epoll_wait");
            exit(1);
        }
        handle_events(epollfd, events, res, listenfd, buf);
    }
    close(epollfd);
}

int main()
{
    int listenfd;
    listenfd = sock_bind(IPADDR, PORT);
    listen(listenfd, LISTENQ);
    do_epoll(listenfd);
    return 0;
}

客户端:
#include"../unp.h"
#include"utili.h"

void do_read(int epollfd,int fd,int sockfd,char*buf)
{
    int nread;
    nread = read(fd,buf,256);
    if(nread == -1)
    {
        perror("read");
        close(fd);
    }else {
        //从标准输入输出读
        if(fd == STDIN_FILENO)
            //读完增加可写事件
            add_event(epollfd,sockfd,EPOLLOUT);
        else{
            //删除套接字的可读事件
            delete_event(epollfd,sockfd,EPOLLIN);
            //增加标准输入输出的可写事件
            add_event(epollfd,STDOUT_FILENO,EPOLLOUT);
        }
    }
}

void do_write(int epollfd,int fd,int sockfd,char*buf)
{
    int nwrite;
    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);
    }
}

void handle_events(int epollfd,epoll_event *events,int num,int sockfd,char*buf)
{
    int fd;
    for(int 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 & EPOLLOUT)
            //可写事件
            do_write(epollfd,fd,sockfd,buf);
    }
}

void handle_connection(int sockfd)
{
    int epollfd;
    epoll_event events[1024];
    //创建内核注册表
    epollfd = epoll_create(FDSIZE);
    //增加标准输入输出的可读事件
    add_event(epollfd,STDIN_FILENO,EPOLLIN);
    int ret;
    char buf[256];
    for(;;)
    {
        ret = epoll_wait(epollfd,events,1024,-1);
        handle_events(epollfd,events,ret,sockfd,buf);
    }
    close(epollfd);
}

int main(int argc,char* argv[])
{
    int sockCli;
    //创建套接字
    sockCli = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in addrSer;
    addrSer.sin_family = AF_INET;
    addrSer.sin_port = htons(PORT);
    addrSer.sin_addr.s_addr = inet_addr(IPADDR);

    //连接服务器
    connect(sockCli,(struct sockaddr* )&addrSer,sizeof(struct sockaddr));

    handle_connection(sockCli);
    return 0;
}

头文件utili.h:
#pragma once
#include"../unp.h"

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

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

头文件unp.h:
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<poll.h>
#include<sys/epoll.h>
#include<sys/types.h>

#define IPADDR  "127.0.0.1"
#define PORT    8787
#define MAXLINE 1024
#define LISTENQ 5

//select
#define SIZE 10
//poll
#define OPEN_SIZE 10
//epoll
#define FDSIZE 100

程序执行结果


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值