Nginx基礎. HTTP過濾模塊添加方式探討

在學習Nginx中的過濾模塊開發時, 遇到了使用全局變量和靜態全局變量構成的單向函數鏈表, 對於我這種新手來說, 有些無法理解.

首先, 關於全局變量與靜態全局變量, 下面貼出一些注意的地方:

從分配內存空間看:
          全局變量、靜態局部變量、靜態全局變量都在靜態存儲區分配空間,而局部變量在棧分配空間。
區別:
          全局變量本身就是靜態存儲方式,靜態全局變量當然也是靜態存儲方式。這兩者在存儲方式上沒有什麼不同。區別在於非靜態全局變量的作用域是整個源程序,當一個源程序由多個源文件組成時,非靜態的全局變量在各個源文件中都是有效的。而靜態全局變量則限制了其作用域,即只在定義該變量的源文件內有效,在同一源程序的其他源文件中不能使用它。由於靜態全局變量的作用域侷限於一個源文件內,只能爲該源文件內的函數公用,因此可以避免在其他源文件中引起錯誤。


所以, 自己按照Nginx中構建鏈表的方式也寫了一個小小的實驗程序如下.

整個框架目的:

目的是爲了能實現一系列相同類型的函數(即可以以同用一個函數指針指向的衆多函數)

如果想要添加能執行的函數(這裏我們稱爲模塊), 只需要編寫新的文件而不需要改動原有的代碼


框架結構體:

首先, 這是頭文件. 用於制定每個模塊需要的一些信息.

只是這裏因爲程序太小太簡陋, 所以不需要什麼信息. 只需要每個模塊的初始化函數. 初始化函數的用途會在下面講

//from  "conf.h"
#include <stdio.h>

#ifndef CONF_H
#define CONF_H

//形成這個函數鏈表的每個(函數)元素的類型
typedef int (*function_init)();
//每個模塊真正工作的函數的指針
typedef void (*function_job)();

typedef struct{//每個模塊結構體當前只包含必須的初始化函數
    function_init init;
}module_t;

#endif


框架自帶的模塊.
//from "module_A.c"
#include "conf.h"

//這個function_top_job函數是整個框架用來遍歷模塊函數的入口, 換句話說, 就是函數鏈表的入口
extern function_job function_top_job;
//這個必須是靜態全局變量, 才能讓每個模塊具有相同的變量名字但指向不同的函數
static function_job function_next_job;

static void module_A_job();
static int module_A_init();

//對於模塊A的定義.
module_t module_A = {
    module_A_init
};
//模塊A的真正工作函數, 其中要包含其調用下一個函數元素的代碼
static void
module_A_job(){
    printf("I am from module A\n");
        //看是否執行到鏈表尾部. 如果還有則繼續執行
    if(function_next_job)
        function_next_job();
}

//模塊A的初始化函數. 從這裏明顯可以看出和Nginx中相同的用法
//將每個模塊插入在鏈表頭部, 所以後初始化的函數會被優先執行
static int
module_A_init(){
        //靜態變量指向原來的整個框架的鏈表首部
    function_next_job = function_top_job;
        //整個框架的鏈表首部被重新賦值
    function_top_job = module_A_job;
}


模塊整合
我們知道, 每個模塊都需要執行它的初始化函數之後纔會被放到鏈表函數中去, 那麼我們不可能在每次添加新模塊的時候都
到main函數中去添加新模塊的初始化函數調用代碼, 所以就需要使用下面這個數組
在Nginx中, 類似的文件叫做 /nginx/obj/ngx_modules.c, 這個文件是由configure文件生成的. 因爲我還不會編寫這個東西...所以每次添加模塊的話, 還
需要到這裏來做一些小小的修改. 不過這個是單獨的文件, 所以改起來比較清晰方便.
//from "modules.c"
#include "conf.h"

//其他文件的全局變量
extern module_t module_A;

//將其他文件的模塊結構體放到這個模塊數組中來
module_t *modules[] = {
    &module_A,
    NULL            //放一個NULL在最後也就方便遍歷, 不需要知道到底有多少元素在這個modules數組中
};



最後, 整個框架需要一個main函數:
//from "main.c"
#include "conf.h"

//定義框架的函數鏈表入口
function_job function_top_job = NULL;

//所有模塊都被放在這個模塊指針數組中
extern module_t *modules[];

int main()
{
    int i;
        //執行每個模塊的初始化函數. 形成函數鏈表
    for(i=0; modules[i]; i++)
        modules[i]->init();
    
        //從入口開始執行, 遍歷每個函數元素
    function_top_job();
    return 0;
}


編譯執行:
好了, 基本的一個框架已經完成了. 如果在編譯後執行, 那麼輸出的內容就是:
$ gcc -o exe conf.h module_A.c modules.c main.c
$ ./exe
I am from module A


添加新模塊
現在來添加新的模塊. 添加新的模塊在原本的設計下也就顯得十分簡單了.

比如, 現在想要添加模塊B和模塊C

//from "module_B.c"
#include "conf.h"

extern function_job function_top_job;
static function_job function_next_job;

static void module_B_job();
static int module_B_init();

module_t module_B = {
    module_B_init
};

static void
module_B_job(){
    printf("I am from module B\n");
    if(function_next_job)
        function_next_job();
}

static int
module_B_init(){
    function_next_job = function_top_job;
    function_top_job = module_B_job;
}


//from "module_C.c"
#include "conf.h"

extern function_job function_top_job;
static function_job function_next_job;

static void module_C_job();
static int module_C_init();

module_t module_C = {
    module_C_init
};

static void
module_C_job(){
    printf("I am from module C\n");
    if(function_next_job)
        function_next_job();
}

static int
module_C_init(){
    function_next_job = function_top_job;
    function_top_job = module_C_job;
}


除了添加這兩個源文件外, 還需要去modules.c文件中進行簡單的修改, 改成下面這個樣子:
#include "conf.h"

extern module_t module_A;
extern module_t module_B;
extern module_t module_C;

module_t *modules[] = {
    &module_A,
    &module_B,
    &module_C,
    NULL
};



最後編譯執行:
$ gcc -o exe conf.h module_A.c modules.c main.c module_B.c module_C.c 
$ ./exe
I am from module C
I am from module B
I am from module A

可以發現, 函數的執行順序與插入操作是相符合的.
好了, 可以發現, 添加的東西與原來模塊的格式相同, 這裏內容雖然也差不多, 但完全可以改成完全不同的操作. 只是格式必須要遵守.
另外需要修改的modules.c 也不是很困難...
發佈了174 篇原創文章 · 獲贊 22 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章