【Nginx源码分析】Nginx的listen处理流程分析

施洪宝

一. 基础

  • nginx源码采用1.15.5
  • 后续部分仅讨论http中的listen配置解析以及优化流程

1.1 概述

  1. 假设nginx http模块的配置如下
http{
    server {
        listen 127.0.0.1:8000;
        server_name www.baidu.com;
        root html;
        location /{
            index index.html;
        }
    }
    server {
        listen 10.0.1.1:8000;
        server_name www.news.baidu.com;
        root html;
        location /{
            index index.html;
        }
    }
    server {
        listen 8000; #相当于0.0.0.0:8000
        server_name www.tieba.baidu.com;
        root html;
        location /{
            index index.html;
        }
    }
    server {
        listen 127.0.0.1:8000;
        server_name www.zhidao.baidu.com;
        location / {
            root html;
            index index.html;
        }
    }
}
  1. 端口, 地址, server的关系
  • 端口是指一个端口号, 例如上面的8000端口
  • 地址是ip+port, 例如127.0.0.1:8000, 10.0.1.1:8000, 0.0.0.0:8000, listen后配置的是一个地址。
  • 每个地址可以放到多个server中, 例如上面的127.0.0.1:8000

总而言之, 一个端口可以有多个地址, 每个地址可以有多个server

1.2 存在的问题

  1. 是否需要在读取完http块中所有的server才能建立监听套接字, 绑定监听地址?
  • 是的, 因为允许配置通配地址, 故而必须将http块中的server全部读取完后, 才能知道如何建立监听套接字。
  1. 一个端口可以对应多个地址, 如何建立监听套接字, 如何绑定地址?
  • 通常情况下, 每个地址只能绑定一次(只考虑tcp协议), 这种情况下, 我们只能选择部分地址创建监听套接字, 绑定监听地址。
  • 当配置中存在通配地址(0.0.0.0:port)时, 只需要创建一个监听套接字, 绑定这个通配地址即可, 但需要能够依据该监听套接字找到该端口配置的其他地址, 这样当客户端发送请求时, 可以根据客户端请求的地址, 找到对应地址下的相关配置。
  • 当配置中不存在通配地址时, 需要对每个地址都创建一个监听套接字, 绑定监听地址。
  1. 一个地址多个server的情况下, 如何快速找到客户端请求的server?
  • 比较合适的方案是通过hash表。
  • 为了快速找到客户端请求的server, nginx以server_name为key, 每个server块的配置(可以理解为一个指针, 该指针指向整个server块的配置)为value, 放入到哈希表。
  • 由于server_name中可以出现正则匹配等情况, nginx将server_name具体分为4类进行分别处理(www.baidu.com, *baidu.com, www.baidu*, ~*baidu)。

1.3 nginx listen解析的流程

总体而言分为2步,

  1. 将所有http模块内的配置解析完成, 将listen的相关配置暂存(主要存储监听端口以及监听地址)。
  2. 根据上一步暂存的监听端口以及监听地址, 创建监听套接字, 绑定监听地址

二. 配置解析

nginx http块解析完成后, 会存储配置文件中配置的监听端口以及监听地址, 其核心结构图如下,
image
总体而言, 结构可以分为3级, 端口->地址->server

2.1 源码

listen的处理流程:

  • ngx_http_core_listen: 读取配置文件配置
  • ngx_http_add_listen: 查看之前是否出现过当前监听的端口, 没有则新建, 否则追加
  • ngx_http_add_address: 查看之前该端口下是否监听过该地址, 没有则新建, 否则追加。
  • ngx_http_add_server: 查看server之前是否出现过, 没有则新建, 否则报错(重复定义)。

三. 创建监听套接字

nginx最终创建的监听套接字及其相关的结构图如下,
image

  • 每个ngx_listening_t结构对应一个监听套接字, 绑定一个监听地址
  • 每个ngx_listening_t结构后面需要存储地址信息, 地址可能不止一个, 因为这个监听套接字可能绑定的是通配地址, 这个端口下的其他地址都会放在这个监听套接字下。例如, 1.1节的配置中, 只会创建一个ngx_listening_t结构, 其他地址的配置都会放到这个通配地址下。
  • 每个监听地址可能对应多个域名(配置文件中的server_name), 需要将这些域名放到哈希表中, 以供后续使用

总体而言, 结构分为3级, 监听套接字->监听地址->server

3.1 源码

