linux下gcc編程10-clion編譯調試nginx

1。clion簡介

CLion是Jetbrains公司旗下新推出的一款專爲開發C/C++所設計的跨平臺IDE,它是以IntelliJ爲基礎設計的,同時還包含了許多智能功能來提高開發人員的生產力。

同樣支持python哦,相信使用過IntelliJ idea開發過java的盆友都很清楚該IDE的強大,所以做爲Jetbrains旗下的c/c++開發工具CLion同樣包含了許多智能功能來提高開發人員的生產力,提高開發人員的工作效率

1.1 clion安裝

1.1.1 centos

clion內置使用cmake來管理編譯c/c++項目,linux環境可以先按照開發工具包

yum groupinstall Development Tools

window請安裝Cygwin 和MinGW,window安裝教程參考其他博客。
下載你想要任何版本的clion,下載地址
下載tar.gz版本服務器解壓:

tar -zxvf CLion-2019.2.1.tar.gz

假設解壓地址:/soft/clion-2019.2.1,可執行文件位於:/soft/clion-2019.2.1/bin/clion.sh
桌面新建一個快捷方式 ,如桌面新建一個clion.desktop 內容:

[Desktop Entry]
Name=clion
Exec=/soft/clion-2019.2.1/bin/clion.sh
Type=Application
Icon=/soft/clion-2019.2.1/bin/clion.png
Terminal=false

左面會自動生成一個圖片圖標,點擊後自動啓動clion
在這裏插入圖片描述

1.1.1 win10

clion安裝通centos一致,解壓即可使用,linux本身不支持gcc和linux命令需要模擬環境。

1.1.1.1 cygwin安裝

linux支持兩款模擬linux編譯環境,cygwin和mingw,比較兩個差異。
1、從目標上說MinGW 是讓Windows 用戶可以用上GNU 工具,比如GCC。Cygwin 提供完整的類Unix 環境,Windows 用戶不僅可以使用GNU 工具,理論上Linux 上的程序只要用Cygwin 重新編譯,就可以在Windows 上運行。
2、從能力上說如果程序只用到C/C++ 標準庫,可以用MinGW 或Cygwin 編譯。如果程序還用到了POSIX API,則只能用Cygwin 編譯。
3、從依賴上說程序經MinGW 編譯後可以直接在Windows 上面運行。程序經Cygwin 編譯後運行,需要依賴安裝時附帶的cygwin1.dll。

並且cygwin是個完全模擬unix,unix相關庫都可以直接通過管理工具下載安裝,如gcc,pcre,zlib,無需重複編譯。
安裝包下載:https://cygwin.com/install.html,64位管理工具
點擊安裝工具,步驟中選擇163或者aliyun私服
在這裏插入圖片描述
對於新手來說,需安裝Base,Devel,Libs,Net,System,Utils幾個模塊

  • Base中安裝shell,core包,sed,tar,which等模塊
  • Devel中安裝auomake,gcc,git,gdb,make,mingw,其他源碼包等模塊。
  • Net中安裝包含網絡(nfs,ftp,http),openss等模塊。
  • System中安裝系統相關,包括:usb驅動,ext2文件系統,監控等模塊
  • Utils中安裝時間,hash,文件歸檔等幫助相關模塊。

安裝完大概佔用系統空間 18G (注意選擇磁盤空間)。
如果發現某些包忘記安裝,可重新打開管理工具安裝。
安裝完成後,cygwin在桌面上新增了一個 Cygwin64 Terminal工具,用於直接打開shell窗口。
該工具[/根目錄]位於你安裝的cygwin64根目錄,工作目錄:/home/window用戶名。
在這裏插入圖片描述
其他盤符目錄格式爲:

GVT@DESKTOP-V14R68B /
$ cd f:/
GVT@DESKTOP-V14R68B /cygdrive/f

如果需要在clion或者shell中引用其他盤,需要路徑爲:/cygdrive/f (表示f盤)

1.1.1.1 clion配置

clion選擇 File-Setting-Build,Execution,Deployment-Toolchains選擇cygwin-選擇目錄
在這裏插入圖片描述
此時可以新建c語言項目進行開發調試了。

2. nginx開發

2.1 nginx編譯

下載nginx源碼

git clone https://github.com/nginx/nginx.git

nginx使用automake編譯源碼,clion使用cmake編譯源碼,可喜的是最終兩種編譯方式最終都會生成makefile,可以理解技術是相通的,在automake執行時可以讓其生成cmakelist.txt或者將生成的makefile轉換成cmakelist.xt。
完全懂cmake和make和automake技術的同學,轉換不是難事,不懂的就麻煩,不過有用心的同學已經提供了這樣的腳本,這裏就不自己去編寫了,參考:nginx_cmake

