首页 关于
树枝想去撕裂天空 / 却只戳了几个微小的窟窿 / 它透出天外的光亮 / 人们把它叫做月亮和星星

Linux下的事件与网络编程

我一直觉得,操作系统、编译器、网络服务器,这三个兄弟是构建信息世界的幕后推手。因为操作系统的存在,人们可以不再关心计算机的底层细节,让计算机得以普及。 因为编译技术的发展,人们设计了各种各样的程序语言,让计算机应用的开发变得简单。因为网络服务器的存在,人们有了信息交换的广场。

在学生时代,了解了一些皮毛之后,跃跃欲试,想要自己也造几个轮子。在逐渐深入了解之后,发现原理看起来很简单,但是造好一个轮子是真TMD难,有太多细节需要考虑了。 再加上“不要重复发明轮子”的毒鸡汤,很多想法都只是想想而已了。后来工作了,拿着一些真香的开源实现,用起来也很开心。但是偶尔掉进坑里,因为不了解原理,爬出来就比较费劲。 每当这个时候,我都在想要是当初能够再深入研究一下就好了。

最近因为项目的需要,要写一些套接字,我想趁这个机会好好捋一捋这些基础知识。TMD毒鸡汤不能喝多了,一个木匠不能顺手造个轮子,他还是一个好木匠吗?

本系列的文章专注在,Linux系统下多路IO的复用,以及在此基础上构建起来的事件机制和Reactor编程模式,顺便了解一下并发网络服务器的设计思想。 依照数据结构与算法的组织形式,本系列也将准备一个小土的网络工具箱, 用来写一些例程。工具箱中的代码将一直更新,文章更新速度比较慢,两者会有比较大差异。

第一部分:事件与回调

套接字与TCP通信 本文中,我们简单介绍了TCP协议,了解了TCP报文首部的字段定义,三次握手建立连接和四次握手断开连接的过程。 编写了一个echo服务器和客户端来体验使用Linux的套接字API编程,并简单通过wireshark进行抓包,解释整个TCP通信过程。 源码tag:v0.0.1
IO复用与并发服务器 上一节中实现的echo服务器是一个很初级的版本,它没有并发能力,同一时间只能响应一个连接。本文,我们初次讨论服务器的开发模型, 并通过IO复用来提供一个并发服务器。 源码tag:v0.0.2
reactor模式的echo服务器 reactor模式的精髓就是事件的回调机制,发生了什么事件,就执行什么回调。本文,我们对poll调用进行层层封装,最后提供一个reactor模式的echo服务器。 源码tag:v0.0.3
eventfd——唤醒PollLoop echo类的服务器提供的是一种被动的服务,所有的服务过程都可以在一个线程中顺序的完成。但如果我们需要服务器主动的发送数据时, 往往需要多个线程来提供服务。一个总是被阻塞在poll调用上,其他的完成计算,然后唤醒poll的线程。 源码tag:v0.0.4
timerfd与定时任务 定时任务,是网络编程当中经常需要处理的一类任务。timerfd提供了一种可以运行在poll调用下的计时器方法,可以纳入我们的PollLoop循环框架下。 源码tag:v0.0.5
时间轮盘与超时连接 对于网络应用而言,掉线是十分常见的一种故障。对于服务器而言,这种故障会造成资源浪费,所以当一个连接上长时间没有数据发送,我们应当主动将之关闭掉。 有了上节分析的timerfd的加持,本文我们采用一种称作时间轮盘的方式来检查连接是否超时,并主动关闭之。 源码tag:v0.0.5

第二部分:初看缓存

在后续的迭代和使用过程中,我们发现这一部分针对缓存所做的一些考虑有些不切实际,有很多过度优化的地方。这里的很多代码在后来的工作中都删除了, 但为了原本的记录下我们的整个学习和思考的过程,这部分的文章仍然保留下来了。

非阻塞IO通信 本系列的第一部分中所有的例程都是阻塞的IO通信。 实际上这种阻塞的IO并不利于充分发挥多路复用的性能,因为当进程阻塞在accept, read, write/send, connect等调用的时候,势必会影响到poll等多路复用的轮训, 进而延迟对于其它IO的响应。所以为了更快的响应速度,我们有必要研究一下非阻塞的IO通信。 源码tag:v0.0.6a
输出缓存与分包发送 上一节中, 我们曾简单提到非阻塞的发送实际上是把将要发送的数据丢给系统内核之后就直接返回了。内核分配的缓冲空间毕竟是有限的, 而发送大量数据也是应用程序的一种比较常见的需求,这就要求我们提供应用层的输出缓存对大数据包分批发送。 源码tag:v0.0.6b
输入缓存与解码分发 对于接收而言,我们就不能假设数据总是在一帧中就可以传送完毕。 而且socket的接口一般都会把通道上的数据看成一个流, 能保证字节顺序的一致,但不能保证接收的时刻。所以也需要一个输入的缓存来解决接收数据的时机和数量上不确定性的问题。 源码tag:v0.0.6c这个输入缓存有点过度优化了。
好吧,输出缓存与分包发送输入缓存与解码分发的数据结构选择循环队列 DataQueue 不是一件好事。 消费一个循环队列就必须要考虑内存折叠的情况,即 mEnd < mBegin 的情况。这增加了代码的复杂度,看似节省了内存,实际没啥作用。 在输入缓存中,我们为了尽可能多的从内核空间搬运数据,会不断增大缓存,而且一旦成功增大,就不会吐回去,除非释放了缓存队列。 这本身就是一个空间换次数的妥协,所以循环队列节省的那点内存是微不足道的。 我们还是改用一维的数组比较好, 这里先用数据结构与算法中提到的 DataArray 替换输入缓存源码tag:v0.0.6

第三部分:HTTP服务器

迟到的TCP服务器系统框图 我们已经有了一个可以冒烟跑着的TCP服务器框架了,接下来准备进行重构。本文中我们主要是回顾一下之前的文章, 画画系统框图。同时我们把编译系统换成了 cmake,并详细解释了 CMakeLists.txt。 源码tag:v0.0.7a
初看HTTP协议 本文中,我们介绍了 HTTP 协议及其与TCP的关系,讨论了 HTTP 报文的格式和5种常用的请求方法, 并通过一个简单的例程u_say_hello_to_browser介绍了从浏览器键入网址到显示出网页的过程。 源码tag:v0.0.7a
HTTP服务器初步设计和实现 本文中,我们初步设计了一个HttpServer,并讨论了它的构造过程,以及关于新建连接、新消息、关闭连接三个事件的回调函数流程。 目前的设计和实现中,把 Http 会话和 Tcp 连接两个概念耦合在一起了,我们将在后续的重构过程中逐渐解耦。 源码tag:v0.0.7b
解析HTTP请求报文 在本文中,针对接收消息事件,我们详细介绍一种解析HTTP请求报文的方法。 源码tag:v0.0.7b
响应HTTP请求的任务队列 在本文中,我们把处理请求报文的任务拆分成了解析请求报文、填充响应报文、发送响应报文三个阶段, 初步实现了一个任务队列专门用来填充响应报文。由一个线程驱动 PollLoop 循环生成处理任务放入对尾,同时用另一个线程执行处理任务。 源码tag:v0.0.7

第四部分:HTTP进阶

epoll——IO事件通知机制 目前为止,我们的所有例程都是建立在PollLoop上的,每次都需要遍历所有连接才可以确认实际接收到消息的那个。当连接数量很大的时候, 这将是一个低效的方法。在本文中我们将探讨这一问题的解决方案——epoll。 源码tag:v0.0.8
Http发送大文件 本文中为了提高大文件的发送效率,我们引入了分段读取文件的机制。这只是一个临时的补丁,用来初窥一下服务响应的分阶段处理机制。 源码tag:v0.0.9a
模块化的 HTTP 服务器 在本文中,我们借鉴 nginx 的设计,结合代码现状,对 HTTP 服务器进行了模块化处理。以流水线的方式,让报文在不同的模块之间流转,并最终生成响应报文。 源码tag:v0.0.9b
通过配置文件构建 HTTP 服务器

第五部分:其它

初看线程安全 简介。
HTTPS服务器 简介。
WebSocket服务器 简介。

地形第十

孙子曰:地形有通者,有挂者,有支者,有隘者,有险者,有远者。我可以往,彼可以来,曰通。通形者,先居高阳,利粮道,以战则利。可以往,难以返,曰挂。 挂形者,敌无备,出而胜之,敌若有备,出而不胜,难以返,不利。我出而不利,彼出而不利,曰支。支形者,敌虽利我,我无出也,引而去之,令敌半出而击之利。 隘形者,我先居之,必盈之以待敌,若敌先居之,盈而勿从,不盈而从之。险形者,我先居之,必居高阳以待敌,若敌先居之,引而去之,勿从也。 远形者,势均难以挑战,战而不利。凡此六者,地之道也,将之至任,不可不察也。

凡兵有走者,有驰者,有陷者,有崩者,有乱者,有北者。凡此六者,非天地之灾,将之过也。夫势均,以一击十,曰走; 卒强吏弱,曰驰;吏强卒弱,曰陷;大吏怒而不服,遇敌怼而自战,将不知其能,曰崩;将弱不严,教道不明,吏卒无常,陈兵纵横,曰乱; 将不能料敌,以少合众,以弱击强,兵无选锋,曰北。凡此六者,败之道也,将之至任,不可不察也。

夫地形者,兵之助也。料敌制胜,计险阨远近,上将之道也。知此而用战者必胜,不知此用而战者必败。故战道必胜,主曰无战,必战可也; 战道不胜,主曰必战,无战可也。故进不求名,退不避罪,唯民是保,而利于主,国之宝也。

视卒如婴儿,故可与之赴深谿;视卒如爱子,故可与之俱死。厚而不能使,爱而不能令,乱而不能治,譬如骄子,不可用也。

知吾卒之可以击,而不知敌之不可击,胜之半也;知敌之可击,而不知吾卒之不可击,胜之半也;知敌之可击,知吾卒之可以击,而不知地形之不可以战,胜之半也。 故知兵者,动而不迷,举而不穷。故曰:知彼知己,胜乃不殆;知天知地,胜乃可全。




Copyright @ 高乙超. All Rights Reserved. 京ICP备16033081号-1