Nginx stream 模块监听端口处理

时间:2024-05-19 20:04:09

一 简介

Nginx 模块在定义服务时,一般是通过配置server里面的listen端口来完成。根据不同的listen语法,可以实现下了几种功能。

     1. 最简单的,可以通过不同ip和port,对应某一个服务。

server1 {

            listen 1.2.3.4:2121;

}

上面的配置,可以让所有到1.2.3.4端口是2121的连接进行server1规定的服务。

 

     2. 通过指定port 范围,实现多个port对应一个服务。

server2 {

            listen 1.2.3.4:2121-2123;

}

上面的配置就可以实现所有到ip地址1.2.3.4端口从2121到2123的连接都指向同样的服务server2。

      3. 如果Nginx所在的服务器有多个ip 地址,可以通过不同的ip和相同的port组合实现不同的服务。

server3 {

            listen 1.2.3.4:2121;

}

server4{

            listen 2.3.4.5:2121;

}

         当客户端连接 1.2.3.4:2121 时,提供server3的服务。连接2.3.4.5:2121时,提供server4的服务。

     4. 如果Nginx所在的服务器有多个ip 地址,通过配置监听所有ip地址上相同的某些port。

server5 {

            listen 2121;

}

server6{

            listen *:2121;

}

上述两种配置的功能是一样的,通过省略ip地址或者用*表示ip地址,可以达到只要连接到达任何一个ip地址的端口2121都可以进行server5或者server6的目的。

     5. 如果Nginx所在的服务器有多个ip 地址,可以通过如下配置,针对同一个port,让特定ip的连接进行某个服务,剩余ip的连接进             行另外的服务。

server7 {

            listen *:2121;

}

server8{

            listen 1.2.3.4:2121;

}

连接到1.2.3.4:2121的连接进行server8的服务。连接别的ip地址的2121port进行server7的服务。

二 实现原理

数据结构

在进行代码分析之前,让我们先来看看与stream模块的listening port相关的数据结构是如何组织的。用户配置的server listen属性,最终需要生成ngx_listen_t结构。

Nginx stream 模块监听端口处理

                                                   图一  stream 模块监听端口相关数据结构

数据转换

从配置listen到生成ngx_listen_t的流程如下。其中需要经过两次数据结构的转换。

  1. 在配置解析阶段,server配置的listen port生成的ngx_stream_listen_t结构被存放到下图的右上角的红色线框中。也就是stream模块对应的ngx_stream_core_main_conf_t结构中的listen字段所对应的链表。
  2. 然后把链表ngx_stream_listen_t转换成ngx_stream_conf_port_t结构。通过这个转换可以把所有端口相同的ngx_stream_listen_t合并成一个ngx_stream_conf_port_t结构。并且把这些具有相同端口的ngx_stream_listen_t结构对应的地址存放到生成的那个ngx_stream_conf_port_t结构的地址数组中.
  3. 最后通过ngx_stream_optimize_servers函数,把ngx_stream_conf_port_t结构转化成ngx_listen_t结构。转换的原则是:如果没有通配监听(listen *:2121或者listen 2121),每一对唯一的host address和port生成一个ngx_listen_t。相反,如果有通配监听,所有相同的port (2121)对应的所有的地址,只生成一个ngx_listen_t结构,同时把所有的对应的port的地址存放到ngx_listen_t结构的addrs数组中。

