TOP

20分钟了解Epoll + 聊天室实战(三)
2019-04-02 20:08:09 】 浏览:487
Tags:20分钟 了解 Epoll 聊天室 实战

pfd:epoll_create创建epoll实例,返回的文件描述符
  • evlist:epoll_event数组,epoll_wait调用会阻塞,直到有文件描述符ready,ready的文件描述符及发生的事件会保存到evlist
  • maxevents:evlist数组的长度
  • timeout:指定epoll_wait将阻塞多长时间
    • 设置为0,非阻塞模式,检查interest list是否有ready的文件描述符后立即返回
    • 设置为-1,永远的阻塞直到1>interest list中的一个或多个文件描述符就绪 2>被信号处理程序终断
    • 设置为正数:永远的阻塞直到1>interest list中的一个或多个文件描述符就绪 2>被信号处理程序终断 3> 指定的timeout毫秒到了,过期返回
  • 2、深入epoll实例

    2.1、先弄清文件描述符和打开文件的关系

    为了弄清楚打开文件、文件描述符和epoll之间的关系,我们先看一下Linux操作系统中文件描述符(file descriptors)、打开文件描述(open file description)、和系统级文件inode表之间的关系,如图所示:

    内核维护了3个数据结构:

    • 每个进程打开的文件描述符(file descriptor table)
      • fd flags:文件的flag,就是只读、只写等这些flag
      • file ptr:指向文件描述的指针
    • 系统中打开文件的描述(open file table)
      • file offset:当前文件的offset
      • status flags:对应open的flags参数
      • inode ptr:指向i-node对象的指针
    • 操作系统维护文件系统 (i-node table)
      • file type:文件类型
      • file locks:一个指向该文件锁相关的列表的指针
      • 以及其它各种标识这个文件相关信息

    图中可以看出:

    • A进程的fd1和fd20这两个文件描述符的file ptr指向同一个同一个open file description(可能执行了dup系统调用复制了文件描述符)
    • A进程fd2和B进程的fd2的file ptr也指向同一个open file description(B可能是A调用系统调用fork出来的子进程)
    • A进程的fd0和B进程的fd3指向不同的file description,但是指向的是同一个i-node,也就说打开的是同一个文件

    2.2、再看epoll_create

    当调用了epoll_create,事实上内核在inode-table创建了一个新的inode(也就是epoll实例),以及在file description table中添加一条打开文件描述记录,并且返回给调用者一个文件描述符。

    接下来我们就可以使用这个文件描述符,向epoll实例中添加需要监听的文件描述符,当调用epoll_ctl添加一个文件描述符到epoll实例的监听列表(interest list)的时候,事实上真正添加的不是文件描述符,而是其对应的文件描述(file description table)。基于这一点,结合上面文件描述符和打开文件关系的图:

    假如进程A调用epoll_create,返回文件描述符fd8。

    • 如果B是A fork出来的子进程,那么会继承A所有打开的文件描述符,包括fd8,所以进程A和进程B共享interest list,
    • A fork完 B 之后,又打开了一个文件,返回文件描述符fd3,然后调用epoll_ctl添加到interest list中,这时B中并不包括A新打开的文件描述符fd3,但是当fd3有I/O 事件的时候,A或B进程调用epoll_wait,都会收到fd3 IO ready的通知

    2.3、epoll为什么性能高于poll和select?

    回顾上述poll()和select()存在的问题,可以看到epoll刚好解决了这些问题:

    • 相比较poll和select,每次调用循环检查每一个文件描述符I/O状态,时间复杂度O(N),不管N大小,调用一次循环检查一次;epoll得益于监听的是文件描述符下一级的打开文件描述,并且每次调用epoll_wait的时候直接返回ready list就可以了,不需要循环
    • 相比较poll和select,每次调用都需要传递监听文件描述符数据到内核,epoll调用epoll_ctl一次性添加到epoll实例,接下来调用epoll_wait不需要再传递这些文件描述符信息过去了
    • 相比较poll()和select()返回所有的文件描述,epoll只返回ready的文件描述符

    监听不同数量文件描述符,执行100000次状态检查select、poll、epoll性能对比:

    3、关于level-trigger 和 edge-trigger

    3.1、文件描述符准备就绪通知的两种模型:

    • 水平触发:如果可以在不阻塞的情况下执行I/O系统调用,则认为文件描述符已经准备好了,触发通知
    • 边缘触发:文件描述符被监控以来,有新的I/O活动(例如 新的输入),触发通知

    3.2、不同的通知模型如何影响我们的程序设计?

    水平触发

    • 确定文件描述符的I/O状态已经ready
    • 已经ready,对这个文件描述符执行一些I/O操作(比如读取几个字节数据),然后继续监视它的IO状态
    • 仍然有未读取的数据,还会继续触发通知,也就是说不用一次执行完所有的IO操作(比如一次读取缓冲区的全部内容)

    边缘触发:

    • 只有新的IO事件发生时,才会触发通知
    • 直到下次IO事件发生,不会再触发通知

    注:对于边缘触发,当触发通知时,我们并不知道有多少IO可用(例如有多少字节可以读),所以一般使用边缘触发通知方式要遵循如下规则:

    1. 接收到事件通知后,程序应该尽可能多的执行IO(读写),因为仅仅通知这一次,不读取完毕,数据可能就丢失了
    2. 为了避免IO阻塞,每个被监视的文件描述符应该是非阻塞模式打开的,然后收到事件通知后,重复的执行IO直到返回错误信息

    接下来的聊天室的实例,我们使用边缘触发的方式

    三、Epoll实战:聊天室

    了解完epoll的基础和原理,使用一个精简版的聊天室的小实例来巩固学习。

    主要思路:一个服务端,可以接收多个客户端的连接,客户端发送的消息会同步到所有聊天室内的客户端

    1、服务端设计与实现

    第一步:服务端创建socket服务

    第二步:创建epoll实例,将socket服务fd加入interest list

    第三步:循环调用epoll_wait看是否有ready的文件描述符,如果有并且是我们的socket服务fd,说明是有新的客户端连接加入,保存客户端fd,并将fd加入interest list;如果非socket 服务fd,说明是客户端有新消息发送至服务端,接收消息然后广播给保存的所有客户端fd

    2、客户端设计与实现

    第一步:创建socket连接服务端

    第二步:创建管道,以便获取客户端输入

    第三步:创建epoll实例,并把socket fd和管道fd加入interest list

    第四步:fork子进程

    第五步:父进程调用epoll_wait获取ready list,如果fd是服务端sockt,则直接打印广播消息;如果fd是管道则为用户输入,读取管道中的消息,发到服务端


    20分钟了解Epoll + 聊天室实战(三) https://www.cppentry.com/bencandy.php?fid=45&id=217010

    首页 上一页 1 2 3 4 下一页 尾页 3/4/4
    】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
    上一篇[C]最大公约数和最小公倍数 下一篇C++编程丨C++11的指针