2.2 轉換cmake

針對該腳本和clion和cmakelist.txt位置問題,步驟尚未調整
1.將src中的cmake複製進nginx/auto裏邊
修改cmake文件(兩處#********部分)

#!/usr/bin/env bash
#NGX_CMAKE_FILE=$NGX_OBJS/CMakeLists.txt
#********此處生成到項目跟目錄,修改$NGX_OBJS/CMakeLists.txt爲CMakeLists.txt
NGX_CMAKE_FILE=CMakeLists.txt
NGX_CMAKE_TMP=$NGX_OBJS/tmp

#output includes
cmake_ngx_incs=`echo $CORE_INCS $NGX_OBJS $HTTP_INCS $MAIL_INCS\
             | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont\1/g" \
                   -e "s/\//$ngx_regex_dirsep/g"`
cat << END                                  > $NGX_CMAKE_TMP
cmake_minimum_required(VERSION 3.6)
include_directories(
    .
    $cmake_ngx_incs)
END

#output src
cmake_ngx_src="$CORE_SRCS $HTTP_SRCS $MAIL_SRCS $NGX_MISC_SRCS $NGX_ADDON_SRCS $NGX_SHARED_SRCS"

cmake_ngx_src=`echo $cmake_ngx_src | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont\1/g"\
                             -e "s/\//$ngx_regex_dirsep/g"`

#******** 次數將ngx_modules.c修改爲$NGX_OBJS/ngx_modules.c
cat << END                                    >> $NGX_CMAKE_TMP
set(SOURCE_FILES
    $NGX_OBJS/ngx_modules.c
    $cmake_ngx_src)
END

#output target
cat << END                                   >> $NGX_CMAKE_TMP
add_executable(nginx \${SOURCE_FILES})
END


#output lib
echo ${CORE_LIBS}
CMAKE_CORE_LIBS=`echo ${CORE_LIBS} | sed -e "s/-l//g"`

cat << END                                   >> $NGX_CMAKE_TMP
target_link_libraries(nginx $CMAKE_CORE_LIBS)
END

if [ -f $NGX_CMAKE_TMP ]
then
    (cat $NGX_CMAKE_TMP | sed -e "s/\\\//g") > $NGX_CMAKE_FILE
    rm $NGX_CMAKE_TMP
fi

在這裏插入圖片描述
這個ngx_modules.c是nginx編譯後記錄所有模塊的字典文件,nginx就是根據這個文件知道要執行哪些模塊,所有通過./auto/configure --add-module=/root/CLionProjects/nginx/src/http/mymodule的所有模塊都被會生成寫入到這個文件。
2.將nginx/auto/configure中的. auto/make內容上加上. auto/cmake
在這裏插入圖片描述
3.正常編譯Nginx(若沒有指定目錄,則相關文件在objs/下)
clion打開teminal窗口 執行命令:

./auto/configure

nginx默認需要依賴的pcre,zlib等需要安裝devel庫 參考博文
https://blog.csdn.net/liaomin416100569/article/details/72897641
在這裏插入圖片描述
這裏./configure目錄就是檢查依賴關係,最終生成ngx_modules.c,最終編譯還得靠CmakeLists.txt。
ok到這裏編譯基本可以通過了 ,此時可以打開main方法的類:src/core/nginx.c運行了。
點擊修改運行配置將配置文件指向當前項目conf/nginx.conf方便調試:

Program arguments中設置
 -c  /root/CLionProjects/nginx/conf/nginx.conf

在這裏插入圖片描述
此時運行可能會報錯 ,缺少/usr/local/nginx或者/usr/local/nginx/logs目錄,這是因爲你配置時未指定prefix默認啓動的日誌和進程等運行時信息都會寫入這個目錄,新建這兩個目錄即可。

mkdir -p /usr/local/nginx/logs

2.3 nginx調試

經過上述轉換過程基本可以啓動nginx了,並且可通過命令查看到nginx進程了,但是進程在clion中一啓用就自動結束了,但是系統中有進程,細心的就知道了,這是因爲daemon引起的。
nginx.conf中取消daemon:

daemon off;

修改後發現進程雖然不自動關閉,但是關閉clion進程,linux還是存在worker進程,細心的就知道了停止的是master進程,這是master-worker進程模式引起的,此時因爲取消master模式:

master_process off;

此時就可以愉快的在clion中斷點任意調試,方便代碼閱讀和確認。
比如在src/http/ngx_http_connection.c方法ngx_http_process_request中下斷點,所有請求都被被這個函數處理,通過curl訪問:

curl localhost

在這裏插入圖片描述

2.4 nginx模塊開發

關於nginx模塊開發建議參考tengine開發人員編寫的 《nginx開發從入門到精通》,實在是經典阿,4個小時閱讀完成並完成helloworld,感謝。

  1. 首先閱讀下nginx架構和nginx封裝的數據結構
    http://tengine.taobao.org/book/chapter_02.html
  2. 接下來閱讀handler模塊(這篇讀完基本可以開發指令了)
    http://tengine.taobao.org/book/chapter_03.html
    接下來我編寫一個say指令位於location指令下,訪問時輸出say內容.
    src/http/下新增一個mymodule目錄:新建文件ngx_http_helloworld_module.c文件,內容:

/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
/**
 * 定義一個結構體用於接受配置
 */
typedef struct
{
    ngx_str_t hello_string;
}ngx_http_hello_loc_conf_t;

/**
 *
 * @param cf 該參數裏面保存從配置文件讀取到的原始字符串以及相關的一些信息。特別注意的是這個參數的args字段是一個ngx_str_t類型的數組,
 *          該數組的首個元素是這個配置指令本身,第二個元素是指令的第一個參數,第三個元素是第二個參數,依次類推。
 * @param cmd 這個配置指令對應的ngx_command_t結構。
 * @param conf 就是定義的存儲這個配置值的結構體,比如在上面展示的那個ngx_http_hello_loc_conf_t。當解析這個hello_string變量的時候,
 *          傳入的conf就指向一個ngx_http_hello_loc_conf_t類型的變量。用戶在處理的時候可以使用類型轉換,轉換成自己知道的類型,再進行字段的賦值。
 * @return 處理成功時,返回NGX_OK,否則返回NGX_CONF_ERROR或者是一個自定義的錯誤信息的字符串。
 */
static char *ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd,
                                   void *conf);
