listen源碼分析第一篇 address:port分析

微信公眾號:鄭爾多斯
關注可了解更多的Nginx知識矾睦。任何問題或建議勾栗,請公眾號留言;
關注公眾號苍苞,有趣有內涵的文章第一時間送達狰晚!

前言

本篇文章詳細介紹一下listen指令的解析筒饰,以及socket的創(chuàng)建過程。
listen的內容太多了壁晒,并且牽涉到后面的很多地方瓷们,所以只能再一次的回到這里仔細的學習listen指令的解析過程。首先要參考listennginx官方文檔秒咐,知道listen的用法谬晕,然后才能學習源碼。

listen配置

我們先從源文件中找到listen的配置項携取,如下:

{ 
      ngx_string("listen"),
      NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
      ngx_http_core_listen,
      NGX_HTTP_SRV_CONF_OFFSET,
      0,
      NULL 
}

我們從配置文件中可以看到攒钳,listen指令的解析函數為ngx_http_core_listen,我們下面分析一下這個函數雷滋。

使用到的結構體

ngx_url_t
typedef struct {
    union {
        struct sockaddr        sockaddr;
        struct sockaddr_in     sockaddr_in;
#if (NGX_HAVE_INET6)
        struct sockaddr_in6    sockaddr_in6;
#endif
#if (NGX_HAVE_UNIX_DOMAIN)
        struct sockaddr_un     sockaddr_un;
#endif
        u_char                 sockaddr_data[NGX_SOCKADDRLEN];
    } u;

    socklen_t                  socklen;

    unsigned                   set:1;
    unsigned                   default_server:1;
    unsigned                   bind:1;
    unsigned                   wildcard:1;
#if (NGX_HTTP_SSL)
    unsigned                   ssl:1;
#endif
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
    unsigned                   ipv6only:2;
#endif

    int                        backlog;
    int                        rcvbuf;
    int                        sndbuf;
#if (NGX_HAVE_SETFIB)
    int                        setfib;
#endif

#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
    char                      *accept_filter;
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
    ngx_uint_t                 deferred_accept;
#endif

    u_char                     addr[NGX_SOCKADDR_STRLEN + 1];
} ngx_http_listen_opt_t;

上面的幾個數據結構是我們分析listen指令時常用到的不撑,這里簡單的說明一下他們的作用。

  • ngx_url_t結構體是用來保存解析address:port的內容惊豺。
  • ngx_http_listen_opt_t結構體是用來保存listen指令后面所配置的選項燎孟。為后面創(chuàng)建socket做準備禽作。

解析地址

我們閱讀ngx_http_core_listen的源碼就會發(fā)現尸昧,解析listen指令的第一步是調用ngx_parse_url()來解析address:port部分。這其實是ngx_http_core_listen函數最重要的一部分旷偿,我們先來分析一下這個函數烹俗。
首先爆侣,我們必須要知道listen指令配置的address:port的格式,我們這里只分析ipv4格式幢妄,我們從nginx文檔中可以查到:

Sets the address and port for IP, or the path for a UNIX-domain socket on which the server will accept requests. Both address and port, or only address or only port can be specified. An address may also be a hostname, for example:

listen 127.0.0.1:8000;
listen 127.0.0.1;
listen 8000;
listen *:8000;
listen localhost:8000;

If only address is given, the port 80 is used.

If the directive is not present then either *:80 is used if nginx runs with the superuser privileges, or *:8000 otherwise.

通過上面的文檔我們可以知道兔仰,address:port可以有一下幾種格式:

listen   8080
listen   *:8080
listen    127.0.0.1:8080
listen     localhost:8080
listen   127.0.0.1;

如果我們沒有指定port,那么默認是80端口蕉鸳。
如果我們在server中沒有配置listen指令乎赴,那么會有兩種情況:

  • 當前啟動nginx的是普通用戶,那么默認為 *:80
  • 當前啟動nginx的是root用戶潮尝,那么默認為 *:8000

那么nginx是如何解析address:port的呢榕吼?這就是下面的ngx_parse_url()函數的功能了。

    u.url = value[1];
    u.listen = 1;
    u.default_port = 80;

    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
        if (u.err) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
              "%s in \"%V\" of the \"listen\" directive",
                               u.err, &u.url);
        }
        return NGX_CONF_ERROR;
    }

在調用ngx_parse_url()函數之前勉失,會先進行一些簡單的賦值羹蚣,

    // address:port部分
    u.url = value[1];
    // 表示當前server顯式的設置了listen指令
    u.listen = 1; 
    // 設置一個默認的端口號
    u.default_port = 80;

