前言:
在阅读muduo网络库源码之前,自己先写尝试写了一个HTTP服务器,写的时候尽量使用了最新的C++11和我所知道的比较好的网络模型,并且尝试将各种资源或者网络事件进行解藕,努力做到代码的复用,当时写完的时候,觉得自己写的真的挺不错了,挺得意自己的水平真不错。等我读完了muduo的源码,才知道自己写的东西那叫一个粗糙。
我的github:
所有的leetcode题目已经刷完了,详细记述了刷题时的思路与遇到的问题。现在更新到350题左右
/YinWenAtBIT
差异对比:
1.1线程模型:
我写的代码同样是基于多线程的,不过每个线程所完成的工作不同。
muduo库的模型是one loop per thread,每个线程都有一个事件循环,其中,主线程里只对listenfd进行监听,对收到的客户链接建立TCPconnection类与Channel,
成功连接的客户将被按顺序分配给其他线程的loop,其他线程进行监听客户发来的消息,并进行处理。
我的做法是主线程中有一个loop,同时监听listenfd 与客户已连接的fd,监听到可读事件后,判断fd是否为listenfd,
如果是listenfd,将得到新的客户的fd加到epollfd中进行监听。如果是客户的已连接fd,则在堆中开辟内存,为客户fd新建立一个Channel并且注册回调函数,
然后通过锁的方式将Channel加入Blockingqueue,其他线程的工作为取出Blockingqueue中的工作Channel,并执行其回调函数。
模型缺点:
1. 将listenfd和客户以连接fd放在一起,如果遭遇到海量客户同时连接,可能会导致listenQ对列满,来不及accept客户,最终导致客户无法连接的问题
2. 所有的线程都在一个Blockingqueue上进行数据交换,使用加锁的方式同步,有大概率在此处成为性能瓶颈。
3. 每次用户发来一个新的请求,就会建立一个新的Channel,反复的内存开辟与释放,对于多次数据交换不利,并且只能实现幂等操作,或者需要使用一个数据结构保存用户的状态。
1.2资源管理:
成熟的C++代码一定有非常好的资源管理方式,在这一点上我的代码基本上是没有做对。不出现异常的情况下没关系,一旦出现异常,必然回导致资源的泄露。
1. 线程资源的管理,muduo库中对每个线程都建立了一个类的成员来管理该线程,线程的id,运行状态,线程的名字,等等,各种相关的信息可以保存在此,在运行时非常方便查看线程检查自己的状态。
我本考虑过使用一个线程类来管理,不过因为不知道有什么资源需要管理,因此直接在一个for循环中进行pthread_create,然后detach自己,不过考虑到我使用的子线程仅仅调用回调函数完成客户请求,其实也是可以运行的。
2. 对互斥器以及条件变量的管理:
因为muduo库中有不少竞争条件出现,广泛的使用了mutex和condition,muduo库中将其封装成类,然后使用guard进行加锁与解锁,避免了初始化与销毁的麻烦,也避免了忘记解锁导致死锁的问题。
我的实现中还是最原始的方式,对锁的管理需要在包含锁的类中手动初始化与销毁。
3. shared_ptr的使用,使用智能指针可以在一定成程度上避免出现内存错误,在代码中应该尽量避免delete操作。也可以减轻编码的负担,特便是遇上需要共享的对象的时候,什么时候销毁非常头疼。
1.3回调函数:
我编写的HTTP服务器中,将TCPServer与HTTP服务的编写是分开的,在HTTP类中编写了一个协议函数,将协议函数注册给TCPServer类即可。整个网络模型中只有这一个回调函数,对于编程来说变得简单了,
但是对于各种情况的处理就没有那么的灵活了,这点上我觉得打平吧,或者等我代码能力更强的时候,会觉得回调函数更好。
1.4系统与网络IO:
使用的IO模型都是基于事件驱动的非阻塞IO,但是具体的实现还是有差别。我的HTTP服务器中没有系统IO,如果有的话于是添加到fastcgi程序中,这个不谈,具体到网络IO,首先事件可读的时候都是循环读,直到EOF,
我使用的是rio库函数,可以看作是线程安全的readline函数。读取上区别不太大。但是在写入上有非常大的区别。我的网络程序在事件处理完之后,回写的IO遇到没有空间可写,还是不断的尝试写入。这样就导致大量发送的时候,
有可能会在此停下,因为不写完是不会推出的。
而muduo库中,是对每个TCPconnnection中设置了一个读写的buffer,如果暂时没有空间写入,那么就将数据写入写的buffer,写的buffer是一个无锁循环的队列,只要有空间就可以写入。
将数据放入buffer中后,在epoll中注册写事件,当可写之后,再回调写即可。这一点上我的实现完全不行。
1.5解耦:
在解耦这一方面,无疑是muduo框架更为实用,毕竟本来设计就是为了支持多种网络协议。我在编写http服务器的时候,尝试去解耦,通过模板类的方式去实现TCPServer,但是这样一来,对于协议的回调函数回收到限制。虽然看起来编写服务器函数变得简单了。
总结:
在真正的阅读源码之前,还总是以为自己实力很强,觉得自己第一次尝试写一个http服务器的时候,还实现了这么多的网络编程类和特性,并且做了解耦与复用。所以说人外有人天外有天。需要不断的去学习加强自己的实力。