nginx继承socket 和 热代码替换

本文详细解析了nginx的热代码替换过程,涉及nginx如何通过ngx_add_inherited_sockets函数从环境变量中继承socket,以及在接收到USR2信号后,如何进行热启动,包括socket的保存、子进程的创建和execve的使用,确保socket文件描述符在进程间继承。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    在看nginx源码的时候,遇到这样一个函数:ngx_add_inherited_sockets, 这个函数在main函数里被调用。
  里面具体做的事情就是:从一个环境变量读出socket文件描述符,然后这个socket文件描述符就可以用了,不用调用socket()函数创建。具体可以看这位大神的博客:http://blog.csdn.net/livelylittlefish/article/details/7277607
 
  但我读完这篇博客还是有一个困惑的问题,可能是我智商捉急不能理解大神的文章:为什么socket从环境变量里读出来,就能用了?
 
    进过反复阅读代码终于懂了,这就是nginx热启动。
    
    来看一下这个热启动的执行过程。
    
    1:给nginx发一个USR2信号:在ngx_process.c文件里有一个signals信号数组,这个数组定义了nginx所支持的信号,
    
    其中一个,
    { ngx_signal_value(NGX_CHANGEBIN_SIGNAL),
      "SIG" ngx_value(NGX_CHANGEBIN_SIGNAL),
      "",
      ngx_signal_handler },
    
    ngx_config.h里面有
    #define NGX_CHANGEBIN_SIGNAL     USR2
    
    2:处理函数ngx_signal_hanler做了啥:
      ngx_process.c:
      
      case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
            if (getppid() > 1 || ngx_new_binary > 0) {

                /*
                 * Ignore the signal in the new binary if its parent is
                 * not the init process, i.e. the old binary's process
                 * is still running.  Or ignore the signal in the old binary's
                 * process if the new binary's process is already running.
                 */
                /* 这里给出了详细的注释,更通俗一点来讲,就是说,进程现在是一个
                * master(新的master进程),但是当他的父进程old master还在运行的话,
                * 这时收到了USR2信号,我们就忽略它,不然就成了新master里又要生成
                * master。。。另外一种情况就是,old master已经开始了生成新master的过程
                * 中,这时如果又有USR2信号到来,那么也要忽略掉。。。(不知道够不够通俗=.=)
                参考文档:http://blog.csdn.net/dingyujie/article/details/7192144
                */
                action = ", ignoring";
                ignore = 1;
                break;
            }

            //正常情况下,需要热代码替换,设置标志位
            ngx_change_binary = 1;
            action = ", changing binary";
            break;
            
      
       里面设置了ngx_change_binary = 1,这一句是设置热启动标志
       
    3:ngx_process_cycle.c里面有个 ngx_master_process_cycle 这里面会循环检测各种标志,其中就包括热启动
    
    //热代码替换
    if (ngx_change_binary) { // 信号处理函数里将这个设置成了1
      ngx_change_binary = 0;
      ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");
      //进行热代码替换,这里是调用execve来执行新的代码
      ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
    }
    
    4:ngx_exec_new_binary干了啥?
      
        1:现将socket写到环境变量里面
        2:pid = ngx_execute(cycle, &ctx)
        
    5:ngx_execute干了啥?
    
      ngx_pid_t
      ngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx)
      {
        return ngx_spawn_process(cycle, ngx_execute_proc, ctx, ctx->name,
                             NGX_PROCESS_DETACHED);
      }
    6: ngx_spawn_process干了啥?
      ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
    char *name, ngx_int_t respawn);
      这个函数创建子进程,并让子进程执行proc函数,第五步传进来的是ngx_execute_proc
      
      ngx_spawn_process函数里有://创建子进程
        pid = fork();

        switch (pid) {

        case -1:
          ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "fork() failed while spawning \"%s\"", name);
          ngx_close_channel(ngx_processes[s].channel, cycle->log);
          return NGX_INVALID_PID;

        case 0:
          ngx_pid = ngx_getpid();
          //在子进程中执行传递进来的函数,即工作进程的具体工作
          proc(cycle, data);
          break;

        default:
          break;
      }
     7:ngx_execute_proc做了啥:
       static void
       ngx_execute_proc(ngx_cycle_t *cycle, void *data)
       {
         ngx_exec_ctx_t  *ctx = data;

         if (execve(ctx->path, ctx->argv, ctx->envp) == -1) {
          ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "execve() failed while executing %s \"%s\"",
                      ctx->name, ctx->path);
       }

       exit(1);
       }
       
       这个函数里调用的execve
       
       而文件描述符只要不设置close_on_exec就可以被继承下来。
       
       说到这里,我感觉原来的我已经懂了。
