高并发服务器--epoll相关函数

一、创建epoll_create()

1.功能

是 Linux 系统中用于创建一个 epoll 实例的系统调用。epoll 是一种高效的 I/O 事件通知机制,通常用于高性能网络服务器或需要处理大量文件描述符的场景。

2.函数原型

int epoll_create(int size);

3.参数

①. size: 指定 epoll 实例可以监控的最大文件描述符数量。这个参数在 Linux 内核 2.6.8 及更高版本中实际上被忽略,因为内核会动态调整资源分配。

4.返回值

①. 成功时返回一个非负的文件描述符,表示创建的 epoll 实例。
②. 失败时返回 -1,并设置 errno 为相应的错误码。

二、上树、下树、修改epoll_ctl()

1.功能

用于向 epoll 实例中添加、修改或删除文件描述符的事件监听。

2.函数原型

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

3.参数

①. epfd: 通过 epoll_create 或 epoll_create1 创建的 epoll 实例的文件描述符。
②. op: 操作类型,可以是以下之一:
        EPOLL_CTL_ADD: 添加一个新的文件描述符到 epoll 实例中。
        EPOLL_CTL_DEL: 从 epoll 实例中删除一个文件描述符。
        EPOLL_CTL_MOD: 修改一个已存在的文件描述符的事件掩码。
③. fd: 需要操作的文件描述符。
④. event: 指向 epoll_event 结构体的指针,用于指定监听的事件类型和关联数据。

struct epoll_event {
    uint32_t events;    // 事件掩码
    epoll_data_t data;  // 用户数据
};

events: 指定需要监听的事件类型,例如 EPOLLIN(可读)、EPOLLOUT(可写)等。
data: 用户数据,可以是文件描述符、指针或其他自定义数据。

4.返回值

①. 成功时返回 0。
②. 失败时返回 -1,并设置 errno 为相应的错误码。

三、监听epoll_wait()

1.功能

是 Linux 系统中用于等待 epoll 实例中事件发生的系统调用。它是 epoll 机制的核心部分,用于阻塞等待直到有事件发生或超时。

2.函数原型

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

3.参数

①. epfd: 通过 epoll_create 或 epoll_create1 创建的 epoll 实例的文件描述符。
②. events: 用于存储返回的事件的数组指针。每个事件由一个 epoll_event 结构体表示。
③. maxevents: 指定 events 数组的最大容量。必须大于 0。
④. timeout: 指定等待事件发生的超时时间(以毫秒为单位)。可以是以下值:
        正数:等待指定的毫秒数。
        0:立即返回,不阻塞。
        -1:无限期阻塞,直到有事件发生。

4.返回值

①. 成功时返回触发事件的数量(可能小于 maxevents)。
②. 失败时返回 -1,并设置 errno 为相应的错误码。
③. 如果超时返回 0。

四、epoll的两种触发模式

1.水平触发(Level-Triggered,LT)

 水平触发是epoll的默认模式,它在文件描述符处于就绪状态时持续通知应用程序,直到事件被处理完毕。
①. 触发条件:只要文件描述符处于就绪状态(例如可读或可写),epoll_wait就会持续返回该事件。
②. 事件处理:应用程序需要持续轮询事件,直到事件不再就绪。
③. 适用场景:适用于简单的场景,特别是当事件处理较快时。

2.边缘触发(Edge-Triggered,ET)

边缘触发只在文件描述符的状态发生变化时通知应用程序一次。

①. 触发条件:只有当文件描述符的状态从非就绪变为就绪时,epoll_wait才会返回该事件。
②. 事件处理:应用程序需要一次性处理所有就绪的事件,因为后续即使事件仍然就绪,也不会再次通知。
③. 适用场景:适用于高并发场景,因为它减少了事件通知的次数,从而提高了性能。

五、程序示例

1.管道中运用epoll

#include <stdio.h>
#include <string.h>
#include <sys/epoll.h>
#include <unistd.h>

