Nginx基础教程(34)Nginx HTTP框架综述之源码分析:Nginx源码探秘:HTTP框架设计精髓揭秘

你以为Nginx只是简单的网页服务器?它的内部架构藏着让你惊叹的智慧!

1. 引言:万能的网络小助手

Nginx(发音为“engine-x”)是一个开源的高性能HTTP服务器和反向代理服务器。它由Igor Sysoev开发,起初是为了解决C10K问题,即如何在单个服务器上同时处理超过1万个并发连接。

想象一下,Nginx就像是网络世界的空中交通管制员,专注于为数以千计的网络请求快速、高效地引导到正确的目的地。

那么,它是如何做到如此高效的呢?答案就藏在它的源码和架构设计中。

2. Nginx基础入门:初识神秘面纱

2.1 安装与启动

在Windows上安装Nginx,只需下载预编译的二进制包,解压后运行start nginx命令即可。Linux上则更简单,使用包管理器如sudo apt-get install nginx就能完成安装。

启动Nginx后,访问http://localhost,看到“Welcome to nginx!”页面,恭喜,你的Nginx小助手已上线!

2.2 核心配置入门

Nginx的核心配置文件是nginx.conf,通常位于/etc/nginx/(Linux)或C:\nginx\conf\(Windows)。

一个简单的静态Web服务器配置看起来是这样的:

http {
    server {
        listen       80;
        server_name  localhost;

        location / {
            root   /var/www/mysite;
            index  index.html index.htm;
        }
    }
}

配置完成后,运行sudo systemctl reload nginx(Linux)或nginx -s reload(Windows)重载配置,你的静态网站就准备好了。

3. Nginx架构设计:高性能的秘诀

3.1 模块化设计:高度模块化的设计是Nginx的架构基础

Nginx服务器被分解为多个模块,每个模块就是一个功能模块,只负责自身的功能,模块之间严格遵循“高内聚,低耦合”的原则。

这些模块可以分为:

  • 核心模块:提供错误日志记录、配置文件解析、事件驱动机制、进程管理等核心功能。
  • 标准HTTP模块:提供HTTP协议解析相关的功能,如端口配置、网页编码设置、HTTP响应头设置等。
  • 可选HTTP模块:主要用于扩展标准的HTTP功能,让Nginx能处理一些特殊的服务,如Flash多媒体传输、解析GeoIP请求、SSL支持等。
  • 邮件服务模块:支持Nginx的邮件服务,包括对POP3协议、IMAP协议和SMTP协议的支持。
  • 第三方模块:为了扩展Nginx服务器应用,完成开发者自定义功能。

3.2 多进程与异步非阻塞

Nginx结合多进程机制异步机制,异步机制使用的是异步非阻塞方式。

每个工作进程使用异步非阻塞方式,可以处理多个客户端请求。当某个工作进程接收到客户端的请求以后,调用IO进行处理,如果不能立即得到结果,就去处理其他的请求(即为非阻塞)。

当IO返回时,就会通知此工作进程;该进程得到通知,暂时挂起当前处理的事务去响应客户端请求。

3.3 事件驱动模型

Nginx的事件驱动模型由事件收集器事件发送器事件处理器三部分基本单元组成。

  • 事件收集器:负责收集worker进程的各种IO请求。
  • 事件发送器:负责将IO事件发送到事件处理器。
  • 事件处理器:负责各种事件的响应工作。

事件发送器将每个请求放入一个待处理事件的列表,使用非阻塞I/O方式调用“事件处理器”来处理该请求。其处理方式称为“多路IO复用方法”,常见的包括select模型、poll模型、epoll模型。

4. HTTP框架核心:源码深入解析

4.1 HTTP模块的数据结构

定义HTTP模块方式很简单,例如:

ngx_module_t ngx_http_mytest_module;

其中,ngx_module_t是一个Nginx模块的数据结构。我们来分析一下这个结构中关键的成员:

struct ngx_module_s {
    ngx_uint_t            ctx_index;
    ngx_uint_t            index;
    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;
    // 初始化函数等...
};
  • ctx_index:对于一类模块而言,表示当前模块在这类模块中的序号。
  • index:表示当前模块在ngx_modules数组中的序号。
  • ctx:指向一类模块的上下文结构体。
  • commands:将处理nginx.conf中的配置项。
  • type:表示该模块的类型。

定义一个HTTP模块时,务必把type字段设为NGX_HTTP_MODULE

4.2 HTTP模块的上下文

对于HTTP类型的模块来说,ngx_module_t中的ctx指针必须指向ngx_http_module_t接口。这个结构体定义了8个回调方法:


typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);
    void       *(*create_main_conf)(ngx_conf_t *cf);
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);
    void       *(*create_srv_conf)(ngx_conf_t *cf);
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
    void       *(*create_loc_conf)(ngx_conf_t *cf);
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

HTTP框架在读取、重载配置文件时定义了由ngx_http_module_t接口描述的8个阶段,会在每个阶段中调用相应的方法。

4.3 配置命令解析

commands数组用于定义模块的配置文件参数,每一个数组元素都是ngx_command_t类型,数组的结尾用ngx_null_command表示。

每个ngx_command_t结构体定义了自己感兴趣的一个配置项:

struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};
  • name:配置项名称,如“gzip”。
  • type:指定配置项可以出现的位置和携带的参数个数。
  • set:出现配置项后,调用set方法处理配置项的参数。

5. HTTP请求处理的11个阶段

在Nginx中,各HTTP模块是以挂载的形式串接而成,以流水线工作模式进行HTTP请求的处理。nginx将一个HTTP请求的处理划分为11个阶段

typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0,
    NGX_HTTP_SERVER_REWRITE_PHASE,
    NGX_HTTP_FIND_CONFIG_PHASE,
    NGX_HTTP_REWRITE_PHASE,
    NGX_HTTP_POST_REWRITE_PHASE,
    NGX_HTTP_PREACCESS_PHASE,
    NGX_HTTP_ACCESS_PHASE,
    NGX_HTTP_POST_ACCESS_PHASE,
    NGX_HTTP_PRECONTENT_PHASE,
    NGX_HTTP_CONTENT_PHASE,
    NGX_HTTP_LOG_PHASE
} ngx_http_phases;

每阶段(部分阶段保留,不允许挂载)允许多个模块挂载,一个模块也可以挂载到多个阶段。因此,初次完成挂载的存储结构是一个二维数组的形式,不过在初始化过程中,ngx_http_init_phase_handlers函数将该二维数组转换成了一维数组。

这11个阶段就像是一条流水线上的11个工作站,每个HTTP请求都会依次经过这些工作站,每个工作站都有机会对请求进行加工处理。

6. 完整示例:编写自定义Nginx模块

理论说了这么多,现在让我们动手实践,创建一个简单的“Hello World”Nginx模块。

6.1 创建模块文件

首先,我们需要创建模块文件夹和文件。模块主文件(例如ngx_http_hello_world_module.c)内容如下:

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

static char *ngx_http_hello_world(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_hello_world_handler(ngx_http_request_t *r);

static u_char ngx_hello_world[] = "hello world";

static ngx_command_t ngx_http_hello_world_commands[] = {
    { 
        ngx_string("hello_world"),
        NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
        ngx_http_hello_world,
        0,
        0,
        NULL 
    },
    ngx_null_command
};

static ngx_http_module_t ngx_http_hello_world_module_ctx = {
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
};

ngx_module_t ngx_http_hello_world_module = {
    NGX_MODULE_V1,
    &ngx_http_hello_world_module_ctx,
    ngx_http_hello_world_commands,
    NGX_HTTP_MODULE,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NGX_MODULE_V1_PADDING
};

static char *ngx_http_hello_world(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_hello_world_handler;

    return NGX_CONF_OK;
}

static ngx_int_t ngx_http_hello_world_handler(ngx_http_request_t *r)
{
    ngx_http_complex_value_t cv;

    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
        return NGX_HTTP_NOT_ALLOWED;
    }

    ngx_http_discard_request_body(r);
    
    ngx_str_t val = ngx_string("text/plain");
    r->headers_out.content_type = val;
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = sizeof(ngx_hello_world) - 1;

    ngx_http_send_header(r);

    ngx_buf_t *b = ngx_create_temp_buf(r->pool, sizeof(ngx_hello_world) - 1);
    b->last = ngx_cpymem(b->pos, ngx_hello_world, sizeof(ngx_hello_world) - 1);
    b->last_buf = 1;

    ngx_chain_t out;
    out.buf = b;
    out.next = NULL;

    return ngx_http_output_filter(r, &out);
}

6.2 模块代码分析

让我们分析一下这个helloworld模块的关键部分:

  • ngx_command_t函数用于定义包含模块指令的静态数组ngx_http_hello_world_commands
  • static u_char ngx_hello_world[] = "hello world"是输出到屏幕的字符串。
  • ngx_http_module_t用来定义结构体ngx_http_hello_world_module_ctx
  • ngx_module_t定义结构体ngx_http_hello_world_module
  • 处理函数ngx_http_hello_world_handler是hello world模块的核心部分。