读取完http块后, 需要创建监听套接字绑定监听地址, 处理函数ngx_http_optimize_servers, 该函数的处理流程:

  • 遍历所有监听端口, 针对每个监听端口, 执行以下3步
  • 对该端口下所有监听地址排序(listen后配置bind的放在前面, 通配地址放在后面)
  • 遍历该端口下的所有地址, 将每个地址配置的所有server, 放到该地址的哈希表中。
  • 为该端口建立监听套接字, 绑定监听地址。

四. 监听套接字的使用

  1. 假设此处我们使用epoll作为事件处理模块
  2. epoll在增加事件时, 用户可以使用epoll_event中的data字段, 当事件发生时, 该字段也会带回。
  3. nginx中的epoll_event指向的是ngx_connection_t结构, 事件发生时, 调用ngx_connection_t结构中的读写事件, 负责具体处理事件, 参见下图。
//c is ngx_connection_t
rev = c->read;
rev->hadler(rev);
wev = c->write;
wev->handler(wev);

image

  1. 每个监听套接字对应一个ngx_connection_t, 该结构的读事件回调函数为ngx_event_accept, 当用户发起tcp握手时, 通过ngx_event_accept接受客户端的连接请求。
  • ngx_event_accept会接受客户端请求, 初始化一个新的ngx_connection_t结构, 并将其加入到epoll中进行监听, 最后会调用ngx_connection_t对应的ngx_listening_t的处理函数(http块对应ngx_http_init_connection, mail块ngx_mail_init_connection, stream块对应ngx_stream_init_connection)

五. 总结

  1. nginx在读取listen相关的配置时, 将结构分为3级, 端口->地址->server, 各级都是一对多的关系。
  2. nginx在创建监听套接字时, 将结构分为3级, 监听套接字->地址->server, 各级都是一对多的关系。

Nginx源码分析
研读nginx源码

一群热爱代码的人 研究Nginx PHP Redis Memcache Beanstalk 等源码 以及一群热爱前端的人

7.1k 声望
12.7k 粉丝
0 条评论
推荐阅读
【转发】来自西红柿(李乐)的《深入理解Go语言
作者:李乐 原文地址:[链接]第一章 Go语言快速入门  第一篇 基本语法  第二讲 数组与切片  第三讲 字符串  第四讲 哈希表MAP  第五讲 结构体与接口 &em...

LNMPRG源码研究1阅读 1k

如何选择适合你的微服务 API 网关:对比 Kong、APISIX、Tyk、Apigee 和其他网关
API 网关并非一个新兴的概念,在十几年前就已经存在了,它的作用主要是作为流量的入口,统一的处理和业务相关的请求,让请求更加安全、快速和准确的得到处理。它有以下传统的功能:

API7_技术团队8阅读 8.6k评论 2

Nginx 配置常用参数,看这一篇就够了
最近在全面学习Nginx,当作笔记了,如有错误,欢迎指出或深入交流。主模块 {代码...} 事件模块 {代码...} http部分 {代码...} 部分参数详细说明server_name {代码...} location {代码...} location表达式类型 {代...

开源到2阅读 1.9k

化虹为桥 - Nginx 如何代理 UDP “连接”
众所周知,UDP 并不像 TCP 那样是基于连接的。但有些时候,我们需要往一个固定的地址发送多个 UDP 来完成一个 UDP 请求。为了保证服务端能够知道这几个 UDP 包构成同一个会话,我们需要在发送 UDP 包时绑定某个端...

spacewander4阅读 1.6k

Nginx unexpected end of file 配置证书遇到问题,如何解决?
通过 letsencrypt 申请证书后,默认服务器安装了 Nginx 1.8 发现,在默认的 /etc/nginx/sites-enabled/default 内容配置 SSL 的 site.com.key 后。重启 Nginx 出现一下错误:

程序员泥瓦匠1阅读 1.2k

mac M1 nginx配置文件位置
mac nginx配置的原位置/usr/local/etc/nginx/nginx.confmac M1 nginx配置位置/usr/local/Homebrew/etc/nginx/nginx.confnginx -t 查看位置

寿兽阅读 3k

(学习到实践)七、mongodb测试,php+nginx负载均衡的部署
从测试容器中匹配搜索得到 mongod.conf.orig,设置可以启动,网上查找配置项反不能启动,原因是配置是yaml格式!好像听说过。百度查询得到:官方配置说明,网站卡得出奇。

沧浪水阅读 2.7k

一群热爱代码的人 研究Nginx PHP Redis Memcache Beanstalk 等源码 以及一群热爱前端的人

7.1k 声望
12.7k 粉丝
宣传栏