int main()
{
        int fd[2];
        pid_t pid;
        pipe(fd);
        pid = fork();
        if(pid < 0)
                perror("fork");
        else if(pid == 0){
                close(fd[0]);
                char buf[5];
                char ch = 'a';
                while(1){
                        sleep(3);
                        memset(buf,ch++,sizeof(buf));
                        write(fd[1], buf, 5);
                }
        }
        else{
                close(fd[1]);
                int epfd = epoll_create(1);
                struct epoll_event ev, evs[1];
                ev.data.fd = fd[0];
                ev.events = EPOLLIN;
                epoll_ctl(epfd, EPOLL_CTL_ADD, fd[0], &ev);
                while(1){
                        int n = epoll_wait(epfd, evs, 1, -1);
                        if(n == 1){
                                char buf[128] = "";
                                int ret = read(fd[0], buf, sizeof(buf));
                                if(ret <= 0){
                                        close(fd[0]);
                                        epoll_ctl(epfd, EPOLL_CTL_DEL,fd[0], &ev);
                                        break;
                                }
                                else{
                                        printf("%s\n", buf);
                                }
                        }
                }
        }

        return 0;
}

2.epoll高并发服务器

①.创建套接字。Ifd = tcp4bind=>socket, bind
②.套接字监听。Listen
③.创建树。epfd = epoll_create
④.将Ifd上树。初始化ev、epoll_ctl
⑤.监听while{
        ⑥.监听epfd。nready = epoll_wait
        ⑦.如果返回值nready<0,break跳出循环。
        ⑧.如果返回值nready =0,contine继出续循环,
        ⑨. 如果返回值nready>0,说明epfd监听到变化
               ⑩ . 如果是cfd变化,连接cfd。Accept⑪.cfd上树。epoll_ct
                ⑫.如果是cfd变化
                ⑬.检查数据while(1){
                        ⑭.读客户端的数据n = read()
                        ⑮.如果n == 0,则关闭cfd,并且下树
                        ⑯.如果n>0,使用write将日志打印到终端页面,并write写回客户端。
                }
   }

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <error.h>
#include <fcntl.h>
#include "wrap.h"
#include <sys/epoll.h>

int main()
{

        //创建套接字
        int lfd = tcp4bind(8000, NULL);
        //套接字监听
        Listen(lfd, 128);
        //创建树
        int epfd = epoll_create(1);
        //将lfd上树
        struct epoll_event ev, evs[1024];
        ev.data.fd = lfd;
        ev.events = EPOLLIN;
        epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
        //监听
        while(1){
                int nready = epoll_wait(epfd, evs, 1024, -1);
                printf("epoll_wait------- \n");
                if(nready < 0){
                        perror("");
                        break;
                }
                else if(nready == 0){
                        continue;
                }
                else{
                        int i;
                        for(i = 0; i <nready; i++){
                                //lfd变化
                                if(evs[i].data.fd == lfd && evs[i].events & EPOLLIN){
                                        struct sockaddr_in cliaddr;
                                        char ip[16] = "";
                                        socklen_t len = sizeof(cliaddr);
                                        int cfd = Accept(lfd,(struct sockaddr *)&cliaddr, &len);
                                        printf("new client ip = %s, port = %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16), ntohs(cliaddr.sin_port));
                                        //设置cfd非堵塞
                                        int flags = fcntl(cfd, F_GETFL);
                                        flags |= O_NONBLOCK;
                                        fcntl(cfd, F_SETFL,flags);
                                        //cfd上树
                                        ev.data.fd = cfd;
                                        ev.events = EPOLLIN | EPOLLET; //设置为边缘触发
                                        epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
                                }
                                else if(evs[i].events & EPOLLIN){
                                //cfd变化
                                        while(1){
                                                char buf[4] = "";
                                                //如果读一个缓冲区,缓冲区没有数据,如果是带阻塞,就阻塞等待,如果
                                                //是非阻塞,返回值等于-1,并且会将errno 值设置为EAGAIN
                                                int n = read(evs[i].data.fd, buf, sizeof(buf));
                                                if(n < 0){
                                                        if(errno == EAGAIN){
                                                                break;
                                                        }

                                                        //普通错误
                                                        perror("");
                                                        close(evs[i].data.fd);//关闭cfd
                                                        epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]);//将cfd下树
                                                        break;
                                                }
                                                else if(n == 0){
                                                        printf("client colse\n");
                                                        close(evs[i].data.fd);//关闭cfd
                            epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]);//将cfd下树
                            break;
                                                }
                                                else{
                                                        write(STDOUT_FILENO, buf, 4);
                                                        write(evs[i].data.fd, buf, n);
                                                }
                                        }
                                }
                        }
                }
        }
        return 0;
}

六、epoll机制

参考:

Linux epoll完全图解,彻底搞懂epoll机制 - 知乎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值