/* Copyright (c) 2025-2026 TP-LINK Technologies CO.LTD * All rights reserved * \file primeNumber.c * \brief The source file for primeNumber * \author zuoshicheng <zuoshicheng@tp-link.com> * \version 1.0.0 * \date 2025/07/30 * \history \arg 1.0.0 2025/07/30 zuoshicheng, Create file */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/stat.h> #include <fcntl.h> #define PORT 80 #define WEB_ROOT "./web" // 网站根目录 #define BUFFER_SIZE 8192 // 根据文件扩展名返回MIME类型 const char* get_mime_type(const char *ext) { if (strcmp(ext, "html") == 0) return "text/html"; if (strcmp(ext, "css") == 0) return "text/css"; if (strcmp(ext, "js") == 0) return "application/javascript"; if (strcmp(ext, "jpg") == 0) return "image/jpeg"; if (strcmp(ext, "png") == 0) return "image/png"; return "application/octet-stream"; } // 发送HTTP响应(含文件内容) void send_response(int client_fd, const char *path) { char buffer[BUFFER_SIZE]; FILE *file = fopen(path, "rb"); if (!file) { // 文件不存在时发送404 const char *not_found = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\n\r\n<h1>404 Not Found</h1>"; send(client_fd, not_found, strlen(not_found), 0); return; } // 获取文件扩展名 const char *ext = strrchr(path, '.'); if (ext) ext++; else ext = ""; // 构造HTTP头部 struct stat st; stat(path, &st); snprintf(buffer, BUFFER_SIZE, "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %ld\r\n\r\n", get_mime_type(ext), st.st_size ); send(client_fd, buffer, strlen(buffer), 0); // 发送文件内容 size_t bytes_read; while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, file)) > 0) { send(client_fd, buffer, bytes_read, 0); } fclose(file); } // 主服务逻辑 int main() { int server_fd, client_fd; struct sockaddr_in server_addr, client_addr; socklen_t addr_len = sizeof(client_addr); // 创建Socket server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd < 0) { perror("Socket creation failed"); exit(EXIT_FAILURE); } // 绑定端口 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))) { perror("Bind failed"); close(server_fd); exit(EXIT_FAILURE); } // 监听请求 if (listen(server_fd, 10) < 0) { perror("Listen failed"); close(server_fd); exit(EXIT_FAILURE); } printf("Server running on http://localhost:%d\n", PORT); // 主循环:接受请求并处理 while (1) { client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len); if (client_fd < 0) { perror("Accept error"); continue; } // 读取HTTP请求 char request[BUFFER_SIZE]; recv(client_fd, request, BUFFER_SIZE, 0); // 解析请求路径(简化版) char *method = strtok(request, " "); char *path = strtok(NULL, " "); if (!method || !path) { close(client_fd); continue; } // 映射路径到本地文件 char full_path[256]; if (strcmp(path, "/") == 0) path = "/Index.html"; // 默认首页 snprintf(full_path, sizeof(full_path), "%s%s", WEB_ROOT, path); // 发送响应 send_response(client_fd, full_path); close(client_fd); } close(server_fd); return 0; } 改用多进程方法实现以上代码
最新发布
08-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值