6.3 编译与配置

下载nginx源码包,与helloworld模块一起编译nginx:

./configure --add-module=/path/to/hello_world/module
make
make install

然后在nginx.conf中配置:

location /hello {
    hello_world;
}

启动nginx,访问http://localhost/hello,就可以看到编写的helloworld模块输出的文字了。

7. Nginx启动过程解析

7.1 主进程启动

nginx主进程启动后,进行一系列的初始化,包括命令行参数解析、时间初始化、日志初始化、SSL初始化、操作系统相关初始化、一致性hash表初始化、模块编号处理等。

7.2 核心初始化

最重要的初始化由ngx_init_cycle()函数完成,该函数围绕nginx中非常核心的一个全局数据结构ngx_cycle_t展开。该函数完成了几个核心初始化:

  • 配置文件解析
  • 创建并监听socket
  • 初始化nginx各模块

7.3 进程模型

主程序的最后,根据是否启用多进程模型,分别进入多进程版本的ngx_master_process_cycle和单进程版本的ngx_single_process_cycle。以常见的多进程版本为例,进入该函数后,首先设置进程名称为:"master process",随后启动各工作子进程。

经过几层封装,最终通过fork启动多个子进程。除了工作子进程,还启动了缓存管理进程。之后主进程进入工作循环,周期性更新时间并检查各全局标记,根据不同情况给子进程发送不同信号。

7.4 子进程工作循环

子进程启动后,进入ngx_worker_process_cycle,进行工作进程的初始化,随后修改进程名称为:"worker process"。接着进入工作循环函数ngx_process_events_and_timers,在该函数中主要负责:

  • 竞争互斥锁,拿到锁的进程才能执行accept接受新的连接,以此在多进程之间解决惊群效应
  • 通过epoll异步IO模型处理网络IO事件,包括新的连接事件和已建立连接发生的读写事件
  • 处理定时器队列中到期的定时器事件,定时器通过红黑树的方式存储

8. HTTP请求处理流程

8.1 通用处理流程

Web服务器的HTTP请求处理流程可以粗略地叙述为:

  1. 监听端口,接受客户端的连接
  2. 读取请求头,包括请求行(request line)和请求头(headers)
  3. 读取或者丢弃请求体(body)
  4. 生成并发送响应头
  5. 生成并发送响应体

8.2 Nginx的处理流程

如果我们忽略Nginx的异步机制,把注意力集中在处理逻辑上,那么Nginx的工作流程如下:

  1. 监听端口,设置回调为ngx_http_init_connection()
  2. 接受客户端的连接,调用ngx_http_wait_request_handler()
  3. 调用ngx_http_create_request()创建请求对象
  4. 接收数据,调用ngx_http_process_request_line()解析请求行
  5. 请求行接收完毕,继续接收数据,调用ngx_http_process_request_headers()解析请求头
  6. 请求头接收完毕,调用ngx_http_process_request()设置异步读写事件
  7. 调用ngx_http_handler()开始真正处理请求
  8. 调用ngx_http_core_run_phases()按阶段处理请求
  9. 调用ngx_http_send_header()发送响应头
  10. 调用ngx_http_output_filter()发送响应体
  11. 处理完毕,记录访问日志

当连接有数据产生时,工作线程读取socket中到来的数据,并根据HTTP协议格式进行解析,最终封装成ngx_request_t请求对象,提交处理。

9. 总结与学习建议

通过本文,我们对Nginx HTTP框架的整体架构有了一个全面的认识,包括其模块化的设计、多进程和异步非阻塞的请求处理方式、事件驱动模型等。

Nginx不仅是一款优秀的高性能web服务器,对于C/C++技术栈的同学来说,还是一个很好的学习对象,其良好的架构设计,优美的代码风格和经典的编程技法无一不值得细细品来。

想要深入学习Nginx,阅读源码是非常重要的一环。以下是一些建议,可以帮助你更深入地学习Nginx:

  • 阅读官方文档:Nginx的官方文档是学习的宝库。详细阅读文档,可以让你更深入地了解Nginx的功能和配置。
  • 实践项目:将Nginx应用到实际项目中,可以帮助你在实践中巩固知识。
  • 参加社区讨论:加入Nginx社区,与其他开发者交流,可以让你了解到更多的经验和技巧。
  • 阅读源代码:如果你对Nginx的内部实现感兴趣,阅读源代码是最直接的方法。

希望通过这篇文章,能帮助你开启Nginx代码分析的学习之旅,并在成为Nginx大师的道路上越走越远!


以上内容仅供参考,具体细节以实际应用和官方文档为准。欢迎交流指正!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值