epoll详解及坑

        epoll是select、poll 的改进版。

        使用select、poll的缺点:

        (1)调用select 时,需要将用户空间的所有fd集合拷贝进内核空间。

        (2)调用select 时,需要在内核空间遍历所有fd的状态。

        (3)select 支持的fd 数目有限,不超过1024。

 

        关于epoll的三个系统调用:epoll_create、epoll_ctl、epoll_wait:

epoll_create:

        #include <sys/epoll.h> 
        int epoll_create ( int size );

        功能:创建一个文件描述符作为“监听的一大堆fd”的标识。

        返回值:返回文件描述符epollfd。注意:使用完epoll后记得close(epollfd)。

        参数介绍:这里的size是早期设计时产生的,那时所有需监测的文件描述符放入hash表中,而现在时放入红黑树中,所以该参数现在并没有实际用到,但是必须 >0。

 

epoll_ctl:

        #include <sys/epoll.h>
        int epoll_ctl ( int epfd, int op, int fd, struct epoll_event *event );

        功能:将需监听的fd添加到epfd对应的红黑树上,其中形参中指定:(1)对fd的操作类型:是删除还是增加。(2)监听fd的哪些事件:读事件还是写事件。(3)监听事件的方式:是水平触发还是边沿触发。

        返回值:成功返回0,不成功返回-1。

        参数介绍:

        epfd:epoll_create 的返回值。

        op:操作方式。有三种:(1)向事件表中注事件,EPOLL_CTL_ADD。(2)修改fd上事件,EPOLL_CTL_MOD。(3)删除fd上事件,EPOLL_CTL_DEL。

        fd:要操作的文件描述符。

        event:指定事件。介绍epoll_event:

        struct epoll_event

        {
            __unit32_t events;    // epoll事件
            epoll_data_t data;     // 用户数据
        };

        介绍epoll_event 的两个成员:
events:
        EPOLLIN :  表示监听对应文件描述符,读事件(包括对端SOCKET正常关闭);
        EPOLLOUT:表示监听对应文件描述符,写事件;
        EPOLLPRI:  表示监听对应文件描述符,紧急数据可读事件(这里应该表示有带外数据到来);
        EPOLLERR: 表示监听文件描述符,发生错误事件;
        EPOLLHUP:表示监听文件描述符,被挂断事件;
        EPOLLET:   将epoll设为边沿触发(Edge Triggered)模式,该参数缺省状态为水平触发LT。
        EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socketfd的话,需重新把这个socketfd加入到EPOLL队列里。
data:在实际应用中多只会用到fd。
        typedef union epoll_data
        {
            void* ptr;              //fd相关用户数据
            int fd;                   //事件从属的文件描述符
            uint32_t u32;
            uint64_t u64;
        } epoll_data_t;

        epoll的水平触发和边沿触发:

        (1)水平触发LT:接受缓冲区不为空,对应fd一直处于“读就绪”状态;发送缓冲区不满,对应fd一直处于“写就绪”状态关于这里的缓冲区:读多少就少多少,写多少就多多少。所以水平触发,只要数据没读完,那么在epoll_wait中,就一直认为该fd处于就绪状态。

        (2)边沿触发ET:当fd对应的读缓冲区从”有“变为了”无“时,对应fd才处于”读就绪“。当fd对应的写缓冲区从”无“变为了”有“时,对应fd才处于”写就绪“。注意:一定是在”有“和”无“之间变化才能触发。

        上述是自己的总结。想通过代码了解到两种方式不同的,推荐大牛博客:https://www.cnblogs.com/lojunren/p/3856290.html

        epoll中的event什么时候移除:不处于就绪状态的fd,就会被移除,根据 LT/ET 而不同。