由于我們使用的是ipv4地址,所以最終調用的是ngx_parse_inet_url()乱凿,如下:

static ngx_int_t
ngx_parse_inet_url(ngx_pool_t *pool, ngx_url_t *u)
{
    u_char              *p, *host, *port, *last, *uri, *args;
    size_t               len;
    ngx_int_t            n;
    struct hostent      *h;
    struct sockaddr_in  *sin;

    u->socklen = sizeof(struct sockaddr_in);
    sin = (struct sockaddr_in *) &u->sockaddr;
    sin->sin_family = AF_INET;
    u->family = AF_INET;
    host = u->url.data;
    last = host + u->url.len;
    
    port = ngx_strlchr(host, last, ':');// 判斷是否有端口號
    uri = ngx_strlchr(host, last, '/');// 判斷是否有path
    args = ngx_strlchr(host, last, '?');// 判斷是否有QueryString
    
    if (args) {
        /*我們的listen指令后面的address:port沒有args顽素,不會執(zhí)行這里*/
    }
    if (uri) {
        /*我們的listen指令后面的address:port沒有uri,不會執(zhí)行這里*/
    }
    if (port) {//如果有端口號
        port++;//port向后移動一位徒蟆,表示從此位置開始是端口號
        len = last - port;//端口號的長度
        n = ngx_atoi(port, len);// 把端口號轉換為數字
        u->port = (in_port_t) n;
        sin->sin_port = htons((in_port_t) n);
        u->port_text.len = len;
        u->port_text.data = port;
        last = port - 1;// 此時last指向了address的最后
    } else {
    // 如果我們沒有冒號,這時候有兩種情況胁出,
// ① 我們沒有指定端口號,如 listen 127.0.0.1
// ② 我們指定了端口號段审,但是沒有指定address划鸽,如 listen  8080
        if (uri == NULL) {
        // 我們在server中顯式的使用了listen指令
            if (u->listen) {
                /* test value as port only */
                // 這句話注釋的很明顯,nginx首先將它作為一個port
                // 進行轉換戚哎,如果成功裸诽,那么就認為這是一個port
                n = ngx_atoi(host, last - host);
                if (n != NGX_ERROR) {
//對于上面的第①種情況,由于無法將 127.0.0.1 
// 轉換為一個正確的端口號型凳,
// 所以就不會執(zhí)行下面的if語句丈冬,而是執(zhí)行
// u->noport = 1 , 表示我們沒有指定端口號
                    u->port = (in_port_t) n;
                    sin->sin_port = htons((in_port_t) n);
                    u->port_text.len = last - host;
                    u->port_text.data = host;
                    u->wildcard = 1;
                    return NGX_OK;
                }
            }
        }  
    // 對于上述的第①種情況,會執(zhí)行到這里甘畅,表示我們沒有指定端口號
        u->no_port = 1;
    }
// 如果執(zhí)行到這里埂蕊,說明listen后面沒有端口號,只有address
// len 表示address的長度疏唾,
// 比如 127.0.0.1 或者  localhost的長度
    len = last - host;
    if (len == 0) {
        u->err = "no host";
        return NGX_ERROR;
    }
    if (len == 1 && *host == '*') {
        len = 0;
    }

    u->host.len = len;
    u->host.data = host;
    if (u->no_resolve) {
        return NGX_OK;
    }

    if (len) {
    // 對于 listen * 的情況蓄氧,上面的代碼會把len設置為0,所以不會執(zhí)行這里
// 這里會首先嘗試把address轉換為ip形式槐脏,如果轉換不成功喉童,
// 那么就會調用gethostbyname()進行DNS地址解析
// 比如 127.0.0.1這種形式就可以通過 ngx_inet_addr()進行轉換,
// 這時就不會調用gethostbyname()進行DNS解析
// 但是對于 localhost 這種情況顿天,只能進行DNS地址解析
        sin->sin_addr.s_addr = ngx_inet_addr(host, len);

        if (sin->sin_addr.s_addr == INADDR_NONE) {
            p = ngx_alloc(++len, pool->log);
            (void) ngx_cpystrn(p, host, len);

            h = gethostbyname((const char *) p);

            ngx_free(p);

            if (h == NULL || h->h_addr_list[0] == NULL) {
                u->err = "host not found";
                return NGX_ERROR;
            }

            sin->sin_addr.s_addr = *(in_addr_t *) (h->h_addr_list[0]);
        }

        if (sin->sin_addr.s_addr == INADDR_ANY) {
            u->wildcard = 1;
        }

    } else {// address和port都忽略的時候
        sin->sin_addr.s_addr = INADDR_ANY;
        u->wildcard = 1;
    }

    if (u->no_port) {
    // 如果沒有指定端口號堂氯,那么會使用默認的80端口
// 從這里也可以看出來蔑担,ngx_url_t 的 default_port 字段就是用來保存默認端口的
// 如果我們沒有指定一個明確的端口號,那么就會使用這個默認的端口咽白,默認是 80
        u->port = u->default_port;
        sin->sin_port = htons(u->default_port);
    }

    if (u->listen) {
        return NGX_OK;
    }
//因為我們的先決條件是 u->listen = 1啤握,所以下面的語句不會被執(zhí)行
    if (ngx_inet_resolve_host(pool, u) != NGX_OK) {
        return NGX_ERROR;
    }

    return NGX_OK;
}

