Epoll多路I/O复用技术

8 篇文章 0 订阅
5 篇文章 0 订阅

Epoll多路I/O复用技术

通常学习一个新的linux技术,我们应该看看man手册对其定义。

NAME
   epoll - I/O event notification facility

SYNOPSIS
   #include <sys/epoll.h>

DESCRIPTION
   The  epoll  API performs a similar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of them.  
   The epoll API can be used either as an edge-triggered or a level-triggered interface and scales well to large  numbers  of  
watched  file descriptors. 

那么从man手册这段文字我们可以看出,epoll它是由linux另一套的并发处理方案poll演变过来的,它与poll相类似:能够监控多个文件描述符的I/O变化。在Linux中,一切皆文件(有部分不是)所以,任何一个连接,也有一个文件描述符(一般为int类型)来存放

重点:epoll比poll的优点:支持水平触发(level-triggered)边沿触发(edge-triggered)两种方案

Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。
Level Triggered (LT) 水平触发只要有数据都会触发。

epoll所需的API函数:

  • epoll_create(2) creates an epoll instance and returns a file descriptor referring to that instance. (The more recent epoll_create1(2) extends the functionality of epoll_create(2).)

  • Interest in particular file descriptors is then registered via epoll_ctl(2). The set of file descriptors currently registered on an epoll instance is sometimes called an epoll set.

  • epoll_wait(2) waits for I/O events, blocking the calling thread if no events are currently available.


1、创建epoll文件描述符

创建一个epoll句柄,参数size用来告诉内核监听的文件描述符的个数,跟内存大小有关。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

int epoll_create(int size);      //size:监听数目

2、管理epoll中的文件描述符集合,增加、修改、删除

epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

第一个参数是epoll_create()的返回值
第二个参数表示动作,用三个宏来表示

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

第三个参数是需要监听的fd
第四个参数是告诉内核需要监听什么事

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

3、收集在epoll监控的事件中已经发送的事件(默认阻塞等待)

int epoll_wait(int epfd, struct epoll_event *events,
                int maxevents, int timeout);
/*
    events:用来从内核得到事件的集合,
    maxevents:告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
    timeout:是超时时间
    -1:阻塞
    0:立即返回,非阻塞
    >0:指定微秒
    返回值:成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1
*/

epoll工作原理

1、epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。

2、另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

epoll 服务端例子

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/epoll.h>
#include <fcntl.h>

#include <unistd.h>
#include <errno.h>

int main()
{
    int sock_server = socket(AF_INET,SOCK_STREAM,0);

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(10099);
    addr.sin_addr.s_addr = 0;

    int ret = bind(sock_server,(struct sockaddr*)&addr,sizeof(addr));

    listen(sock_server,5);

    int epollfd = epoll_create(2);

    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = sock_server;
    //吧sock_server加入eopll集合中
    epoll_ctl(epollfd,EPOLL_CTL_ADD,sock_server,&ev);

    while(1)
    {
        struct epoll_event outev[8];
        int ret = epoll_wait(epollfd,outev,8,1000);
        if (ret < 0)
        {
            if (errno == EINTR)//若被信号打断,则重新循环
                continue;
            break;
        }
        if (ret > 0)//有被唤醒的文件描述符
        {
            for(int i = 0 ; i<ret; i++)
            {
                int fd = outev[i].data.fd;
                if (fd == sock_server)
                {
                //若为socket的文件描述符,则用accept进行三次握手,建立连接
                    int newfd = accept(fd,NULL,NULL);
                //EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
                    ev.events = EPOLLIN;
                    ev.data.fd = newfd;
                //把新的fd加到epollfd中,继续等待下一次唤醒                   
                    epoll_ctl(epollfd,EPOLL_CTL_ADD,newfd,&ev);
                }
                else
                {
                //若不是socketfd,则为已经建立的连接,可以直接读取数据
                    char buf[1024];
                    int readlen = read(fd,buf,sizeof(buf));

                    if (readlen<=0)
                    {
                    //read<=0,证明已经没有数据,或者出错,关闭fd
                        close(fd);
                    }
                    else
                    {
                        printf("read data is :%s\n",buf);
                    }
                }
            }
        }
    }
    return 0;
}

测试epoll的客户端

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/epoll.h>
#include <fcntl.h>

#include <unistd.h>
#include <errno.h>
int main()
{
    int fd = socket(AF_INET,SOCK_STREAM,0);

    struct sockaddr_in addr;

    addr.sin_family = AF_INET;
    addr.sin_port = htons(10099);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int ret = connect(fd , (struct sockaddr*)&addr, sizeof(addr));

    write(fd,"hello server",sizeof("hello server"));

    char buf[1024];
    read(fd,buf,sizeof(buf));
    printf("server:%s\n",buf);
    close(fd);
    return 0;
}

测试结果我就暂时不贴图了,希望这次博客能给大家带来一点收获!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值