本文将结合曾经实现过的一个网络模块,来聊聊网络编程相关的内容。
服务器模型
为了能较好地应对高并发,采用了事件驱动模型,整体结构如下图:
整个网络模块基于epoll的事件机制进行设计,尽可能地减少每次调用的阻塞时间,以达到高并发的目的。其中业务回调,利用线程池,封装成了一个异步操作,避免业务回调的阻塞影响事件分发。
事件分发
为了减少网络模块中的阻塞操作,主要做了以下一些工作:
1、所有IO操作都是非阻塞调用,每个读取操作在读到数据或者出错后,立即返回;
2、在整个消息体读取完成后,再回调业务。比如,一个请求的消息体为200字节,若每次在可读事件到达时,就立即回调业务代码,则必须等到完整地读完整个消息体才能进行回调,这个过程可能会造成读等待。因为,在每个可读事件到达时,并不一定能完整地读到200个字节;
3、对于需要等待外部调用的操作,通过eventfd或者pipe,转换为事件触发;比如业务回调时间较长,采用的方式是,在epoll中注册一个eventfd,待业务回调完成后,往eventfd中写入执行结果,便可以触发读事件;
4、对于阻塞时间较长的操作,将分成多次小粒度的操作。
socket相关
由于网络编程涉及的内容比较多且复杂,比如,网络不稳定,客户端异常、系统参数等。因此,对异常情况地正确处理,是网络框架本身稳定性的重要保障。
针对网络方面的异常,一般参考下面这张图:
所谓网络异常,就是假设图中,每一次交互都有可能不可达;比如:
read和write在非阻塞情况下,可能会存在读写中断;
客户端关闭连接时,服务端不一定都能收到;
在异步模式下,可能在数据返回前,提前关闭一个连接;
主动关闭连接时,造成大量TIME_WAIT;
当然,有部分情况,可以通过设置合适的参数,交给TCP去处理。但是,也有些是需要应用程序去处理的。
系统参数
常用到的系统参数主要有:
fs.file-max:打开的最大文件数,在linux环境下,一个socket连接将占用一个文件,因此,该参数直接影响并发连接数;
总结
本文主要介绍网络模块中,与网络相关的部分。除此之外,一个网络模块还应当考虑消息体定义、线程池、内存管理 、消息队列等问题。所述内容也仅为个人经验,不妥之处也在所难免。文中所提网络模块代码暂未整理完成,预计将会在下周附上。最后,推荐几本网络编程方面的经典书籍:《UNIX网络编程》、《TCP/IP详解》。
下一篇将聊聊分布式一致性协议---Raft协议
领取专属 10元无门槛券
私享最新 技术干货