結合圖片以及代碼中的注釋,我們簡單的分析一下nginx對listen指令中 address:port 的解析方法:
address:port 的格式組合格式有好幾種
address: 可以沒有該字段晶框,可以為IP地址排抬,可以為域名
port: 可以不設置該字段,可以為一個固定的端口

所以組合形式就有 3 * 2 = 6中授段。
nginx對于他們的解析有固定的格式畜埋,如下:
如果address沒有設置,那么 u->wildcard = 1畴蒲,表示這時一個通配的地址匹配
如果address設置為一個ip格式悠鞍,那么監(jiān)聽的地址就是這個ip地址
如果address是一個域名格式,那么就會對該域名進行DNS地址解析模燥,獲取監(jiān)聽的IP地址
如果端口號為空咖祭,那么就會使用默認的80端口。

address:port 解析完之后蔫骂,我們可以從 listen 指令的處理函數 ngx_http_core_listen() 中看到么翰,只有 u.sockaddr, u.socklen,以及 u.wildcard 三個字段被ngx_http_listen_opt_t 結構體用到了辽旋,ngx_url_t 的其他字段都是作為 ngx_parse_url() 函數的輔助字段使用浩嫌。我們在后續(xù)的分析過程中,可以結合上面的圖片進行學習

ngx_url_t結構體使用情況

內部布局總結

根據上面的分析补胚,我們對常見的幾種address:port格式的內存布局進行了總結码耐,如下:

listen   8080
listen   *:8080
listen    127.0.0.1:8080
listen     localhost:8080

對應的圖片如下:


圖1
圖2
圖3
圖4

喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯溶其,更多精彩內容第一時間送達


鄭爾多斯
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末骚腥,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子瓶逃,更是在濱河造成了極大的恐慌束铭,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厢绝,死亡現場離奇詭異契沫,居然都是意外死亡,警方通過查閱死者的電腦和手機昔汉,發(fā)現死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門懈万,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事钞速〈矗” “怎么了嫡秕?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵渴语,是天一觀的道長。 經常有香客問我昆咽,道長驾凶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任掷酗,我火速辦了婚禮调违,結果婚禮上,老公的妹妹穿的比我還像新娘泻轰。我一直安慰自己技肩,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布浮声。 她就那樣靜靜地躺著虚婿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泳挥。 梳的紋絲不亂的頭發(fā)上然痊,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音屉符,去河邊找鬼剧浸。 笑死,一個胖子當著我的面吹牛矗钟,可吹牛的內容都是我干的唆香。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼吨艇,長吁一口氣:“原來是場噩夢啊……” “哼袋马!你這毒婦竟也來了?” 一聲冷哼從身側響起秸应,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤虑凛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后软啼,有當地人在樹林里發(fā)現了一具尸體桑谍,經...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年祸挪,在試婚紗的時候發(fā)現自己被綠了锣披。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖雹仿,靈堂內的尸體忽然破棺而出增热,到底是詐尸還是另有隱情,我是刑警寧澤胧辽,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布峻仇,位于F島的核電站,受9級特大地震影響邑商,放射性物質發(fā)生泄漏摄咆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一人断、第九天 我趴在偏房一處隱蔽的房頂上張望吭从。 院中可真熱鬧,春花似錦恶迈、人聲如沸涩金。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽步做。三九已至,卻和暖如春熔吗,著一層夾襖步出監(jiān)牢的瞬間辆床,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工桅狠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留讼载,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓中跌,卻偏偏與公主長得像咨堤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子漩符,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內容