libevent、libev和libuv都是c语言实现的异步事件库。注册异步事件,检测异步事件,根据事件的触发先后顺序调用相对应的函数处理事件。 处理的时间包括:**网络IO事件,定时事件以及信号事件。**这三个是驱动服务器逻辑的三个重要事件。 libevent和libev解决了跨平台的问题,封装了异步事件库与操作系统的交互。 libevent使用了大量的全局变量,很难安全得在多线程环境中运行;event的数据结构太大,包含了io、时间以及信号 处理全封装在一个结构体中,额外的组件如http、dns、openssl等实现质量差,计时器采用最小二叉堆(libuv改为最小四叉堆)不能很好的处理时间事件。
记住两个线索:
- IO检测
- IO操作
- 连接建立的问题 (限制最大连接数,设置黑白名单,创建用户的对象)
- 连接断开
- 数据到达 (具体消息的分发,解密)
- 数据发送
不注重效率的都会使用getdateoftime(),会对我们的系统有较大的考验,因为它会读一个文件,把时间取出来。如果我们要经常获取时间,我可以运用缓存时间。 systime_mono() 机器启动到现在的时间。 systime_wall() 现在的时间。 wall+mono2-mono1为什么要这样计算时间?担心有的人会更好系统时间,导致我们服务器出现错乱。 更新时间缓存的目的是避免过多的调用系统调用,影响性能。 最小堆的顶端是最近要触发的任务。 收集网络事件,收集定时事件,将事件进行包装,根据事件优先级不同放入不同队列中,并没有急着处理。 event_asign()相当于epoll_mod,是个增强版,没有还会增加。
linux协议栈中每一个fd在内核态中都有一个读写缓冲区,而在内核态也同样有一个读写缓冲区,为什么用户态需要实现一个对写缓冲区? 能不能确保一次int n=read(fd,buf,size)读出内核态所有数据?答案肯定是不能,因为数据包的界定是有固定长度和特殊字符两种,size参数的填写是盲猜的。有了用户态缓冲区就更容易读出完整的数据。 而写的时候int n=write(fd,buf,size),size是打算写的值,而n表示实际写入的值,如果n<size说明数据没有全部写出去,剩下的数据要放到缓冲区。我们要缓存数据,然后注册写事件。 常用的解决方案:
-
fix buffer char buf[1610241024] char buf[1610241024] 容纳的最大值,还要进行错误判断,数据移动频繁,存在空间浪费。
-
ringbuf 多线程、网卡,逻辑上连续但是物理上上是数组不连续。
-
chainbuf 最大的缺点是内存不连续,会带来多次IO
用不同的回调函数对不同的事件进行解耦。
今天通过Mark老师细心的讲解,我又被深深的上了一课,脱离libevent这个框架之外的习惯我觉得更值得我去学习。Mark老师反复的强调,写代码之前要思路清晰,只有思路清晰代码写起来才能更加流畅。将事件进行归类,读起代码来也会变得得心应手。虽然小生目前依然才疏学浅,相信在不远的将来一定会成为栋梁之才。
原文作者:屯门山鸡叫我小鸡