/**
 * 調用該函數創建本模塊位於location block的配置信息存儲結構。每個在配置中指明的location創建一個。該函數執行成功,返回創建的配置對象。失敗的話,返回NULL。
 * @param cf
 * @return
 */
static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf);

static ngx_int_t
ngx_http_hello_init(ngx_conf_t *cf);

/**
 * say指令默認沒有值使用默認值
 */
static u_char ngx_hello_default_string[] = "Hello, world!";



/**
 * 參數1:定義指令名稱
 * 參數2:指令位置 接受一個參數或者沒有參數
 * 參數3:這是一個函數指針,當nginx在解析配置的時候,如果遇到這個配置指令,將會把讀取到的值傳遞給這個函數進行分解處理。因爲具體每個配置指令的值如何處理,只
 *  有定義這個配置指令的人是最清楚的。來看一下這個函數指針要求的函數原型。
 */
static ngx_command_t ngx_http_hello_commands[] = {
        {
                ngx_string("say"),
                NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
                ngx_http_hello_string,
                NGX_HTTP_LOC_CONF_OFFSET,
                offsetof(ngx_http_hello_loc_conf_t, hello_string),
                NULL },

        ngx_null_command
};

/**
 * nginx加載模塊時生命週期函數定義 ,other :http://tengine.taobao.org/book/chapter_03.html
 * 這是一個ngx_http_module_t類型的靜態變量。這個變量實際上是提供一組回調函數指針,這些函數有在創建存儲配置信息的對象的函數,也有在創建前和創建後會調用的函數。這些函數都將被nginx在合適的時間進行調用。
 *  preconfiguration:
 	    在創建和讀取該模塊的配置信息之前被調用。
    postconfiguration:
        在創建和讀取該模塊的配置信息之後被調用。
 */
static ngx_http_module_t ngx_http_hello_module_ctx = {
        NULL,                          /* preconfiguration */
        ngx_http_hello_init,           /* postconfiguration */

        NULL,                          /* create main configuration */
        NULL,                          /* init main configuration */

        NULL,                          /* create server configuration */
        NULL,                          /* merge server configuration */

        ngx_http_hello_create_loc_conf, /* create location configuration */
        NULL                            /* merge location configuration */
};

ngx_module_t ngx_http_hello_module = {
        NGX_MODULE_V1,
        &ngx_http_hello_module_ctx,    /* module context */
        ngx_http_hello_commands,       /* module directives */
        NGX_HTTP_MODULE,               /* module type */
        NULL,                          /* init master */
        NULL,                          /* init module */
        NULL,                          /* init process */
        NULL,                          /* init thread */
        NULL,                          /* exit thread */
        NULL,                          /* exit process */
        NULL,                          /* exit master */
        NGX_MODULE_V1_PADDING
};


