万字多图,搞懂 Nginx 高性能网络工作原理!

本文深入探讨了 Nginx 的 Master 和 Worker 进程在网络编程中的角色,重点解析了 Nginx 如何在多进程下利用 epoll 实现高效网络通信。Master 进程主要负责初始化和管理 Worker 进程,而 Worker 进程则执行 epoll_create、epoll_ctl 和 epoll_wait,处理用户连接。在多 Worker 进程环境下,Nginx 通过监听端口的分配和连接事件的处理策略确保高并发性能。
摘要由CSDN通过智能技术生成

在单进程的网络编程模型中。所有的网络相关的动作都是在一个进程里完成的,如监听 socket 的创建, bind、listen。再比如 epoll 的创建、要监听事件的添加,以及 epoll_wait 等待时间发生。这些统统都是在一个进程里搞定。

一个客户端和使用了 epoll 的服务端的交互过程如下图所示。

​以下是其大概的代码示例(没耐心看的同学可以先)。

int main(){
 //监听
 lfd = socket(AF_INET,SOCK_STREAM,0);
 bind(lfd, ...)
 listen(lfd, ...);

 //创建epoll对象,并把 listen socket的事件管理起来
 efd = epoll_create(...);
 epoll_ctl(efd, EPOLL_CTL_ADD, lfd, ...);

 //事件循环
 for (;;)
 {
  size_t nready = epoll_wait(efd, ep, ...);
  for (int i = 0; i < nready; ++i){

   if(ep[i].data.fd == lfd){
    //lfd上发生事件表示都连接到达,accept接收它
    fd = accept(listenfd, ...);
    epoll_ctl(efd, EPOLL_CTL_ADD, fd, ...);
   }else{
    //其它socket发生的事件都是读写请求、或者关闭连接
    ...
   }
  }
 }
}

在单进程模型中,不管有多少的连接,是几万还是几十万,服务器都是通过 epoll 来监控这些连接 socket 上的可读和可写事件。当某个 socket 上有数据发生的时候,再以非阻塞的方式对 socket 进行读写操作。

事实上,Redis 5.0 及以前的版本中,它的网络部分去掉对 handler 的封装,去掉时间事件以后,代码基本和上述 demo 非常接近。而且因为 Redis 的业务特点只需要内存 IO,且 CPU 计算少,所以可以达到数万的 QPS。

但是单进程的问题也是显而易见的,没有办法充分发挥多核的优势。所以目前业界绝大部分的后端服务还都是需要基于多进程的方式来进行开发的。到了多进程的时候,更复杂的问题多进程之间的配合和协作问题就产生了。比如

  • 哪个进程执行监听 listen ,以及 accept 接收新连接?

  • 哪个进程负责发现用户连接上的读写事件?

  • 当有用户请求到达的时候,如何均匀地将请求分散到不同的进程中?

  • 需不需要单独搞一部分进程执行计算工作

  • ...

事实上,以上这些问题并没有标准答案。各大应用或者网络框架都有自己不同的实现方式。为此业界还专门总结出了两类网络设计模式 - Reactor 和 Proactor。不过今天我不想讨论这种抽象模式,而是想带大家看一个具体的 Case - Nginx 是如何在多进程下使用 epoll 的。

【文章福利】另外小编还整理了一些C++后端开发面试题,教学视频,后端学习路线图免费分享,需要的可以自行添加:学习交流群点击加入~ 群文件共享

小编强力推荐C++后端开发免费学习地址:C/C++Linux服务器开发高级架构师/C++后台开发架构师​icon-default.png?t=M666https://ke.qq.com/course/417774?flowToken=1013189

一、 Nginx Master 进程初始化

在 Nginx 中,将进程分成了两类。一类是 Master 进程,一类是 Worker 进程。

在 Master 进程中,主要的任务是负责启动整个程序、读取配置文件、监听和处理各种信号,并对 Worker 进程进行统筹管理。

不过今天我们要查看的重点问题是看网络。在 Master 进程中,和网络相关的操作非常简单就是创建了 socket 并对其进行 bind 和 监听。

​具体细节我们来看 Main 函数。

//file: src/core/nginx.c
int ngx_cdecl main(int argc, char *const *argv)
{
 ngx_cycle_t      *cycle, init_cycle;

 //1.1 ngx_init_cycle 中开启监听
 cycle = ngx_init_cycle(&init_cycle);

 //1.2 启动主进程循环
 ngx_master_process_cycle(cycle);
}

在 Nginx 中,ngx_cycle_t 是非常核心的一个结构体。这个结构体存储了很多东西,也贯穿了好多的函数。其中对端口的 bind 和 listen 就是在它执行时完成的。

ngx_master_process_cycle 是 Master 进程的主事件循环。它先是根据配置启动指定数量的 Worker 进程,然后就开始关注和处理重启、退出等信号。接下来我们分两个小节来更详细地看。

1.1 Nginx 的服务端口监听

我们看下 ngx_init_cycle 中是如何执行 bind 和 listen 的。

//file: src/core/ngx_cycle.c
ngx_cycle_t *ngx_init_cycle(ngx_cycle_t *old_cycle)
{
 ......
 if (ngx_open_listening_sockets(cycle) != NGX_OK) {
  goto failed;
 }
}

真正的监听还是在 ngx_open_listening_sockets 函数中,继续看它的源码。

//file: src/core/ngx_connection.c
ngx_int_t ngx_open_listening_sockets(ngx_cycle_t *cycle)
{
 ......

 //要监听的 socket 对象
 ls = cycle->listening.elts;
 for (i = 0; i < cycle->listening.nelts; i++) {

  //获取第i个socket
  s = ngx_socket(ls[i].sockaddr->sa_famil
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值