Android中的动画详解系列【2】——飞舞的蝴蝶

1.select和poll模型为什么会慢。
    假如有100w用户和一个进程保持tcp连接,而每一个时刻只有几十个活跃的连接,也就是说,每一个时刻进程只需要处理这100w连接中的一小部分。那么如何高效的处理?进程是否在每次询问操作系统收集有事件发生的tcp连接时,把这100w个连接告诉操作系统,然后由操作系统来找出发生的连接?select和poll正是这么做的。
    这里有个明显的问题,就是在某一时候,进程收集有事件的连接时,其实这100w连接中的大部分都没有事件发生。因此每次收集事件时,都把这100w连接的套接字告诉操作系统,然后由操作系统内核寻找这些连接上是否发生事件,将会是巨大的浪费。

2.epoll对象和事件关系
    每个epoll对象对应一个eventpoll结构体。这个结构体中包含保存监听事件的红黑树字段和满足条件的双向链表字段。详见深入理解nginx的312页。

    所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调函数,也就是说,相应事件发生时会调用这里的回调函数。这个回调方法在内核中叫做ep_poll_callback,它会把就绪的事件放到上面的rdlist链表中。

    当调用epoll_wait检查是否有发生事件的连接时,只是检查eventpoll对象中的rdlist双向链表是否由epitem元素而已。如果rdlist链表不为空,则把这里的事件复制到用户态内存中,同时将事件数量返回給用户。因此epoll_wait效率相当的高。epoll_ctl向epoll对象中添加修改删除事件时,从rbr红黑树中查找事件也非常快,也就是说,epoll时非常高效的,他可以轻易的处理百万级别并发连接。


3.如何使用epoll
epoll使用下面三个系统调用为用户提供服务。
(1)epoll_create系统调用
epoll_create在c库的原型如下:
    int epoll_create(int size);
epoll_create返回一个句柄,之后epoll的使用都将依靠这个句柄来标示。参数size是告诉epoll所要处理的大致事件数目。不再使用epoll时,必须调用close关闭句柄。

(2)epoll_ctl系统调用
原型如下:
    int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
epoll_ctl向epoll对象中添加、修改、删除感兴趣的事件,返回0标示成功,否则返回-1,此时需要根据errno错误码判定错误类型。epoll_wait方法返回的事件必然是通过epoll_ctl添加到epoll中的。参数epfd是epoll_create返回的句柄。

    epfd:epoll_create返回值。
    fd:待检测的连接套接字。
    op:操作
    event:告诉epoll对什么样的事件感兴趣,它使用了epoll_event结构体,它使用了epoll_event结构体。
    struct epoll_event{
        uint32_t events;
        epoll_data_t data;
    };
    events:代表感兴趣事件的类型,如读、写等
    data成员是联合。
    typedef union epoll_data{
        void *ptr;
        int fd;
        uint32_t u32;
        uint64_t u64;
     }epoll_data_t;

(3)epoll_wait
 在c库原型如下:
    int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
    
    收集在epoll监控的事件已经发生的事件,如果epoll没有事件发生,则最多等待timeout毫秒返回。epoll_wait返回值表示当时发生的事件个数,如果返回0,则表示本次调用中没有事件发生,如果-1表示出现错误。

    epfd:epoll描述符
    events:则是分配好的epoll_event结构体数组,epoll将会把发生的事件复制到events数组中(events不可以是空,内核只负责把数据复制到这个events数组中,不会帮助我们在用户态中分配内存。内核这种做法效率很高)。
    maxevents:表示本次可以返回的最大事件数目,通常maxevents参数与分配的events数组大小相当。
    timeout:表示没有检测到事件发生时最多等待的时间。如果timeout=0,则表示epoll_wait在rdlist为空时,立刻返回,不需等待。


4.epoll俩种工作模式:LT/ET

    LT(level triggered)是epoll缺省工作方式,并且同时支持block和no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不做任何的操作,内核还是会继续通知你。
    ET(edge-triggered)是高速工作方式,只支持no-block socket,它的效率要比LT更高。ET和LT的却别在于,当一个新的事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字没有新的事件在此到来时,在ET模式下时无法再次从epoll_wait调用中获取这个事件的。而LT模式相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取事件


epoll的优点:
1.支持一个进程打开大数目的socket描述符。
    select最不能忍受的一点是打开的FD数量是有限制的,由FD_SETSIZE设置,默认是2048.对于那些需要支持的上万连接数目的IM服务器来说显然太少。这时候你一定是选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降。不过epoll没有这个限制,它所支持的FD上限是最大可以打开文件数目,这个数字一般远大于2048。举个例子,在1GB内存的机器上大约是10w,和内存大小相关。

2.IO效率不随FD数目增加线性下降。
    传统的select/poll另外致命的缺点是当拥有大集合的套接字,常常只有部分套接字是活跃的,但是select/poll每次会线性扫描全部集合,导致效率的线性下降。但是epoll不存这个问题,它只会对活跃的socket进行操作--这是因为内核实现中epoll是根据每个fd上的callback函数实现的。那么只有活跃的socket才会主动的去调用callback函数,因为空闲的套接字则不会。

3.使用mmap加速内核与用户空间的消息传递
    这点实际上涉及到epoll的具体实现。无论select/poll还是epoll都需要将内核把FD消息传递給用户空间,如何避免不必要的内存拷贝很重要,在这点上,epoll是通过内核与用户空间映射同一块内存实现的。

Android中的动画详解系列【2】——飞舞的蝴蝶,布布扣,bubuko.com

Android中的动画详解系列【2】——飞舞的蝴蝶

上一篇:iOS - 获取文件MD5 :


下一篇:ios图片添加文字或者水印