多路复用-epoll

epoll的原理

epoll底层使用了红黑树和链表的结合解决了select的最高限制,并且更高效的对多个IO同时进行操作。epoll中的红黑树和链表在内存中统一一份,就绪链表只是在红黑树的基础上,对节点进行连接指向。

课前小黑板

  • 事件触发的两种模型
  1. ET: 边沿触发;buf中无数据到有数据才触发
  2. LT:水平触发;只要buf有数据就一直触发
  • io三种状态
    可读、可写、是否出错
    可读可写的判断依据为recvbuf是否有值以及sendbuf是否为空

epoll默认为水平触发。如何选择使用哪种触发?一般来讲

  1. 当实际的大小大于buf大小的时候,选择LT模式
  2. listen监听的时候选择LT模式,因为如果选择ET模式的话,发生3个以上客户端同时连接时,有可能漏掉个把
我们都知道了select是遍历所有的IO口,再在fd_set 中判断该io口是否就绪。而epoll则在epoll_wait时获取的event,就是准备就绪的链表

基本函数

  • 头文件

sys/epoll.h

epoll_create()

  • 函数作用
    用于创建一个epoll实例,返回实例的fd;
  • 函数原型
int epoll_create(int size);
  • 参数介绍
参数名说明
size最大监听的数量。目前该参数无意义,只有是否大于零的区别,如果需要有监听,则填写一个大于零的数即可

epoll_ctl()

  • 函数作用
    用于添加或删除所要监听的socket。成功返回0,失败返回-1.错误值可在errno中获取。

  • 函数原型

int epoll_ctl(int epfd,int op,int fd,struct epoll_event * event);
  • 参数介绍
参数名说明
eptd需要操作的文件描述符,即一开始create的那个epollfd
op操作类型
EPOLL_CTL_ADD:注册目标文件描述符fd
EPOLL_CTL_MOD:更改与fd相关联的事件
EPOLL_CTL_DEL:删除epfd中的fd
fd所要操作的socketfd
eventevent事件,设置一些事件类型,比如可读触发,边沿触发这些,具体可参看event事件

epoll_wait

  • 函数作用
    等待通过epoll句柄fd找到的就绪事件。函数返回准备好的事件数量。

  • 函数原型

int epoll_wait(int _epfd, struct epoll_event *_events, int _maxevents, int _timeout)
  • 参数介绍
参数名说明
_epfd总的句柄
_events准备好的事件集合
_maxevents希望返回的最大事件数量,一般为events的数组大小
_timeout最大等待时长,单位为毫秒。-1==infinite

event事件

以下列出部分event事件,全部的话可在epoll.h中查看

事件名事件说明
EPOLLIN可读
EPOLLPRI有紧急的事件可以读
EPOLLOUT可写
EPOLLET边沿触发,默认为水平触发

用epoll搭建一个简单的服务器

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>

#include <sys/socket.h>
#include <sys/epoll.h>
#include <errno.h>
#include <arpa/inet.h>

#define EPOLL_SIZE 1024
#define BUFFER_LENGTH 1024

int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
     printf("sockfd is %d\n", sockfd);
    struct sockaddr_in servAddr;
    memset(&servAddr, 0,sizeof(servAddr));

    servAddr.sin_family = AF_INET;
    servAddr.sin_port = htons(4022);
    servAddr.sin_addr.s_addr = INADDR_ANY;
    if(bind(sockfd, (struct sockaddr*)&servAddr,sizeof(struct sockaddr_in)) < 0)
    {
        printf("bind sockfd error!\n");
        return -1;
    }

    if(listen(sockfd, 5) < 0)
    {
        printf("listen error!\n");
        return -1;
    }

    int epollFd = epoll_create(EPOLL_SIZE);

    struct epoll_event ev, events[EPOLL_SIZE] = {0};

    ev.events = EPOLLIN;
    ev.data.fd = sockfd;
    epoll_ctl(epollFd, EPOLL_CTL_ADD, sockfd, &ev);

    while(1)
    {
        int nReady = epoll_wait(epollFd, events, EPOLL_SIZE, -1);

        if (nReady == -1)
        {
            printf("nReady error!\n");
            break;
        }

        for (int i = 0; i < nReady; i++)
        {
                printf("2 \n");

            if (events[i].data.fd == sockfd)
            {
                struct sockaddr_in cliAddr;
                memset(&cliAddr, 0, sizeof(cliAddr));
                int cliLen = sizeof(cliAddr);
                printf("1 :%d\n");

                int clifd = accept(sockfd, (struct sockaddr*)&cliAddr, &cliLen);
                printf("clifd :%d\n", clifd);
                if (clifd <= 0)
                {
                    continue;
                }

                char str[INET_ADDRSTRLEN] = {0};
                printf("recvfrom %s at port %d, sockfd:%d, clifd:%d\n", inet_ntop(AF_INET, &cliAddr.sin_addr, str, sizeof(str)),
                 ntohs(cliAddr.sin_port), sockfd, clifd);

                //将接收到的io口也放入观察集中
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = clifd;
                epoll_ctl(epollFd, EPOLL_CTL_ADD, clifd, &ev);
                
            }
            else
            {
                int clifd = events[i].data.fd;

                char buffer[BUFFER_LENGTH] = {0};
                int ret = recv(clifd, buffer, BUFFER_LENGTH, 0);
                if (ret < 0)
                {
                    if (errno == EAGAIN || errno == EWOULDBLOCK)
                    {
                        printf("read all data ready!\n");
                    }

                    close(clifd);

                    ev.events = EPOLLIN | EPOLLET;
                    ev.data.fd = clifd;
                    epoll_ctl(epollFd, EPOLL_CTL_DEL, clifd, &ev);
                }
                else if(ret == 0)
                {
                    printf("disconnect %d \n", clifd);

                    close(clifd);

                    ev.events = EPOLLIN | EPOLLET;
                    ev.data.fd = clifd;
                    epoll_ctl(epollFd, EPOLL_CTL_DEL, clifd, &ev);
                    continue;
                }
                else
                {
                    printf("recv: %s, %dBytes", buffer, ret);
                }
            }
            
        }
        
        
    }

    return 0;
    
}

总结

 epoll 相较于 select来说在处理更多的io时,更为高效。这两种多路复用的原理都是将数据等待和读取数据分离开来。select用到了位图,而epoll使用的红黑树和链表。
 遗憾的是不同的操作系统特供的 epoll 接口有很大差异,所以使用类似于 epoll 的接口实现具有较好跨平台能力的服务器会比较困难。比如mac电脑系统就没有epoll,只有kqueue,与epoll应用类似。

  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值