nginx如何调用 socket() bind() listen() accept()等网络编程接口(一)

  学习过Linux网络编程的小伙伴大多知道,一个基于Linux系统的TCP服务器程序通常都需要调用 socket,bind,listen,accept以及read/write等函数。对于有并发要求的服务器程序,还会考虑使用IO多路复用(select、poll、epoll)接口以及多进程或者多线程的函数接口。有一定网络编程经验的同学可以很熟练地使用这些接口实现一个支持并发处理的Linux TCP服务器程序。nginx同样具备TCP服务器的功能,并且这也是它最主要的功能。nginx的两大应用层框架,http和mail框架以及它的TCP代理框架stream框架都是基于TCP服务实现的,那么nginx的框架中是如何调用上述这些函数接口的呢?
  要搞清楚这个问题,我们首先要知道nginx的进程结构。nginx通常工作在一个master进程-多个worker进程的工作模式下,而且用户还可以通过nginx的配置文件选择使用或不使用worker进程,并能够控制其数量。nginx的每一个worker进程负责处理客户端发来的连接请求,并且每一个worker进程都只有单个线程。看到这有同学就会想,难道说nginx的并发处理是基于多进程的思想实现的吗?并不全是。因为nginx的worker进程是单线程结构,如果一个进程只服务一个客户端连接,那nginx也就没有性能可言了。所以nginx的worker进程是通过IO多路复用的方式来处理客户端连接请求的。
这就是nginx干活时的模样
  nginx在刚启动时只有一个master进程,它的初始执行代码就在nginx的main函数中,就是nginx.c文件中的main函数。main函数最开始执行的都是一些初始化代码,其中最重要的一个初始化函数是 ngx_init_cycle 函数。该函数对nginx最重要的数据 结构 ngx_cycle_t 完成创建和初始化工作。在ngx_cycle_t 结构中,有一个和监听套接字相关的数组对象 listening。其创建代码如下:

    n = old_cycle->listening.nelts ? old_cycle->listening.nelts : 10;
    if (ngx_array_init(&cycle->listening, pool, n, sizeof(ngx_listening_t))
    {
        ngx_destroy_pool(pool);
        return NULL;
    }
    ngx_memzero(cycle->listening.elts, n * sizeof(ngx_listening_t));

  上面的代码创建并初始化了 cycle->listen 数组对象。该数组的成员数据类型 ,ngx_listening_t ,中有我们比较关心的Linux网络接口函数参数,例如套接字 fd,struct sockaddr 结构体,套接字类型以及套接字选项等数据。
在这里插入图片描述
  那么这些数据是从何而来的呢?
  首先,nginx 调用 socket、bind、listen接口的位置在 ngx_init_cycle 调用的 ngx_open_listening_sockets 函数中。通读 ngx_init_cycle 函数的代码可以发现,从初始化 cycle->listen 数组对象到调用 ngx_open_listening_sockets 打开监听套接字。中间经历了一个至关重要的环节,就是 ngx_conf_parse 函数的调用。没错,就是nginx的配置文件解析过程。
  使用过nginx的同学都知道,nginx 的http、mail以及stream的配置框架中,总有一个个的 server 配置块存在,每一个server 块都有一个 listen 指令的存在。下面我们以 http 框架为例,简单说一下配置文件的listen指令参数如何通过配置解析到达 socket、bind、listen这些接口的。
首先看 listen 指令的的使用方法:

listen address[:port] [default_server] [setfib=number] [backlog=number] [rcvbuf=size] [sndbuf=size] [accept_filter=filter] [deferred] [bind] [ipv6only=on|off] [ssl] [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]];

  listen 指令在配置监听地址端口的同时,还支持许多套接字选项的配置。因此 listen 指令的解析函数 ngx_http_core_listen 的大致结构分为以下三块:listen 参数地址端口解析,套接字选项解析,配置结构存储。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  ngx_http_core_listen 函数的地址解析调用了 ngx_parse_url 函数。此函数通过解析地址格式得到 socket 函数的地址族参数以及 bind 函数的 struct sockaddr_in 结构参数:
在这里插入图片描述
  对于nginx.conf 中常用的 “listen + 端口号”的配置模式,bind 函数的 struct sockaddr_in 参数的内容如下:
在这里插入图片描述
  ngx_http_core_listen 函数主要操作的数据对象是 ngx_http_listen_opt_t 类型的局部变量 lsopt。
在这里插入图片描述
  对 listen 指令后面的套接字选项参数的解析过程主要也是设置 lsopt 变量中的相应数据成员标志位。在listen指令参数解析完成后,lsopt 变量便保存了listen参数的所有的解析结果。接下来ngx_http_core_listen 调用 ngx_http_add_listen 函数将局部变量 lsopt 结构体中的所有数据存放到了 cmcf->ports 数组成员的 addrs 数组成员的 opt成员当中。
在这里插入图片描述

在这里插入图片描述
  那么这个解析结果又是如何跑到我们关心的 cycle->listen 数组之中的呢?
  这就要看nginx 的 “http” 指令的解析函数 ngx_http_block 调用的 ngx_http_optimize_servers 函数了。ngx_http_optimize_servers 调用位置位于 ngx_http_block 函数的最后,也就是说,ngx_http_block 函数调用 ngx_http_optimize_servers 函数时,listen指令已经解析完成。ngx_http_optimize_servers 的第三个参数正是 cmcf->ports 数组,ngx_http_optimize_servers 调用 ngx_http_init_listening 将 cmcf->ports 数组成员的 addrs 数组成员的 opt 内容复制到了 cycle->listen 数组的成员之中
在这里插入图片描述
在这里插入图片描述
  (上述内容的详细流程可参见 ngx_http_add_listening 函数的执行流程)。
  以上就说完了 socket、bind、listen 函数的参数来源。 socket、bind、listen 三个函数的具体调用位置在 ngx_open_listening_sockets 函数中,
在这里插入图片描述
  上图中的代码省略了一些套接字的设置和适配代码,有兴趣的同学可自行阅读研究一下这些选项参数的作用,本人认为对Linux网络编程的学习大有裨益。
  截止目前,我们分析的代码还处于 nginx 启动阶段,也就是说当nginx fork 出 worker 进程后,每一个 worker 进程都会继承 master 进程的监听套接字结构,并会对这些监听套接字都会处在 listen 的状态。如果此时客户端向某一个监听端口发起连接,这许多个 worker 进程会做出怎样的反应?nginx 又是如何调用 accept 函数呢?nginx 同客户端的读写通信又是如何实现的呢?请看下篇。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值