我们知道,原生 Nginx 增加、修改一个第三方模块,需要重新编译源代码,所有的模块都是用静态链接的形式组织起来的。而 Tengine 有一个增强的功能,即动态模块加载 DSO(Dynamic Shared Objects), 可以实现运行时动态加载模块,而不用每次都要重新编译Tengine。

在 2016 年农历春节期间,Nginx 官方发布了最新版本 Nginx-1.9.11,也增加了该功能。

Nginx-1.9.11 的Changelog 如下:

Changes with nginx 1.9.11 09 Feb 2016

*) Feature: TCP support in resolver.

*) Feature: dynamic modules.

*) Bugfix: the $request_length variable did not include size of request
headers when using HTTP/2.

*) Bugfix: in the ngx_http_v2_module.

从使用的角度上来说,是增加了一个指令 load_modules 指令,来加载编译为 so 形式的动态模块。

该功能的实现还是挺简单的,主要包括如下几个方面:

  • 将模块相关的类型定义和操作方法调整到新的文件 ngx_module.h 和 ngx_module.c 中
  • 添加 dlopen 的封装
  • 调整编译脚本
  • 少量 Nginx 核心代码的调整。主要是为 cycle 新增了一个成员modules, 用来取代之前的全局变量 ngx_modules, 并将之前初始化时对于 ngx_modules 的操作封装成几个函数,放在 init_cycle 里调用。

我认为我们需要重点关注的是如下几个:

  • 编译脚本有了较大的变化,在以后的模块编程中,尽量要让自己的模块能够静态链接和动态链接。这就需要适应一下新的编译框架。
  • 不光旧的模块需要考虑兼容性,新的模块也要考虑对于旧的 Nginx 版本的兼容性。这包括两个方面,一个是 c 语言层面,要关注动态模块对于 Nginx 框架的调整,例如之前凡是用到全局变量 ngx_modules 的地方,要修改为 cycle->modules,当然正确的做法应该是用宏开关来判断当前 Nginx 版本是否大于 1.9.11 来决定是否调整;另一个是编译脚本,也需要判断版本,从而做出一些开关的选择,重点是对 ngx_module_link 变量的判断。

举个例子,对于 nginx-rtmp 模块,我们要将其修改为既能在 Nginx-1.9.11 及其以后的版本中,同时支持静态链接(–add-module)和动态链接(–add-dynamic-module),又要让其在老的版本中依然能支持旧的编译框架下的静态链接(–add-module)。

为了达到这个目的,我做了如下测试,首先将 nginx-rtmp 的 congfig 文件修改为:

ngx_addon_name="ngx_rtmp_module"  
RTMP_CORE_MODULES="ngx_rtmp_module                             \  
                   ngx_rtmp_core_module                        \  
                   ngx_rtmp_cmd_module                         \  
                   ngx_rtmp_codec_module                       \  
                   ngx_rtmp_access_module                      \  
                   ngx_rtmp_record_module                      \  
                   ngx_rtmp_live_module                        \  
                   ngx_rtmp_play_module                        \  
                   ngx_rtmp_flv_module                         \  
                   ngx_rtmp_mp4_module                         \  
                   ngx_rtmp_netcall_module                     \  
                   ngx_rtmp_relay_module                       \  
                   ngx_rtmp_exec_module                        \  
                   ngx_rtmp_auto_push_module                   \  
                   ngx_rtmp_notify_module                      \  
                   ngx_rtmp_log_module                         \  
                   ngx_rtmp_limit_module                       \  
                   ngx_rtmp_hls_module                         \  
                   ngx_rtmp_dash_module                        \  
                   "  
RTMP_HTTP_MODULES="ngx_rtmp_stat_module                        \  
                   ngx_rtmp_control_module                        \  
                  "  
RTMP_DEPS="$ngx_addon_dir/ngx_rtmp_amf.h               \  
           $ngx_addon_dir/ngx_rtmp_bandwidth.h         \  
           $ngx_addon_dir/ngx_rtmp_cmd_module.h        \  
           $ngx_addon_dir/ngx_rtmp_codec_module.h      \  
           $ngx_addon_dir/ngx_rtmp_eval.h              \  
           $ngx_addon_dir/ngx_rtmp.h                   \  
           $ngx_addon_dir/ngx_rtmp_version.h           \  
           $ngx_addon_dir/ngx_rtmp_live_module.h       \  
           $ngx_addon_dir/ngx_rtmp_netcall_module.h    \  
           $ngx_addon_dir/ngx_rtmp_play_module.h       \  
           $ngx_addon_dir/ngx_rtmp_record_module.h     \  
           $ngx_addon_dir/ngx_rtmp_relay_module.h      \  
           $ngx_addon_dir/ngx_rtmp_streams.h           \  
           $ngx_addon_dir/ngx_rtmp_bitop.h             \  
           $ngx_addon_dir/ngx_rtmp_proxy_protocol.h    \  
           $ngx_addon_dir/hls/ngx_rtmp_mpegts.h        \  
           $ngx_addon_dir/dash/ngx_rtmp_mp4.h          \  
           "  