epoll_wait:

        #include <sys/epoll.h>
        int epoll_wait ( int epfd, struct epoll_event* events, int maxevents, int timeout );

        功能:发现并获得”就绪“状态的fd。

        返回值:成功,返回就绪文件描述符个数。失败返回-1,并设置errno(使用errno需#include <errno.h>)。

        参数介绍:

        (1) epfd:epoll_create创建的epollfd。

        (2) events:一个数组,若检测到某fd处于就绪状态,将事件从内核事件表复制到该数组。

        (3) maxevents:指定最多监听多少事件。

        (4) 坑一:timeout:epoll超时时间,单位毫秒。为-1表示阻塞,为0表示非阻塞,其他值表示超时时间。这里一定要注意,程序涉猎多线程和多进程时,这个timeout的设置初学时也容易出错。我们来说明以下几种设置方式:

1. 设置为-1,程序阻塞在此,后续任务没法执行。

2. 设置为0,程序能继续跑,但即使没事件时,程序也在空转,十分占用cpu时间片,我测试时每个进程都是60+%的cpu占用时间。

3. 综上,我们给出比较好的设置方法:将其设置为1,但还没完,因为即使这样设置,处理其它任务时,
在每次循环都会在这浪费1ms的阻塞时间,多次循环后性能损失就比较明显了。为了避免该现象,我们通常
向epoll再添加一个fd,我们有其它任务要执行时直接向该fd随便写入一个字节,将epoll唤醒从而跳过
阻塞时间。没任务时epoll超过阻塞时间1ms也会自动挂起,不会占用cpu,两全其美。

 

        坑二:在多进程中,epoll的创建和添加,最好放在同一进程中进行。本人就因此遇上了问题:

        坑的创建:父进程创建epoll,子进程有4步操作:(1) socket;(2) 设置reuseport避免“惊群”;(3) 将socket_fd添加进epoll;(4) 子进程通过epoll_wait等待然后accept;这样做是错误的,运行程序会报错:Resource temporarily unavailable。

        产生坑的原因:多个子进程socket产生的socket_fd是相同的数值,但表示不同的套接字(详见“网络编程”),所以实际上加入父进程epoll的,应该只是某一子进程的套接字,如果有客户端来connect,由于设置了reuseport,只有一个子进程来处理该事件,调用accept,至于能否accept成功取决于:添加入epoll的套接字是否是该子进程创建的套接字,那么只有 1/(进程数) 的概率accept 成功。

        综上述:epoll的三个系统调用最好在同一进程内使用,会方便很多。

        注:accept本身已经有效避免“惊群”了,引起“惊群”的是:epoll/select/poll等。

       

 

### 构建任务失败解决方案 当遇到 `Execution failed for task ':app:shrinkReleaseRes'` 错误时,这通常意味着资源压缩过程中出现了问题。此错误可能由多种原因引起,包括但不限于配置不正确、依赖冲突或特定于项目的其他因素。 #### 可能的原因分析 1. **ProGuard 或 R8 配置不当** ProGuard R8 是用于优化混淆代码以及减少 APK 大小的工具。如果这些工具的配置存在问题,可能会导致资源无法正常处理[^1]。 2. **重复资源** 如果项目中有多个模块定义了相同的资源名称,可能导致冲突并引发该错误。检查是否存在重名的 drawable、string 等资源文件[^2]。 3. **第三方库兼容性** 某些第三方库可能当前使用的 Gradle 插件版本或其他库存在兼容性问题,从而影响到资源打包过程中的行为[^3]。 4. **Gradle 缓存问题** 有时旧缓存数据会干扰新编译的结果,尝试清理本地仓库重新同步项目可以帮助排除此类潜在障碍[^4]。 #### 推荐的操作方法 为了有效解决问题,建议按照以下步骤逐一排查: ```bash # 清理项目构建目录 ./gradlew clean # 删除 .gradle 文件夹下的所有内容以清除缓存 rm -rf ~/.gradle/caches/ ``` 调整 `build.gradle` 中的相关设置也是一个重要环节: ```groovy android { ... buildTypes { release { minifyEnabled true // 是否启用代码缩减 shrinkResources true // 是否开启资源压缩 proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' // 尝试禁用 shrinkResources 来测试是否为资源压缩引起的错误 // shrinkResources false } } } ``` 此外,在 `proguard-rules.pro` 文件内添加必要的保留规则,防止关键类被意外移除: ```text -keep class com.example.yourpackage.** { *; } # 替换为你自己的包路径 -dontwarn androidx.**,com.google.** # 忽略警告信息 ``` 最后,确保所使用的 Android Studio 版本是最新的稳定版,并且已经应用了所有的补丁更新。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值