前言
nginx简称ngx, ngx是我第一个剖析的开源代码, 因此剖析的过程不仅是学习ngx中的优秀代码, 也是学习如何剖析源码.
源码剖析导读中, 主要记录了我遇到的困惑和做的探索. 主要困惑有:1.看源码前要做的事. 2.源码从哪里入手. 3.抽象和具体-C语言中的多态
一 看源码前做的事
1.1 把握源码实现的功能
代码的本质是解决问题, 把握住问题就能理解源码的脉络. ngx的主要功能(以我常用的功能举例)是http服务器的负载均衡代理. 那么基于这个功能, ngx至少需要实现接收客户端的http请求和模拟客户端向下游服务器发送请求. 因为ngx是用C语言实现的, 因此ngx必然需要实现基于socket的TCP消息收发, 解析TCP消息流成http协议. 基于以上需求分析, ngx重心在网络消息的收发和根据Http协议处理网络消息.
1.2 了解源码的主要模块
任何工程问题都能通过树结构的方式简化问题, 以ngx为例.
ngx的http协议转发功能可通过树结构, 将实现一步步细化.
对于复杂的工程问题, 将实现一步步细化后, 就变得简单了. 源码的本质是解决问题, 基于要解决的问题, 开发者必然需要把问题细化, 一个个解决. 因此把握住源码实现的功能后, 需要逐步了解ngx的模块, 知道ngx有哪些模块和这些模块是为了解决哪些问题. 下面分析ngx的三个主要模块.
a. 配置模块, 配置模块主要解决的问题配置加载和根据配置初始化进程.
b. 事件模块, 事件模块主要分网络事件和定时器事件, 网络事件主要是tcp消息的收发, 定时器事件则是一些需要定时处理的业务, 如和下游服务器http请求的超时处理等.
c. http模块, 最复杂的模块, 负责: 1.客户端http请求的处理和客户端响应的回复. 2.下游服务器的请求发送和回复的响应的处理.
1.3 了解源码的配置文件
结合源码实现的功能, 了解配置的含义, 同时根据配置可以推测源码的实现方式和一些实现细节. 尤其是ngx几乎是由配置驱动程序, 了解配置的含义后能很好的把握住源码的功能. 力推<<深入理解nginx>>, 很好的讲解了每个配置的作用, 其它模块讲的也非常不错.
1.4 简单的编译和修改
网上有很多ngx插入自定义模块的的方法. 非常建议阅读源码前试一试, 在后面阅读源码时, 再看看其实现, 会有非常多的收获. 其中要注意./configure操作中每个参数的含义. ./configur脚本执行过程中有生成源码的操作, 这也是nginx实现跨OS的方式之一, 不在代码中定义define而是根据操作系统生成对应的代码.
二 源码从哪里入手
2.1 数据结构
源码的风格不尽相同, 风格主要体现在命名, 代码文件配置, 封装实现等. 习惯源码的风格有助于后面的阅读, 同时重写数据结构部分也有助于加强自己的数据结构编写的能力. 我写的数据结构主要有红黑树和内存池, 在写数据结构的过程中, 猜测源码通常有自己重写常用数据结构和算法的习惯, 一来可能是没有对应的库, 二来主要是根据自己的需求优化数据结构的效率和内存.
红黑树和内存池的源码和解析在网上有很多, 想要理解的可自行Google. 另外从数据结构下手最主要的原因是数据结构是业务弱相关的, 通过阅读数据结构可以很快把握源码的风格. 顺便吐槽下ngx的内存池, ngx的内存池并没有考虑部分内存回收的再利用. 后面深入了解后估计才能理解为什么它要这么做.
2.2 配置文件
配置文件和业务的耦合性比数据结构高, 比具体的业务模块低, 在把握其源码风格后, 根据配置文件基本可以理解配置文件的解析方式. 因此强烈推荐看完数据结构部分源码后看配置文件解析部分的代码.
2.3 event模块
event模块是基于socket开发的, 如果有网络事件库经验, 可根据网络事件库的通用规则反推它的实现. 因为其使用的socket API已经比较熟悉, 所以这个模块也比较容易上手. 顺带提一下我心中的网络事件模型.
多线程下处理网络事件的流程: 网络线程不断收消息并将消息丢到事件队列, 主线程读取事件队列并调用对应的处理函数.
ngx是多进程单线程模型, 因此由主线程处理网络消息的接收并选择立即处理消息还是延后处理.
2.4 http模块
http模块异常复杂, 在看其源码之前建议先理解http协议, http模块就是基于http协议实现的模块.
三 抽象和具体-C语言中的多态
3.1 抽象和具体
有面向对象编程经验的都知道这几个概念, 抽象类, 接口. 其本质就是抽象, 即具有什么样的功能或结构, 具体则是实现该功能的具体方式. 举例, 排序是抽象接口, 而快速排序, 堆排序等则是具体的实现. C++中纯虚类是接口, 定义了一组方法和一些数据, 而继承纯虚类的类则是具体类, 具体定义了方法的实现和数据的使用. C语言中没有类, 但也有其实现抽象和具体即多态的方式.
3.2 C语言的多态
多态的目的是运行时确定, 可以根据需要修改某个功能的具体实现方式. 如网络模块, 在不同的操作系统中, 有不同的接口, 那么就需要在不同的操作系统中调用不同的网络模块. C语言是如何实现多态的呢? 首先多态主要分数据和接口, 需要动态绑定不同的数据和接口, 数据采用 void * 方式定义, 接口则通过定义函数指针. 举例
如图, vodi * ctx; 可以绑定不同的数据, init_master; 则可以根据需要绑定不同的初始化主进程函数. 在ngx中处处是这种实现多态的方式, 理解和习惯这种方式有助于快速阅读源码.
不得不感叹, 编程有很多语言无关的思想, 不要拘泥于某种语言, 更重要的是掌握编程思想. 当然深入了解一门语言也是必不可少的.
总结
这篇导读的目的不是为了讲清楚ngx的具体功能, 而是记录第一次源码剖析遇到的问题和解决方案. 其中最重要的感悟就是, 学会做自己的老师, 我知道自己没有源码阅读经验, 于是就从最边缘的数据结构入手, 然后再加深去了解配置模块, 然后再是有一定基础的网络事件模块. 层层递增, 不会一次跨越太多,保证自己能读下去, 读得懂.同时又兼顾了效率, 没有死磕.