当前位置: 首页 > article >正文

3.20-epoll 函数

epoll 函数

如果内存 1G, epoll 支持 100k 连接

  • 不能跨平台
  • 支持的并发量很大

IO 多路转接函数

  • select
    • 跨平台: 支持
      • 在 Windows 平台: select的第一个参数是无意义的
      • 在 linux 平台: 第一个参数的意义是最大的文件描述符 + 1
    • 检测的连接数
      • 最大 1024
    • 检测方式和效率
      • 线性检测, 文件描述符越多, 效率越低
      • 使用 select 会发生多次数据拷贝
        • 内核区拷贝到用户区
        • 用户区拷贝到内核区
    • 传出信息的量:
      • 有多少描述符发生变化 -> 返回值
      • 到底是谁发生变化需要手动检测内核传出的集合
  • poll
    • 跨平台: 不支持, 仅支持 linux
    • 检测的连接数
      • 和内存有关
    • 检测方式和效率
      • 线性检测, 文件描述符越多, 效率越低
  • epoll
    • 跨平台: 不支持, 仅支持 linux
    • 检测的连接数
      • 和内存有关
    • 检测方式和效率
      • 树状(红黑树)模型, 检测效率很高
      • 委托 epoll 检测的文件描述符集合用户和内核使用的是同一块内存, 没有数据的拷贝
        • 使用了共享内存
    • 传出信息的量:
      • 有多少描述符发生变化 -> 返回值
      • 可以精确地知道是哪个文件描述符发生了变化

epoll 的使用

// epoll 使用步骤
// epoll 是一个模型, 使用 epoll 需要调用三个函数
/*
	1. 需要创建一个树状模型, 没有节点
	2. 要检测的节点添加到 epoll 树上
		文件描述符的类型:
			- 监听的
			- 通信的
        从检测的事件上说:
			- 读
			- 写
    3. 开始委托内核对树上的节点进行检测
    4. 处理的过程
    	- 监听的: 建立新的连接
    	- 通信的: 接受和发送数据
*/
  • 函数
#include <sys/epoll.h>
// 创建一个 epoll 模型
int epoll_create(int size);
size:
	该参数无意义, > 0 即可
返回值:
	- 成功: 返回一个有效的文件描述符. 可以理解为红黑树的根节点, 通过这个文件描述符就可以访问创建的实例
    - 失败: -1, errno
        
struct epoll_event {
   uint32_t      events;  /* Epoll events */
   epoll_data_t  data;    /* User data variable */
};
- events: 
	- EPOLLIN: 读事件, 检测文件描述符的读缓冲区
    - EPOLLOUT: 写事件, 检测文件描述符的写缓冲区
- data.fd:
	使用 epoll_ctl 的第三个参数

union epoll_data {
   void     *ptr;
   int       fd; // 常用的一个成员
   uint32_t  u32;
   uint64_t  u64;
};

        
// 对 epoll 的操作函数
int epoll_ctl(int epfd, int op, int fd,
             struct epoll_event *_Nullable event);
参数:
	- epoll_create() 的返回值, 找到 epoll 树的实例
    - op:
		- EPOLL_CTL_ADD: 添加新节点
		- EPOLL_CTL_MOD: 修改已经添加到树上的节点
            - 如原来检测的是读, 可以改为写事件
        - EPOLL_CTL_DEL: 删除
    - fd: 要操作的文件描述符
        - 如何操作: 添加 / 修改 / 删除
        - 种类:
			- 监听的
            - 通信的
    - event:
		- 添加: 设置要检测的文件描述符的事件
		- 修改: 修改对应
		- 删除: NULL
        
// 阻塞函数
// 委托内核检测 epoll 树上的文件描述符的状态, 没有状态变化, 该函数一直阻塞
// 有满足条件的文件描述符被检测到, 函数返回
int epoll_wait(int epfd, struct epoll_event *events,
              int maxevents, int timeout);
参数:
	- epoll_creat 的返回值, 找到 epoll 的实例
    - events: 传出参数, 记录了当前一轮检测所有的发生变化的文件描述符信息
        - 这个参数是一个结构体数组的地址
    - maxevent: 指定第二个参数的容量
    - timeout: 超时时长, -1 阻塞, 0 不阻塞
        
返回值:
	成功: 有多少个文件描述符发生了变化