配置举例

            server {

                         listen *:2121;

                         proxy_timeout 65534;

                         proxy_pass v*nftp1;

                         alg ftp;

            }

            server {

                         listen 10.250.64.103:2121;

                         proxy_timeout 65534;

                         proxy_pass v*nftp;

                         alg ftp;

            }

            server {

                         listen 60.60.60.77:2121;

                         proxy_timeout 65534;

                         proxy_pass v*nftp;

                         alg ftp;

             }

             上述例子会在上述转换过程中,会生成3个ngx_stream_listen_t,1个 ngx_stream_conf_port_t,因为有通配符的存在只会1               个ngx_listen_t.

            server {

                         listen 10.250.64.103:2121;

                         proxy_timeout 65534;

                         proxy_pass v*nftp;

                         alg ftp;

            }

            server {

                         listen 60.60.60.77:2121;

                         proxy_timeout 65534;

                         proxy_pass v*nftp;

                         alg ftp;

            }

            上述例子会在上述转换过程中,会生成2个ngx_stream_listen_t,1个 ngx_stream_conf_port_t,因为没有通配符所以生成2              个ngx_listen_t。

              server {

                         listen 60.60.60.77:2122;

                         proxy_timeout 65534;

                         proxy_pass v*nftp;

                         alg ftp;

            }

            server {

                         listen 60.60.60.77:2121;

                         proxy_timeout 65534;

                         proxy_pass v*nftp;

                         alg ftp;

              }

            上述例子会在上述转换过程中,会生成2个ngx_stream_listen_t,2个 ngx_stream_conf_port_t, 2个ngx_listen_t.

             server {

                         listen *:2121;

                         proxy_timeout 65534;

                         proxy_pass v*nftp;

                         alg ftp;

               }

               server {

                         listen 10.250.64.103:2121-2123;

                         proxy_timeout 65534;

                         proxy_pass v*nftp;

                         alg ftp;

                }

               server {

                         listen 60.60.60.77:2121;

                         proxy_timeout 65534;

                         proxy_pass v*nftp;

                         alg ftp;

                 }

              上述例子会在上述转换过程中,会生成5个ngx_stream_listen_t,3个 ngx_stream_conf_port_t, 3个ngx_listen_t.

三 代码流程

配置平面

在配置解析过程中,对应于stream模块配置解析入口是ngx_stream_block.在此函数中,对于stream server中对应的每一个listen字段,相应的解析函数是ngx_stream_core_listen。

函数的大提流程是:

  1. 调用ngx_parse_url -> ngx_parse_inet_url 生成了ngx_url_t数据结构。在ngx_url_t这个结构中,会存放listen定义的所有的port和可能的地址。如果语法是 listen 2121. 则会生成一个port,和一个INADDR_ANY地址。如果语法是listen *:2121,也会生成一个port,和一个INADDR_ANY地址。如果语法是listen 1.2.3.4:2121,生成一个port,和一个1.2.3.4的地址。如果语法是listen 1.2.3.4:2121-2123 则生成3个port和一个1.2.3.4的地址。如果语法是listen hostname:2121-2123则会生成3个port并且根据对hostname的域名解析得到的地址个数生成相应的地址数量。最后通过ngx_inet_add_addr函数把总数为port_num * addr_num数量的addr存放到ngx_url_t结构中。
  2. 生成上述的结构ngx_url_t后,根据结构中地址的个数 (port_num * addr_num)生成相应数量的ngx_stream_listen_t添加到ngx_stream_core_main_conf_t结构中的listen成员中。同时解析比如reuse,sndbuf,rcvbuf等属性。
  3. 通过函数ngx_stream_add_ports把上述的listen成员中的ngx_stream_listen_t数据按照相同的port number生成一个ngx_stream_conf_port_t的原则把所有的ngx_stream_listen_t结构转换成ngx_stream_conf_port_t结构。对于原来ngx_stream_listen_t中port number相同但是地址不相同的情况把所有的地址都组合起来存放到同一个ngx_stream_conf_port_t结构中的addr数组中。
  4. 通过函数ngx_stream_optimize_servers把上述的ngx_stream_conf_port_t结构定义的port转变成ngx_listening_t结构。转换的规则是:对于有通配匹配的情况(list *:2121或者listen 2121)的端口,如果有几个server对应的port number是一样的,则会生成唯一的一个ngx_listen_t结构,然后把几个server对应的地址存放到ngx_listen_t的地址数组中。对于没有通配匹配的端口,则会根据唯一的地址和端口对生成一个唯一的ngx_listen_t结构。每一个结构中只包含一个addr_t结构。

数据平面

当连接初次建立时,(对于udp协议路径是ngx_event_recvmsg,对于tcp协议路径是ngx_event_accept),ngx_stream_init_connection函数得到调用。此时如果对应connection上的ngx_listen_t对应的addr有多个。(针对listen *情况),根据连接的Nginx侧的连接信息来确定使用的server是哪一个。通过这个server来连接upstream结构和server的context.

四 结语

为了支持监听端口的通配匹配,Nginx做了很多额外的工作进行了数据结构的两次转化。但是也没有想到更好的办法可以省略这两次转换。

另外,这篇文章更多是帮助自己理解代码而写的,希望对大家也有帮助。