自己写一个网络库的心得

总是在看网络相关的代码,还没有完整的实现过一个网络库。南宋大诗人陆游有云,纸上得来终觉浅,绝知此事要躬行。哈哈这也是我们高中的校训之一。这两周抽了点时间实现了一个小型的网络库,深度参考了muduo,只不过我是用C语言实现的,名字就叫muevent。

 

muduo是用C++的,我为什么要用C语言呢,而且我曾经用C++的时间要远多于C。因为C语言更加接近底层实现,C++干了太多不为人知的事,毕竟只是为了学习,要清楚的知道每行代码的作用还是很重要的。

 

总体说来,muevent的构成和muduo差不多,只不过muduo分的更加细,像每一个连接里面包含一个channel对象,这样便于扩展和维护。而我只是想学习muduo的设计思路,为了保持整体代码结构清晰,所以并没有拆分的很细。而且在多线程的处理上,我的muevent更加简洁。

 

本来muduo的思想就是one loop per thread,为了提高程序处理的效率我们通常会用到多线程,尤其是在网络开发这种多并发的程序中。但是一路学习下来,发现muduo的多线程处理还是有点过于复杂,虽然他这么做显得很严谨,但是对新手相当不友好。

 

首先主线程要等待工作线程中event loop完成,这个要做好同步工作。muduo中用了很严谨但是很不直观的thread pool,线程初始化等工作就费好多功夫,不信你去看看。

其次,工作线程在创建成功之后就开始等待网络事件,他是阻塞的。所以当有新的连接时,要通过特殊的方式来唤醒已阻塞的工作线程。muduo的做法是,如果发现线程函数不是在自己的线程中执行就wakeup,往一个特性的fd中写入数据,从而唤醒epoll_wait。为什么会不在自己的线程中执行线程函数?主线程一般是用作处理连接请求,而这个新的连接之后的数据处理是要分配到新的线程中的(如果开启了多线程)。在这里主线程就会调用到了工作线程的处理逻辑。除了这里,目前还没有仔细看看有没有其他地方会跨线程调用。

 

由于跨线程的处理非常麻烦,不仅逻辑看起来复杂,而且很容易出错,代码也不直观。为了规避这个问题,我发现其实不用费那么多功夫。每个工作线程处理自己的网络数据逻辑,上面说了通知工作线程有了新的连接会跨线程处理。如果只让epoll_wait新监听的一个新fd,没有必要在工作线程中去操作,直接在主线程中调用即可。我们只需要知道工作线程的epoll fd,这个是每个工作线程有的属性,因此也不会有竞争的问题。

 

再说一下缓冲区的处理。陈硕大神已告诉我们,非阻塞io必须使用缓冲区。一个好的缓冲区设计能够是程序的性能大大提高。刚开始的时候我使用了skynet中处理网络数据的缓冲区设计,buffer lis,参考这篇文章:skynet socket.lua 读写缓冲区剖析。由于要正确的处理数据的释放问题,我发现这个设计中有大量的数据拷贝。而这个正是影响性能的一个瓶颈。后来我换了环形缓冲区的设计思路,减少数据的拷贝,发现吞吐量大大提高,超过了muduo。以下是数据对比图:

 

 

最后说一下定时器的处理。定时器实现的思路很简单,利用epoll_wait的超时时间来处理。在时间事件的处理上,没有用到一般的数据结构--大顶堆,而是利用了很简单的数组,两个数组。增加定时器时只会在其中一个数组的后面添加,用一个索引来保存最小数据的位置。删除定时器时把剩余的数据倒腾到另一个数组中并重新计算最小数据的索引。利用两个数组其实是受了redis中hash表数据结构的启发。不知道这种算法对比大顶堆的效率怎么样,以后可以测试一下。

 

由于时间有限,还有很多网络程序中比较常见的问题还没有去完善,比如如何优雅的让socket退出等等。这些有时间了再好好完善一下。

 

代码在git上,赏个星星

https://github.com/shonm520/mu_event

 

欢迎加入QQ群 858791125 讨论skynet,游戏后台开发,lua脚本语言等问题。

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值