int main()
{
    // 1. 创建监听的套接字
    int lfd = socket();
    // 2. 绑定
    bind();
    // 3. 设置监听
    listen();
    // 4. 创建 epoll 模型
    int epfd = epoll_create();
    // 5. 将要检测的文件描述符添加到 epoll 模型中
	struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = lfd;
    epoll_ctl(epfd, epoll_ctl_add, lfd, &ev);
    
    // 6. 开始检测
    struct epoll_event events[1024];
    while (1)
    {
        int num = epoll_wait(epfd, events, 1024, -1);
        // 处理 num 个有状态变化的文件描述符
        for (int i= 0; i < num; i++)
        {
            int curfd = events[i].data.fd;
            if (curfd == fd)
            {
                int cfd = accept(lfd, NULL, NULL);
                // cfd 添加到 epoll 中
                ev.events = EPOLLIN;
             	ev.data.fd = cfd;
                epoll_ctl(epfd, epoll_ctl_add, cfd, &ev);
            }
            else
            {
                // 通信
                int len = recv(curfd, buf, size - 1)
                if (len == 0)
                {
                    printf("客户端断开连接\n");
                    // 删除
                    epoll_ctl(epfd, epoll_stl_del, curfd, NULL);
                    colse(curfd);
                }
                else if (len > 0)
                {
                    send
                }
                else
                {
                    perror("recv");
                    exit(0);
                }
            }
        }
    }
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/epoll.h>

int main()
{
    // 1. 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (lfd == -1)
    {
        perror("socket");
        exit(0);
    }

    // 2. 绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET; // ipv4
    addr.sin_port = htons(8989); // 转网络字节序
    addr.sin_addr.s_addr = INADDR_ANY; // 0 地址
    if (-1 == bind(lfd, (struct sockaddr*)&addr, sizeof addr))
    {
        perror("bind");
        exit(0);
    }

    //  3. 设置监听
    if (-1 == listen(lfd, 128))
    {
        perror("listen");
        exit(0);
    }

    // 4. 创建 epoll 模型
    int epfd = epoll_create(100);
    if (epfd == -1)
    {
        perror("epfd");
        exit(0);
    }

    // 5. 将要检测的节点添加到 epoll 模型中
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = lfd;
    if (-1 == epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev))
    {
        perror("epoll_ctrl");
        exit(0);
    }

    struct epoll_event evs[1024];
    int size = sizeof evs / sizeof evs[0];
    // 6. 不停地委托内核检测 epoll 模型中的文件描述符状态
    while (1)
    {
        int num = epoll_wait(epfd, evs, size, -1);
        printf("num = %d\n", num);
        for (int i = 0; i < num; i++)
        {
            // 取出当前元素中的文件描述符
            int curfd = evs[i].data.fd;
            if (curfd == lfd)
            {
                // 建立新连接, 本次调用绝对不会阻塞
                int cfd = accept(lfd, NULL, NULL);
                ev.events = EPOLLIN;
                ev.data.fd = cfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
            }
            else
            {
                char buf[1024];
                memset(buf, 0, sizeof buf);
                int len = recv(curfd, buf, sizeof buf, 0);
                if (len == 0)
                {
                    printf("连接中断\n");
                    epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
                    close(curfd);
                }
                else if (len > 0)
                {
                    printf("recv: %s\n", buf);
                    send(curfd, buf, len, 0);
                }
                else
                {
                    perror("recv");
                    exit(0);
                }
            }

        }
    }

    close(lfd);
    return 0;
}

http://www.kler.cn/a/596373.html

相关文章:

  • 通俗易懂搞懂@RequestParam 和 @RequestBody
  • 2025年01月03日微创网络(杭州银行外包)前端面试
  • OpenCV Objdetect 模块使用指南
  • 尝试将相机采集图像流程封装成相机采图类
  • 微信小游戏:跳一跳,自动化操作
  • BlockChain.java
  • 西门子200smart之modbus_TCP(做从站与第三方设备)通讯
  • 【机器学习】手撕封装PCA——将高维数据映射到低维数据的过程
  • TPCTF 2025 web 复现
  • 【unordered_set和unordered_map】—— 我与C++的不解之缘(二十七)
  • wyq01
  • geant4的主要模块及其作用:
  • 【大模型算法工程】大模型应用工具化、忠诚度以及知识库场景下PDF双栏解析问题的讨论
  • Apache Flink技术原理深入解析:任务执行流程全景图
  • spring boot 拦截器
  • 【Attention】SKAttention
  • MySQL密码修改的全部方式一篇详解
  • vue学习九
  • 红宝书第十一讲:超易懂版「ES6类与继承」零基础教程:用现实例子+图解实现
  • 生物信息复习笔记(3)——GEO数据库