static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_hello_loc_conf_t* local_conf = NULL;
    local_conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
    if (local_conf == NULL)
    {
        return NULL;
    }

    ngx_str_null(&local_conf->hello_string);

    return local_conf;
}
/**
 *
 * @param cf 該參數裏面保存從配置文件讀取到的原始字符串以及相關的一些信息。特別注意的是這個參數的args字段是一個ngx_str_t類型的數組,
 *          該數組的首個元素是這個配置指令本身,第二個元素是指令的第一個參數,第三個元素是第二個參數,依次類推。
 * @param cmd 這個配置指令對應的ngx_command_t結構。
 * @param conf 就是定義的存儲這個配置值的結構體,比如在上面展示的那個ngx_http_hello_loc_conf_t。當解析這個hello_string變量的時候,
 *          傳入的conf就指向一個ngx_http_hello_loc_conf_t類型的變量。用戶在處理的時候可以使用類型轉換,轉換成自己知道的類型,再進行字段的賦值。
 * @return 處理成功時,返回NGX_OK,否則返回NGX_CONF_ERROR或者是一個自定義的錯誤信息的字符串。
 */
static char *
ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{

    ngx_http_hello_loc_conf_t* local_conf;


    local_conf = conf;
    char* rv = ngx_conf_set_str_slot(cf, cmd, conf);

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "hello_string:%s", local_conf->hello_string.data);

    return rv;
}


static ngx_int_t
ngx_http_hello_handler(ngx_http_request_t *r)
{
    ngx_int_t    rc;
    ngx_buf_t   *b;
    ngx_chain_t  out;
    ngx_http_hello_loc_conf_t* my_conf;
    u_char ngx_hello_string[1024] = {0};
    ngx_uint_t content_length = 0;

    ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "ngx_http_hello_handler is called!");

    my_conf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
    if (my_conf->hello_string.len == 0 )
    {
        ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "hello_string is empty!");
        return NGX_DECLINED;
    }
    ngx_sprintf(ngx_hello_string, "%s", my_conf->hello_string.data);

    ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "hello_string:%s", ngx_hello_string);
    content_length = ngx_strlen(ngx_hello_string);

    /* we response to 'GET' and 'HEAD' requests only */
    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
        return NGX_HTTP_NOT_ALLOWED;
    }

    /* discard request body, since we don't need it here */
    rc = ngx_http_discard_request_body(r);

    if (rc != NGX_OK) {
        return rc;
    }

    /* set the 'Content-type' header */
    /*
     *r->headers_out.content_type.len = sizeof("text/html") - 1;
     *r->headers_out.content_type.data = (u_char *)"text/html";
     */
    ngx_str_set(&r->headers_out.content_type, "text/html");


    /* send the header only, if the request type is http 'HEAD' */
    if (r->method == NGX_HTTP_HEAD) {
        r->headers_out.status = NGX_HTTP_OK;
        r->headers_out.content_length_n = content_length;

        return ngx_http_send_header(r);
    }

    /* allocate a buffer for your response body */
    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    /* attach this buffer to the buffer chain */
    out.buf = b;
    out.next = NULL;

    /* adjust the pointers of the buffer */
    b->pos = ngx_hello_string;
    b->last = ngx_hello_string + content_length;
    b->memory = 1;    /* this buffer is in memory */
    b->last_buf = 1;  /* this is the last buffer in the buffer chain */

    /* set the status line */
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = content_length;

    /* send the headers of your response */
    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    /* send the buffer chain of your response */
    return ngx_http_output_filter(r, &out);
}
/**
 * 配置文件讀取完畢後需要去掛載handler
 * 爲了更精細地控制對於客戶端請求的處理過程,nginx把這個處理過程劃分成了11個階段。他們從前到後
 *  NGX_HTTP_POST_READ_PHASE:
 	讀取請求內容階段
    NGX_HTTP_SERVER_REWRITE_PHASE:
        Server請求地址重寫階段
    NGX_HTTP_FIND_CONFIG_PHASE:
        配置查找階段:
    NGX_HTTP_REWRITE_PHASE:
        Location請求地址重寫階段
    NGX_HTTP_POST_REWRITE_PHASE:
        請求地址重寫提交階段
    NGX_HTTP_PREACCESS_PHASE:
        訪問權限檢查準備階段
    NGX_HTTP_ACCESS_PHASE:
        訪問權限檢查階段
    NGX_HTTP_POST_ACCESS_PHASE:
        訪問權限檢查提交階段
    NGX_HTTP_TRY_FILES_PHASE:
        配置項try_files處理階段
    NGX_HTTP_CONTENT_PHASE:
        內容產生階段
    NGX_HTTP_LOG_PHASE:
        日誌模塊處理階段
 * 一般情況下,我們自定義的模塊,大多數是掛載在NGX_HTTP_CONTENT_PHASE階段的。掛載的動作一般是在模塊上下文調用的postconfiguration函數中。
 * @param cf
 * @return
 */
