https://cloud.tencent.com/developer/article/1859856
简介
nginx作为一个web服务器,肯定是有listen套接字对外提供服务的,listen套接字是用于接收HTTP请求。
nginx监听套接字的创建是根据配置文件的内容来创建的,在nginx.conf文件中有多少个地址就需要创建多少个监听套接字。
本文不针对源码逐一注解分析,只是说明套接字创建监听流程。
流程
当客户端发来http请求与服务端创建一个连接,过程如下:
1.nginx首先在main函数中调用了ngx_init_cycle()函数,在这个函数的最后调用了ngx_open_listening_sockets函数,这个函数负责将创建的监听套接字进行套接字选项的设置(比如非阻塞、接受发送的缓冲区、绑定、监听处理)。
2.nginx创建套接字是在哪里呢?在解析http{}配置的时候,也就是在ngx_http_block()函数内,在这个函数的最后调用ngx_http_optimize_servers()函数。
在这个函数内最后调用了ngx_http_init_listening()函数,这个函数调用了ngx_http_add_listening函数,在这个函数总调用了ngx_create_listening()函数。
这个函数根据每一个IP地址:port这种配置创建一个监听套接字,这个函数还有一个很重要的任务,就是将监听套接字的回调函数设置为ngx_http_init_connection函数,记住这是监听套接字上的回调,而不是监听套接字对应的可读事件的回调函数。
3.nginx什么时候接受客户端http请求建立的连接呢?在ngx_event_process_init()函数内,这个函数是作为ngx_event_core_module模块创建的init_process函数。
这个函数是在worker进程初始化是被被调用的,ngx_event_process_init函数将每个监听套接字和一个连接(ngx_connection_t)相互创建关系。
在cycle内创建一个连接池,创建一个读事件池,创建一个写事件的池,然后创建for循环遍历cycle中的所有ngx_listening_t的结构体,对每一个ngx_listening_t结构体,也就是每一个监听套接字,从连接池中获取一个连接,将这个连接对应这个监听套接字,然后将读事件设置为ngx_event_accept,那么在对应的监听套接字上accept接受新的连接(划重点)!!!
4.连接结束完成后,调用这个监听套接字上的handler,也就是ngx_http_init_connection函数,从这个函数开始了HTTP请求的交互......
总结
总结首先对于一个初始化好的ngx_listening_t时,这里面只有一个套接字,没有可读可写事件,它需要ngx_connection_t托管,在ngx_connection_t中可读可写事件,在ngx_event_t结构中,data对应一个ngx_connection_t结构体。
参考资料:
https://blog.csdn.net/wangmanjie/article/details/52793847?utm_medium=distribute.pc_relevant.none-task-blog-2defaultBlogCommendFromBaidudefault-5.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2defaultBlogCommendFromBaidudefault-5.control
Nginx源码分析:3张图看懂启动及进程工作原理
图一:nginx 启动及内存申请过程分析
任何程序都离不开启动和配置解析。ngx 的代码离不开 ngx_cycle_s 和 ngx_pool_s 这两个核心数据结构,所以我们在启动之前先来分析下。
内存申请过程分为 3 步
假如申请的内存小于当前块剩余的空间,则直接在当前块中分配。
假如当前块空间不足,则调用 ngx_palloc_block 分配一个新块然后把新块链接到 d.next 中,然后分配数据。
假如申请的大小大于当前块的最大值,则直接调用 ngx_palloc_large 分配一个大块,并且链接到 pool→large 链表中
内存分配过程图解如下
(图片来自网络)
为了更好理解上面的图,可以参看文末附 2 的几个数据结构:ngx_pool_s 及 ngx_cycle_s。
知道了这两个核心数据结构之后,我们正式进入 main 函数,main 函数执行过程如下
调用 ngx_get_options() 解析命令参数;
调用 ngx_time_init() 初始化并更新时间,如全局变量ngx_cached_time;
调用 ngx_log_init() 初始化日志,如初始化全局变量 ngx_prefix,打开日志文件 ngx_log_file.fd;
清零全局变量 ngx_cycle,并为 ngx_cycle.pool 创建大小为 1024B 的内存池;
调用 ngx_save_argv() 保存命令行参数至全局变量 ngx_os_argv、ngx_argc、ngx_argv 中;
调用 ngx_process_options() 初始化 ngx_cycle 的 prefix, conf_prefix, conf_file, conf_param 等字段;
调用 ngx_os_init() 初始化系统相关变量,如内存页面大小 ngx_pagesize , ngx_cacheline_size , 最大连接数 ngx_max_sockets 等;
调用 ngx_crc32_table_init() 初始化 CRC 表 ( 后续的 CRC 校验通过查表进行,效率高 );
调用 ngx_add_inherited_sockets() 继承 sockets:
解析环境变量 NGINX_VAR = “NGINX” 中的 sockets,并保存至 ngx_cycle.listening 数组;
设置 ngx_inherited = 1;
调用 ngx_set_inherited_sockets() 逐一对 ngx_cycle.listening 数组中的 sockets 进行设置;
初始化每个 module 的 index,并计算 ngx_max_module;
调用 ngx_init_cycle() 进行初始化;
该初始化主要对 ngx_cycle 结构进行;
若有信号,则进入 ngx_signal_process() 处理;
调用 ngx_init_signals() 初始化信号;主要完成信号处理程序的注册;
若无继承 sockets,且设置了守护进程标识,则调用 ngx_daemon() 创建守护进程;
调用 ngx_create_pidfile() 创建进程记录文件;( 非 NGX_PROCESS_MASTER = 1 进程,不创建该文件 )
进入进程主循环;
若为 NGX_PROCESS_SINGLE=1模式,则调用 ngx_single_process_cycle() 进入进程循环;
否则为 master-worker 模式,调用 ngx_master_process_cycle() 进入进程循环;
在 main 函数执行过程中,有一个非常重要的函数 ngx_init_cycle,这个阶段做了什么呢?下面分析 ngx_init_cycle,初始化过程:
更新 timezone 和 time
创建内存池
给 cycle 指针分配内存
保存安装路径,配置文件,启动参数等
初始化打开文件句柄
初始化共享内存
初始化连接队列
保存 hostname
调用各 NGX_CORE_MODULE 的 create_conf 方法
解析配置文件
调用各NGX_CORE_MODULE的init_conf方法
打开新的文件句柄
创建共享内存
处理监听socket
创建socket进行监听
调用各模块的init_module
图二:master 进程工作原理及工作工程
以下过程都在ngx_master_process_cycle 函数中进行,启动过程:
暂时阻塞所有 ngx 需要处理的信号
设置进程名称
启动工作进程
启动cache管理进程
进入循环开始处理相关信号
master 进程工作过程
设置 work 进程退出等待时间
挂起,等待新的信号来临
更新时间
如果有 worker 进程因为 SIGCHLD 信号退出了,则重启 worker 进程
master 进程退出。如果所有 worker 进程都退出了,并且收到 SIGTERM 信号或 SIGINT 信号或 SIGQUIT 信号等,master 进程开始处理退出
处理SIGTERM信号
处理SIGQUIT信号,并且关闭socket
处理SIGHUP信号
平滑升级,重启worker进程
不是平滑升级,需要重新读取配置
处理重启 10处理SIGUSR1信号 重新打开所有文件 11处理SIGUSR2信号 热代码替换,执行新的程序 12处理SIGWINCH信号,不再处理任何请求
图三:worker 进程工作原理
启动通过执行 ngx_start_worker_processes 函数:
先在 ngx_processes 数组中找坑位if (ngx_processes[s].pid == -1) {break;}
进程相关结构初始化工作
创建管道 ( socketpair )
设置管道为非阻塞模式
设置管道为异步模式
设置异步 I/O 的所有者
如果 exec 执行的时候本 fd 不传递给 exec 创建的进程
fork 创建子进程。创建成功后,子进程执行相关逻辑:proc(cycle, data)。
设置 ngx_processes[s] 相关属性
通知子进程新进程创建完毕 ngx_pass_open_channel(cycle, &ch);
接下来是 ngx_worker_process_cycle worker 进程逻辑
ngx_worker_process_init
初始化环境变量
设置进程优先级
设置文件句柄数量限制
设置 core_file 文件
用户组设置
cpu 亲和度设置
设定工作目录
设置随机种子数
初始化监听状态
调用各模块的init_process方法进行初始化
关闭别人的fd[1],保留别人的fd[1]用于互相通信。自己的fd[1]接收master进程的消息。
监听channel读事件
进程模式
处理管道信号。这个过程由 ngx_channel_handler 完成,这部分具体实现在管道事件中讲解。
线程模式
ngx_worker_thread_cycle 是一个线程的循环:死循环中除了处理退出信号。主要进行ngx_event_thread_process_posted工作,这块具体内容在后面讲事件模型的时候再展开。
处理相关信号
master 和 worker 通信原理为: