nginx源碼初讀(9)--讓煩惱從數據結構開始(ngx_listening/ngx_connection)

在nginx中connection就是對tcp連接的封裝,其中包括連接的socket,讀事件,寫事件。利用nginx封裝的connection,我們可以很方便的使用nginx來處理與連接相關的事情,比如,建立連接,發送與接受數據等。而nginx中的http請求的處理就是建立在connection之上的,所以nginx不僅可以作爲一個web服務器,也可以作爲郵件服務器。當然,利用nginx提供的connection,我們可以與任何後端服務打交道。

結合一個tcp連接的生命週期,我們看看nginx是如何處理一個連接的。首先,nginx在啓動時,會解析配置文件,得到需要監聽的端口與ip地址,然後在nginx的master進程裏面,先初始化好這個監控的socket(創建socket,設置addrreuse等選項,綁定到指定的ip地址端口,再listen),然後再fork出多個子進程出來,然後子進程會競爭accept新的連接。此時,客戶端就可以向nginx發起連接了。當客戶端與服務端通過三次握手建立好一個連接後,nginx的某一個子進程會accept成功,得到這個建立好的連接的socket,然後創建nginx對連接的封裝,即ngx_connection_t結構體。接着,設置讀寫事件處理函數並添加讀寫事件來與客戶端進行數據的交換。最後,nginx或客戶端來主動關掉連接,到此,一個連接就壽終正寢了。

當然,nginx也是可以作爲客戶端來請求其它server的數據的(如upstream模塊),此時,與其它server創建的連接,也封裝在ngx_connection_t中。作爲客戶端,nginx先獲取一個ngx_connection_t結構體,然後創建socket,並設置socket的屬性( 比如非阻塞)。然後再通過添加讀寫事件,調用connect/read/write來調用連接,最後關掉連接,並釋放ngx_connection_t。


第一點:ngx_listening
在瞭解connection之前,我們可以先看一下ngx_listening,他保存了跟監聽有關的信息:

typedef struct ngx_listening_s  ngx_listening_t;
struct ngx_listening_s {
    ngx_socket_t        fd;     
    /* socket套接字句柄
     * ... getsockname(ls[i].fd, ls[i].sockaddr, &ls[i].socklen) == -1; ...
     * ... bind(s, ls[i].sockaddr, ls[i].socklen); 
           listen(s, ls[i].backlog);
           ls[i].fd = s;  ...
     */  

    struct sockaddr    *sockaddr;              // 監聽sockaddr地址
    socklen_t           socklen;               // size of sockaddr
    size_t              addr_text_max_len;     // 存儲ip字符串的addr_text的最大長度
    ngx_str_t           addr_text;             // 以字符串形式存儲的ip地址
    /* ... ngx_create_listening中的一部分代碼:
           sa = ngx_palloc(cf->pool, socklen);    // 給sockaddr分配地址空間
           if (sa == NULL) {
               return NULL;
           }
           ngx_memcpy(sa, sockaddr, socklen);     // 複製地址到sockaddr中

           ls->sockaddr = sa;
           ls->socklen = socklen;

           // 把ip轉爲字符串格式並存儲再addr_text中,並根據具體使用情況獲得max的值
           len = ngx_sock_ntop(sa, socklen, text, NGX_SOCKADDR_STRLEN, 1);
           ls->addr_text.len = len;
           switch (ls->sockaddr->sa_family) {
       #if (NGX_HAVE_INET6)
               case AF_INET6:
                   ls->addr_text_max_len = NGX_INET6_ADDRSTRLEN;
                   break;
               ...    
           }       
           ls->addr_text.data = ngx_pnalloc(cf->pool, len);
           if (ls->addr_text.data == NULL) {
               return NULL;
           }
           ngx_memcpy(ls->addr_text.data, text, len); ...
     */      

    int                 type;                 
    /* 套接字類型。types是SOCK_STREAM時,表示是tcp。與linux的一致。*/

    int                 backlog;
    /* TCP監聽時的backlog隊列,它表示系統所允許的已通過三次握手建立好的tcp連接、但還沒有任何進程開始處理的最大數 */

    int                 rcvbuf; 
    int                 sndbuf;
    /* socket接收/發送緩衝區,是在http模塊中add_listening調用create後(create爲-1),單獨處理給的一塊內存區 */

#if (NGX_HAVE_KEEPALIVE_TUNABLE)
    int                 keepidle;
    int                 keepintvl;
    int                 keepcnt;
#endif

    ngx_connection_handler_pt   handler;
    /* handler of accepted connection(對新建立連接的處理方法)  
     * ... ls->handler = ngx_http_init_connection; ...
     */

    void               *servers;  
    /* array of ngx_http_in_addr_t, for example
     * 當前主要用於HTTP或mail等模塊,用於保存當前監聽端口對應的所有主機名 */

    ngx_log_t           log;
    ngx_log_t          *logp;
    /* 看了看源碼,初步分析log是自身帶的一個日誌,logp指向相應業務core模塊的日誌 */

    size_t              pool_size;
    /* 要爲新的tcp連接建立內存池的話,內存池的大小爲pool_size  
     * ... c->pool = ngx_create_pool(ls->pool_size, ev->log); ...
     */

    size_t              post_accept_buffer_size;
    /* 服務器可以接收的頭部buffer大小?超過了就不要啦~~
     * ls->post_accept_buffer_size = cscf->client_header_buffer_size;
     */ 

    ngx_msec_t          post_accept_timeout;
    /* should be here because of the deferred accept
     * TCP連接建立後,在post_accept_timeout之後仍然沒有收到用戶數據,則內核直接丟棄連接
     * ... if (!rev->timer_set) {    
               // 加入定時紅黑樹,處理超時時間
               ngx_add_timer(rev, c->listening->post_accept_timeout);
           } ...
     */      

    ngx_listening_t    *previous;
    /* 指向前一個listening結構,都統統連起來 */

    ngx_connection_t   *connection;
    /* 當前監聽句柄對應的connection結構 ...  c = ls[i].connection; ...*/

    unsigned            open:1;
    /* 1:當前監聽句柄有效   0:正常關閉 */

    unsigned            remain:1; 
    /* 用已有的ngx_cycle_t來初始化ngx_cycle_t結構體時,不關閉監聽端口,對於運行中升級有用 */

    unsigned            ignore:1;
    /* 1:跳過設置當前ngx_listening_t結構體中的套接字    0:正常初始化 
     * ... for (i = 0; i < cycle->listening.nelts; i++) {
               if (ls[i].ignore) {
                   continue;
               }
               ...    // 使用setsockopt對socket進行設置
           } ...    
     */

    unsigned            bound:1;       // already bound   已經綁定啦 
    unsigned            inherited:1;   // inherited from previous process  當前監聽句柄從上一個進程來的
    unsigned            nonblocking_accept:1;   // 沒人用這個
    unsigned            listen:1;      // 當前結構體對應的socket已監聽
    unsigned            nonblocking:1;          // 是否阻塞,也沒用。因爲nginx是異步非阻塞的
    unsigned            shared:1;    // shared between threads or processes 沒人用- -,畢竟worker間沒聯繫
    unsigned            addr_ntop:1; // 爲1表示當前ip地址被轉換爲字符串形式,調用create_listening後自行設置

#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
    unsigned            ipv6only:1;
#endif
    unsigned            keepalive:2;

#if (NGX_HAVE_DEFERRED_ACCEPT)
    unsigned            deferred_accept:1;
    unsigned            delete_deferred:1;
    unsigned            add_deferred:1;
#ifdef SO_ACCEPTFILTER
    char               *accept_filter;
#endif
#endif
#if (NGX_HAVE_SETFIB)
    int                 setfib;
#endif

#if (NGX_HAVE_TCP_FASTOPEN)
    int                 fastopen;
#endif
};

跟listening有關的幾個函數:

ngx_listening_t *ngx_create_listening(ngx_conf_t *cf, void *sockaddr,
    socklen_t socklen);                                    // 創建listening_t
ngx_int_t ngx_set_inherited_sockets(ngx_cycle_t *cycle);   // 只有在nginx.c中用了一下,作爲返回值生成方法
ngx_int_t ngx_open_listening_sockets(ngx_cycle_t *cycle);  // cycle_init中使用
void ngx_configure_listening_sockets(ngx_cycle_t *cycle);  // cycle_init中使用
void ngx_close_listening_sockets(ngx_cycle_t *cycle);      // 出錯或者需要退出時調用

第二點:ngx_connection_t
以下就是connection結構體的定義:

struct ngx_connection_s {
    void               *data;
    /* 連接未使用時,data用於充當連接池中空閒鏈表中的next指針。
     * 連接使用時由模塊而定,HTTP中,data指向ngx_http_request_t。*/

    ngx_event_t        *read;
    ngx_event_t        *write;
    /* 連接對應的讀寫事件 */

    ngx_socket_t        fd;
    /* 連接對應的socket */

    ngx_recv_pt         recv;
    ngx_send_pt         send;
    /* 直接接收/發送網絡字節流的方法 */

    ngx_recv_chain_pt   recv_chain;
    ngx_send_chain_pt   send_chain;
    /* 以ngx_chain接收/發送網絡字節流的方法 */

    ngx_listening_t    *listening;
    /* 這個連接對應的ngx_listening_t監聽對象,此連接由listening監聽端口的事件建立 */

    off_t               sent;
    /* 已發送字節數 */

    ngx_log_t          *log;
    /* 對應的日誌對象 */

    ngx_pool_t         *pool;
    /* 在accept一個新連接的時候,會創建一個內存池,而這個連接結束時候,會銷燬一個內存池。  
       這裏所說的連接是成功建立的tcp連接.內存池的大小由pool_size決定。  
       所有的ngx_connect_t結構體都是預分配的。*/

    struct sockaddr    *sockaddr;
    socklen_t           socklen;
    ngx_str_t           addr_text;
    /* 連接客戶端的socket結構體,socket結構體的長度,以字符串表示的ip */

    ngx_str_t           proxy_protocol_addr;
    /* 在1.5.12新增,代理地址,用於http代理模塊 */

#if (NGX_SSL)
    ngx_ssl_connection_t  *ssl;
    /* 根據安裝時的配置,選擇是否建立ssl連接 */
#endif

    struct sockaddr    *local_sockaddr;
    socklen_t           local_socklen;
    /* 本機監聽端口對應的sockaddr結構體,實際上就是listening監聽對象的sockaddr成員 */

    ngx_buf_t          *buffer;
    /* 用戶接受、緩存客戶端發來的字符流,buffer是由連接內存池分配的,大小自由決定 */

    ngx_queue_t         queue;
    /* 將當前連接添加到ngx_cycle_t核心結構體的reuseable_connection_queue雙向鏈表中,表示可以重用 */

    ngx_atomic_uint_t   number;
    /* 連接使用次數。ngx_connection_t每建立一條來自client的連接或主動向server發起連接時,number都會加1 */

    ngx_uint_t          requests;
    /* 處理請求的次數 */

    unsigned            buffered:8;
    /* 緩存中的業務類型,各個模塊有自己的一些定義 */

    unsigned            log_error:3;     
    /* ngx_connection_log_error_e */
    /* 本連接的日誌級別,只定義了5個值,由ngx_connection_log_error_e枚舉表示 */

    unsigned            unexpected_eof:1;       // 不期待字符流結束
    unsigned            timedout:1;             // 連接已超時
    unsigned            error:1;                // 處理過程出現了錯誤
    unsigned            destroyed:1;            // 標識此連接已銷燬,pool/socket等不可用 

    unsigned            idle:1;                 // 連接處於空閒狀態,如keepalive兩次請求中間的狀態
    unsigned            reusable:1;             // 連接可重用,與上面的queue字段對應使用
    unsigned            close:1;                // 連接關閉

    unsigned            sendfile:1;             // 正在發送文件數據
    unsigned            sndlowat:1;
    /* 表示只有連接套接字對應的發送緩衝區必須滿足最低設置的大小閥值時,事件驅動模塊纔會分發該事件。
     * 這與ngx_handle_write_event方法中的lowat參數是對應的 */

    unsigned            tcp_nodelay:2;   /* ngx_connection_tcp_nodelay_e,枚舉定義有3個 */
    unsigned            tcp_nopush:2;    /* ngx_connection_tcp_nopush_e,枚舉定義有3個 */

    unsigned            need_last_buf:1;         // 新加進來的,用得地方比較少,是否需要最後一塊buf?

#if (NGX_HAVE_IOCP)
    unsigned            accept_context_updated:1;
#endif

#if (NGX_HAVE_AIO_SENDFILE)
    unsigned            busy_count:2;
#endif

#if (NGX_THREADS)
    ngx_thread_task_t  *sendfile_task;
#endif
};

我們可以看到connection結構體中包含了與連接有關的各種參數,還有控制連接和信息傳輸的一些規則。梳理完這些東西,下一節就可以開始看漫長的cycle了。

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