static ngx_int_t
ngx_http_hello_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_hello_handler;

    return NGX_OK;
}

源代碼同目錄新增config文件

ngx_addon_name=ngx_http_hello_module
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_helloworld_module.c"

重新configure生成新ngx_module.c

./auto/configure --add-module=/root/CLionProjects/nginx/src/http/mymodule

在這裏插入圖片描述
conf/nginx.conf中添加say:helloworld

       location / {
            say   "helloworld";
            root   html;
            index  index.html index.htm;
        }

在這裏插入圖片描述
最終打印:helloworld

3. window下nginx開發

3.1 nginx編譯

如果直接使用linux下編譯的CMakeLists.txt,則會出現

[ 10%] Building C object CMakeFiles/nginx.dir/src/os/unix/ngx_linux_init.c.o    
/cygdrive/f/code/c/nginx/src/os/unix/ngx_linux_init.c: In function 'ngx_os_specific_init':
/cygdrive/f/code/c/nginx/src/os/unix/ngx_linux_init.c:36:21: error: storage size of 'u' isn't known
   36 |     struct utsname  u;
      |                     ^
/cygdrive/f/code/c/nginx/src/os/unix/ngx_linux_init.c:38:9: warning: implicit declaration of function 'uname'; did you mean 'rename'? [-Wimplicit-function-declaration]
   38 |     if (uname(&u) == -1) {
      |         ^~~~~
      |         rename
make[3]: *** [CMakeFiles/nginx.dir/build.make:908: CMakeFiles/nginx.dir/src/os/unix/ngx_linux_init.c.o] Error 1

實際上是因爲cmakelist.txt文件以下三行引起:

	src/os/unix/ngx_linux_init.c 
	src/event/modules/ngx_epoll_module.c 
	src/os/unix/ngx_linux_sendfile_chain.c 

替換成:

	event/modules/ngx_select_module.c
	event/modules/ngx_poll_module.c

如果不想替換,可以直接使用之間的2.2轉換cmake後,使用cygwin直接編譯生成的cmakelist.txt自然就是我所講的沒換內容。
打開Cygwin64 Terminal

 
GVT@DESKTOP-V14R68B /cygdrive/f/code/c/nginx
$ pwd
/cygdrive/f/code/c/nginx

GVT@DESKTOP-V14R68B /cygdrive/f/code/c/nginx
$ ./auto/configure

在這裏插入圖片描述
同nginx設置當前項目nginx.conf,比如代碼在f:/code/c/nginx/conf/nginx.conf,指定必須按照cygwin路徑來指定: -c /cygdrive/f/code/c/nginx/conf/nginx.conf。
在這裏插入圖片描述
打開Cygwin64 Terminal,創建目錄/usr/local/nginx/logs。
接下來直接運行,報錯:

F:\code\c\nginx\cmake-build-debug\nginx.exe -c /cygdrive/f/code/c/nginx/conf/nginx.conf
nginx: [emerg] the maximum number of files supported by select() is 64

通過搜索源碼:the maximum number of files 發現416行邏輯,調試下斷點:
在這裏插入圖片描述
發現nginx.conf配置的連接數不能大於FD_SETSIZE對應的64,將

events {
    worker_connections  1024;
}

修改爲小於64將不會出現這個問題。

3.1 手工添加handler

由於開發一個handler,configure在window的速度太慢所有直接手工修改ngx_modules.c和CmakeLists.txt。

  • ngx_modules.c 用於指定所有存在的模塊信息(這個必須要configure一次纔有)。
  • CmakeLists.txt 需要應用模塊信息對應的c文件。

ngx_modules.c修改:

  1. 添加模塊外部定義應用。
extern ngx_module_t  ngx_http_hello_module;
  1. 添加到nginx模塊數組,將來nginx直接從這拿。
ngx_modules[]={
 。。。
 &ngx_http_hello_module,
 NULL
}
  1. 添加到nginx模塊名稱數組,將來nginx直接從這拿。
char *ngx_module_names[] = {
 。。。
 "ngx_http_hello_module",
 NULL
}
  1. nginx.conf配置指令say
        location / {
            say   "helloworld";
            root   html;
            index  index.html index.htm;
        }

啓動:curl localhost,打印:helloworld。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章