HighConcurrencyCommFramework c++通讯服务器框架 :Epoll:事件驱动技术

在单独的进程或者线程中运行,收集处理事件,没有上下文切换的消耗,高校;

写小demo很简单,正经让epoll技术融合到商业环境中,那么难度很大;

达到的效果:

1.理解工作原理;

2.开始写代码;

3.认可nginx epoll 源码;并且能复用;

四个函数对着源码讲

https://github.com/wangbojing/NtyTcp/blob/master/src/ntyepollrb.c

一位网友的手写源码

epoll_create();

epoll_ctl();

epoll_wait();

epolleventcallback();

学完再看;

epoll_create()
int epoll_create(int size); //size >0就行;
功能 :创建一个epoll对象,返回该对象的描述符,描述符就是epoll对象 epfd ,最后要用clsoe关闭

原理:
rbr:
  分配一个指针内存,代表红黑树的根节点,指向空;
  红黑树,用来保存,键【数字】、值【成员】对 一起的,查找速度特别快,能快速通过key值把value取出
rdist:
  代表一个双向链表的表头指针;遍历很快;

 总结:创建了一个eventpoll对象,被系统保存
       rbr初始化成一课红黑树的根
       rlist 成员初始化指向一个双向链表的根
epoll_ctl()
int epoll_ctl(int epfd,int op,int sockfd,struct epoll_event*event);
参数:
  epfd :epoll_create()返回的epoll文件描述符
  op :动作,添加,删除,修改,对应数字是1,2,3,EPOLL_CTL_ADD,EPOLL_CTL_DEL,EPOLL_CTL_MOD
      添加事件:等于往红黑树中添加节点,每个客户端链接的服务器后,服务器都会产生一个对应的socket,每个连接这个socket都不重复,这个socket就是红黑树的key,把节点添加到红黑树上去;   
      修改事件:你用了add之后才可以修改
      删除动作:是从红黑树把这个节点制空,这回导致这个socket这个TCP连接上无法收到任何系统通知事件;
  sockfd:文件描述符
  event:事件信息;

 红黑树的节点是由epoll_ctl()add 
epoll_wait()
int epoll_wait(int epfd,struct epoll_event*events,int maxevent,int timeout)
系统将可读事件通过回调函数将变化的套接字加入到双向链表里,然后链表,系统将可读事件拷贝回用户空间的集合返回总个数,用户来遍历,都是已经经过变化的连接直接

epoll 通过两个方面,很好解决了 select/poll 的问题。

第一点,epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述字,把需要监控的 socket 通过 epoll_ctl() 函数加入内核中的红黑树里,红黑树是个高效的数据结构,增删改一般时间复杂度是 O(logn)。而 select/poll 内核里没有类似 epoll 红黑树这种保存所有待检测的 socket 的数据结构,所以 select/poll 每次操作时都传入整个 socket 集合给内核,而 epoll 因为在内核维护了红黑树,可以保存所有待检测的 socket ,所以只需要传入一个待检测的 socket,减少了内核和用户空间大量的数据拷贝和内存分配。

第二点, epoll 使用事件驱动的机制,内核里维护了一个链表来记录就绪事件,当某个 socket 有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中,当用户调用 epoll_wait() 函数时,只会返回有事件发生的文件描述符的个数,不需要像 select/poll 那样轮询扫描整个 socket 集合,大大提高了检测的效率。

从下图你可以看到 epoll 相关的接口作用:

https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8/epoll.png

epoll 的方式即使监听的 Socket 数量越多的时候,效率不会大幅度降低,能够同时监听的 Socket 的数目也非常的多了,上限就为系统定义的进程打开的最大文件描述符个数。

epoll函数实战

epollcreate(),epollctl() ,epoll_wait();

总结:

ngxgetconnection()重要函数;从连接池找空闲连接

ngxepollinit()调用;在子进程执行;

ngxepolladd_event();

连接池:

首先连接池维护两个列表一个是空闲连接列表,一个是全部的(无论是否空闲都放在这里),一开始这两个列表是一样大的,大小都为最大连接数;首先我们监听套接字在监听,epoll将监听事件添加,当有新的连接时,此时连接池的空闲连接(未被使用的对象)分配出来,getconn(cfd),将这个连接绑定到这个对象中,并且将这个对象从队列里取出;

LT | ET

  • 使用边缘触发模式时,当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,因此我们程序要保证一次性将内核缓冲区的数据读取完;

  • 使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取;

事件驱动框架:和nginx框架差不多

就是有一些事件发生源(三次握手内核通知,事件发生源就是客户端),通过事件收集器收集和分发事件

事件收集器(epoll_wait())产生事件

分发事件:就是调用函数

事件处理器(waitrequesthandler(),event_accept())用来消费事件都不为阻塞函数;

腾讯后台开发的面试题?

使用linux epoll模型的水平触发模式时,当socket可写时,也就是写缓冲区没满时,那么会不停的触发socket可写事件通知你,如何处理?

1.要写的时候再把这个socket加入到epoll事件中去,等待可写事件,接到可写事件时调用write或者send发送数据,当所有数据都写完时,将socket移除

  缺点:即使发送数据很小,那也要添加上去,写完还要移除epoll,有一定的操作代价

2.

一种改进的万式:

开始不把socket加入epoll,需要向socket写数据的时候,直接调用write或者send发送数据。如果返回EAGAIN,把socket加入epoll,在epoll的驱动下写数据,全部数据发送完毕后,再移出epoll。这种方式的优点是:数据不多的时候可以避免epo11的事件处理,提高效率。

直接写然后如果能写就写了,不能写代表满了,出现错误之后,在进行epoll检测是否没满(类似于乐观锁)

对于非阻塞的套接字:如果缓冲区没有数据,他会返回-1 errno==EAGAIN,如果返回这个,那么说明数据已经读完或者接收完;

ET LT触发的事件来临,有事件来了通知不一样

LT :模式下,只要读缓冲区有数据,那么双向链表一定会把这个事件在加进来,没读完就得反复加进来(只要正常读完数据效率也会加大)

ET:模式下,只要有新事件,从不可读到可读事件来,操作系统就会把这个可读事件加到链表里,然后通知你一次,不关你读没读,这个事件就被链表删除了,想要在读,只能再触发。

如何选择ET LT :

如果收发数据包有固定格式,采取LT模式,简单清晰,写好效率也很高;

准备LT 这种方法【采用固定格式的数据收发方式来写这个项目】

如果收发数据包没有固定格式,可以考虑采用ET格式;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值