RTMP_CORE_SRCS="$ngx_addon_dir/ngx_rtmp.c                   \  
                $ngx_addon_dir/ngx_rtmp_init.c              \  
                $ngx_addon_dir/ngx_rtmp_handshake.c         \  
                $ngx_addon_dir/ngx_rtmp_handler.c           \  
                $ngx_addon_dir/ngx_rtmp_amf.c               \  
                $ngx_addon_dir/ngx_rtmp_send.c              \  
                $ngx_addon_dir/ngx_rtmp_shared.c            \  
                $ngx_addon_dir/ngx_rtmp_eval.c              \  
                $ngx_addon_dir/ngx_rtmp_receive.c           \  
                $ngx_addon_dir/ngx_rtmp_core_module.c       \  
                $ngx_addon_dir/ngx_rtmp_cmd_module.c        \  
                $ngx_addon_dir/ngx_rtmp_codec_module.c      \  
                $ngx_addon_dir/ngx_rtmp_access_module.c     \  
                $ngx_addon_dir/ngx_rtmp_record_module.c     \  
                $ngx_addon_dir/ngx_rtmp_live_module.c       \  
                $ngx_addon_dir/ngx_rtmp_play_module.c       \  
                $ngx_addon_dir/ngx_rtmp_flv_module.c        \  
                $ngx_addon_dir/ngx_rtmp_mp4_module.c        \  
                $ngx_addon_dir/ngx_rtmp_netcall_module.c    \  
                $ngx_addon_dir/ngx_rtmp_relay_module.c      \  
                $ngx_addon_dir/ngx_rtmp_bandwidth.c         \  
                $ngx_addon_dir/ngx_rtmp_exec_module.c       \  
                $ngx_addon_dir/ngx_rtmp_auto_push_module.c  \  
                $ngx_addon_dir/ngx_rtmp_notify_module.c     \  
                $ngx_addon_dir/ngx_rtmp_log_module.c        \  
                $ngx_addon_dir/ngx_rtmp_limit_module.c      \  
                $ngx_addon_dir/ngx_rtmp_bitop.c             \  
                $ngx_addon_dir/ngx_rtmp_proxy_protocol.c    \  
                $ngx_addon_dir/hls/ngx_rtmp_hls_module.c    \  
                $ngx_addon_dir/dash/ngx_rtmp_dash_module.c  \  
                $ngx_addon_dir/hls/ngx_rtmp_mpegts.c        \  
                $ngx_addon_dir/dash/ngx_rtmp_mp4.c          \  
                "  
RTMP_HTTP_SRCS="$ngx_addon_dir/ngx_rtmp_stat_module.c       \  
                $ngx_addon_dir/ngx_rtmp_control_module.c    \  
                "  
  
#nginx version >= 1.9.11  
if test -n "$ngx_module_link"; then  
    ngx_module_incs=$ngx_addon_dir  
    ngx_module_deps=$RTMP_DEPS  
    if [ $ngx_module_link = DYNAMIC ] ; then  
        ngx_module_name="$RTMP_CORE_MODULES $RTMP_HTTP_MODULES"  
        ngx_module_srcs="$RTMP_CORE_SRCS $RTMP_HTTP_SRCS"  
        . auto/module  
    elif [ $ngx_module_link = ADDON ] ; then  
        ngx_module_type=CORE  
        ngx_module_name=$RTMP_CORE_MODULES  
        ngx_module_srcs=$RTMP_CORE_SRCS  
        . auto/module  
        ngx_module_type=HTTP  
        ngx_module_name=$RTMP_HTTP_MODULES  
        ngx_module_srcs=$RTMP_HTTP_SRCS  
        . auto/module  
    fi  
  
#nginx version < 1.9.11  
else  
    CORE_MODULES="$CORE_MODULES  
                  $RTMP_CORE_MODULES"  
    HTTP_MODULES="$HTTP_MODULES  
                  $RTMP_HTTP_MODULES"  
    NGX_ADDON_DEPS="$NGX_ADDON_DEPS \  
                    $RTMP_DEPS"  
    NGX_ADDON_SRCS="$NGX_ADDON_SRCS  
                    $RTMP_CORE_SRCS  
                    $RTMP_HTTP_SRCS"  
    CFLAGS="$CFLAGS -I$ngx_addon_dir"  
fi  
  
USE_OPENSSL=YES  

然后调整所有 c 源文件里使用 ngx_modules 的地方,可以添加类似如下的代码片段:

#if defined(nginx_version) && nginx_version >= 1009011  
    modules = cf->cycle->modules;  
#else  
    modules = ngx_modules;  
#endif  

这样就可以达到我们的目的了。

具体关于编译脚本的调整,我们参考一下官方的几个文档即可:

注意,上面的文档中,关于 rtmp 模块的调整,是有问题的,一方面如果那些使用了 ngx_modules 全局变量的 c 文件不相应修改的话,运行时会有段错误;另一方面,示例里对 config 文件的调整,只能保证对于 Nginx-1.9.11 及其以后的版本能同时支持编译为动态模块和静态模块,却对 Nginx-1.9.11 之前的版本的支持有问题。