/*
Nginx中定義了基本的數據結構ngx_connection_t來表示連接,這個連接表示是客戶端主動發起的、Nginx服務器被動接受的TCP連接,我們可以簡單稱
其為被動連接。同時,在有些請求的處理過程中,Nginx會試圖主動向其他上游服務器建立連接,并以此連接與上游服務器通信,因此,這樣的
連接與ngx_connection_t又是不同的,Nginx定義了}ngx_peer_connection_t結構體來表示主動連接,當然,ngx_peer_connection_t主動連接是
以ngx_connection-t結構體為基礎實現的。本節將說明這兩種連接中各字段的意義,同時需要注意的是,這兩種連接都不可以隨意創建,必須從
連接池中獲取,
*/
/*
在使用連接池時,Nginx也封裝了兩個方法,見表。
如果我們開發的模塊直接使用了連接池,那么就可以用這兩個方法來獲取、釋放ngx_connection_t結構體。
表9-1 連接池的使用方法
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓
┃ 連接池操作方法名 ┃ 參數含義 ┃ 執行意義 ┃
┣━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━┫
┃npc_connection_t *ngx_get_connection ┃ s是這條連接的套接字句柄, ┃ 從連接池中獲取一個ngx_connection_t ┃
┃(ngx_socket_t s, ngx_log_t *log) ┃log則是記錄日志的對象 ┃結構體,同時獲取相應的讀/寫事件 ┃
┣━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━┫
┃void ngx_free_connection ┃ c是需要回收的連接 ┃ 將這個連接回收到連接池中 ┃
┃(ngx_connection_t) ┃ ┃ ┃
┗━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━┛
*/
/*一個ngx_connection_s對應一個ngx_event_s read和一個ngx_event_s write,其中事件的fd是從ngx_connection_s->fd獲取,他們
在ngx_worker_process_init->ngx_event_process_init中關聯起來 */
//ngx_event_t事件和ngx_connection_t連接是處理TCP連接的基礎數據結構, 通過ngx_get_connection從連接池中獲取一個ngx_connection_s結構,
//被動連接(客戶端連接nginx)對應的數據結構是ngx_connection_s,主動連接(nginx連接后端服務器)對應的數據結構是ngx_peer_connection_s
struct ngx_connection_s { //cycle->read_events和cycle->write_events這兩個數組存放的是ngx_event_s,他們是對應的,見ngx_event_process_init
/*
連接未使用時,data成員用于充當連接池中空閑連接鏈表中的next指針(ngx_event_process_init)。當連接被使用時,data的意義由使用它的Nginx模塊而定,
如在HTTP框架中,data指向ngx_http_request_t請求
在服務器端accept客戶端連接成功(ngx_event_accept)后,會通過ngx_get_connection從連接池獲取一個ngx_connection_t結構,也就是每個客戶端連接對于一個ngx_connection_t結構,
并且為其分配一個ngx_http_connection_t結構,ngx_connection_t->data = ngx_http_connection_t,見ngx_http_init_connection
*/
/*
在子請求處理過程中,上層父請求r的data指向第一個r下層的子請求,例如第二層的r->connection->data指向其第三層的第一個
創建的子請求r,c->data = sr見ngx_http_subrequest,在subrequest往客戶端發送數據的時候,只有data指向的節點可以先發送出去
listen過程中,指向原始請求ngx_http_connection_t(ngx_http_init_connection ngx_http_ssl_handshake),接收到客戶端數據后指向ngx_http_request_t(ngx_http_wait_request_handler)
http2協議的過程中,在ngx_http_v2_connection_t(ngx_http_v2_init)
*/
void *data; /* 如果是subrequest,則data最終指向最下層子請求r,見ngx_http_subrequest */
//如果是文件異步i/o中的ngx_event_aio_t,則它來自ngx_event_aio_t->ngx_event_t(只有讀),如果是網絡事件中的event,則為ngx_connection_s中的event(包括讀和寫)
ngx_event_t *read;//連接對應的讀事件 賦值在ngx_event_process_init,空間是從ngx_cycle_t->read_event池子中獲取的
ngx_event_t *write; //連接對應的寫事件 賦值在ngx_event_process_init 一般在ngx_handle_write_event中添加些事件,空間是從ngx_cycle_t->read_event池子中獲取的
ngx_socket_t fd;//套接字句柄
/* 如果啟用了ssl,則發送和接收數據在ngx_ssl_recv ngx_ssl_write ngx_ssl_recv_chain ngx_ssl_send_chain */
//服務端通過ngx_http_wait_request_handler讀取數據
ngx_recv_pt recv; //直接接收網絡字符流的方法 見ngx_event_accept或者ngx_http_upstream_connect 賦值為ngx_os_io 在接收到客戶端連接或者向上游服務器發起連接后賦值
ngx_send_pt send; //直接發送網絡字符流的方法 見ngx_event_accept或者ngx_http_upstream_connect 賦值為ngx_os_io 在接收到客戶端連接或者向上游服務器發起連接后賦值
/* 如果啟用了ssl,則發送和接收數據在ngx_ssl_recv ngx_ssl_write ngx_ssl_recv_chain ngx_ssl_send_chain */
//以ngx_chain_t鏈表為參數來接收網絡字符流的方法 ngx_recv_chain
ngx_recv_chain_pt recv_chain; //賦值見ngx_event_accept ngx_event_pipe_read_upstream中執行
//以ngx_chain_t鏈表為參數來發送網絡字符流的方法 ngx_send_chain
//當http2頭部幀發送的時候,會在ngx_http_v2_header_filter把ngx_http_v2_send_chain.send_chain=ngx_http_v2_send_chain
ngx_send_chain_pt send_chain; //賦值見ngx_event_accept ngx_http_write_filter和ngx_chain_writer中執行
//這個連接對應的ngx_listening_t監聽對象,通過listen配置項配置,此連接由listening監聽端口的事件建立,賦值在ngx_event_process_init
//接收到客戶端連接后會沖連接池分配一個ngx_connection_s結構,其listening成員指向服務器接受該連接的listen信息結構,見ngx_event_accept
ngx_listening_t *listening; //實際上是從cycle->listening.elts中的一個ngx_listening_t
off_t sent;//這個連接上已經發送出去的字節數 //ngx_linux_sendfile_chain和ngx_writev_chain沒發送多少字節就加多少字節
ngx_log_t *log;//可以記錄日志的ngx_log_t對象 其實就是ngx_listening_t中獲取的log //賦值見ngx_event_accept
/*
內存池。一般在accept -個新連接時,會創建一個內存池,而在這個連接結束時會銷毀內存池。注意,這里所說的連接是指成功建立的
TCP連接,所有的ngx_connection_t結構體都是預分配的。這個內存池的大小將由listening監聽對象中的pool_size成員決定
*/
ngx_pool_t *pool; //在accept返回成功后創建poll,見ngx_event_accept, 連接上游服務區的時候在ngx_http_upstream_connect創建
struct sockaddr *sockaddr; //連接客戶端的sockaddr結構體 客戶端的,本端的為下面的local_sockaddr 賦值見ngx_event_accept
socklen_t socklen; //sockaddr結構體的長度 //賦值見ngx_event_accept
ngx_str_t addr_text; //連接客戶端字符串形式的IP地址
ngx_str_t proxy_protocol_addr;
#if (NGX_SSL)
ngx_ssl_connection_t *ssl; //賦值見ngx_ssl_create_connection
#endif
//本機的監聽端口對應的sockaddr結構體,也就是listening監聽對象中的sockaddr成員
struct sockaddr *local_sockaddr; //賦值見ngx_event_accept
socklen_t local_socklen;
/*
用于接收、緩存客戶端發來的字符流,每個事件消費模塊可自由決定從連接池中分配多大的空間給buffer這個接收緩存字段。
例如,在HTTP模塊中,它的大小決定于client_header_buffer_size配置項
*/
ngx_buf_t *buffer; //ngx_http_request_t->header_in指向該緩沖去
/*
該字段用來將當前連接以雙向鏈表元素的形式添加到ngx_cycle_t核心結構體的reusable_connections_queue雙向鏈表中,表示可以重用的連接
*/
ngx_queue_t queue;
/*
連接使用次數。ngx_connection t結構體每次建立一條來自客戶端的連接,或者用于主動向后端服務器發起連接時(ngx_peer_connection_t也使用它),
number都會加l
*/
ngx_atomic_uint_t number; //這個應該是記錄當前連接是整個連接中的第幾個連接,見ngx_event_accept ngx_event_connect_peer
ngx_uint_t requests; //處理的請求次數
/*
緩存中的業務類型。任何事件消費模塊都可以自定義需要的標志位。這個buffered字段有8位,最多可以同時表示8個不同的業務。第三方模
塊在自定義buffered標志位時注意不要與可能使用的模塊定義的標志位沖突。目前openssl模塊定義了一個標志位:
#define NGX_SSL_BUFFERED Ox01
HTTP官方模塊定義了以下標志位:
#define NGX HTTP_LOWLEVEL_BUFFERED 0xf0
#define NGX_HTTP_WRITE_BUFFERED 0x10
#define NGX_HTTP_GZIP_BUFFERED 0x20
#define NGX_HTTP_SSI_BUFFERED 0x01
#define NGX_HTTP_SUB_BUFFERED 0x02
#define NGX_HTTP_COPY_BUFFERED 0x04
#define NGX_HTTP_IMAGE_BUFFERED Ox08
同時,對于HTTP模塊而言,buffered的低4位要慎用,在實際發送響應的ngx_http_write_filter_module過濾模塊中,低4位標志位為1則憊味著
Nginx會一直認為有HTTP模塊還需要處理這個請求,必須等待HTTP模塊將低4位全置為0才會正常結束請求。檢查低4位的宏如下:
#define NGX_LOWLEVEL_BUFFERED OxOf
*/
unsigned buffered:8; //不為0,表示有數據沒有發送完畢,ngx_http_request_t->out中還有未發送的報文
/*
本連接記錄日志時的級別,它占用了3位,取值范圍是0-7,但實際上目前只定義了5個值,由ngx_connection_log_error_e枚舉表示,如下:
typedef enum{
NGXi ERROR—AIERT=0,
NGX' ERROR ERR,
NGX ERROR_INFO,
NGX, ERROR IGNORE ECONNRESET,
NGX ERROR—IGNORE EIb:fVAL
}
ngx_connection_log_error_e ;
*/
unsigned log_error:3; /* ngx_connection_log_error_e */
//標志位,為1時表示不期待字符流結束,目前無意義
unsigned unexpected_eof:1;
//每次處理完一個客戶端請求后,都會ngx_add_timer(rev, c->listening->post_accept_timeout);
/*讀客戶端連接的數據,在ngx_http_init_connection(ngx_connection_t *c)中的ngx_add_timer(rev, c->listening->post_accept_timeout)把讀事件添加到定時器中,如果超時則置1
每次ngx_unix_recv把內核數據讀取完畢后,在重新啟動add epoll,等待新的數據到來,同時會啟動定時器ngx_add_timer(rev, c->listening->post_accept_timeout);
如果在post_accept_timeout這么長事件內沒有數據到來則超時,開始處理關閉TCP流程*/
//當ngx_event_t->timedout置1的時候,該置也同時會置1,參考ngx_http_process_request_line ngx_http_process_request_headers
//在ngx_http_free_request中如果超時則會設置SO_LINGER來減少time_wait狀態
unsigned timedout:1; //標志位,為1時表示連接已經超時,也就是過了post_accept_timeout多少秒還沒有收到客戶端的請求
unsigned error:1; //標志位,為1時表示連接處理過程中出現錯誤
/*
標志位,為1時表示連接已經銷毀。這里的連接指是的TCP連接,而不是ngx_connection t結構體。當destroyed為1時,ngx_connection_t結
構體仍然存在,但其對應的套接字、內存池等已經不可用
*/
unsigned destroyed:1; //ngx_http_close_connection中置1
unsigned idle:1; //為1時表示連接處于空閑狀態,如keepalive請求中麗次請求之間的狀態
unsigned reusable:1; //為1時表示連接可重用,它與上面的queue字段是對應使用的
unsigned close:1; //為1時表示連接關閉
/*
和后端的ngx_connection_t在ngx_event_connect_peer這里置為1,但在ngx_http_upstream_connect中c->sendfile &= r->connection->sendfile;,
和客戶端瀏覽器的ngx_connextion_t的sendfile需要在ngx_http_update_location_config中判斷,因此最終是由是否在configure的時候是否有加
sendfile選項來決定是置1還是置0
*/
//賦值見ngx_http_update_location_config
unsigned sendfile:1; //標志位,為1時表示正在將文件中的數據發往連接的另一端
/*
標志位,如果為1,則表示只有在連接套接字對應的發送緩沖區必須滿足最低設置的大小閱值時,事件驅動模塊才會分發該事件。這與上文
介紹過的ngx_handle_write_event方法中的lowat參數是對應的
*/
unsigned sndlowat:1; //ngx_send_lowat
/*
標志位,表示如何使用TCP的nodelay特性。它的取值范圍是下面這個枚舉類型ngx_connection_tcp_nodelay_e。
typedef enum{
NGX_TCP_NODELAY_UNSET=O,
NGX_TCP_NODELAY_SET,
NGX_TCP_NODELAY_DISABLED
) ngx_connection_tcp_nodelay_e;
*/
unsigned tcp_nodelay:2; /* ngx_connection_tcp_nodelay_e */ //域套接字默認是disable的,
/*
標志位,表示如何使用TCP的nopush特性。它的取值范圍是下面這個枚舉類型ngx_connection_tcp_nopush_e:
typedef enum{
NGX_TCP_NOPUSH_UNSET=0,
NGX_TCP_NOPUSH_SET,
NGX_TCP_NOPUSH_DISABLED
) ngx_connection_tcp_nopush_e
*/ //域套接字默認是disable的,
unsigned tcp_nopush:2; /* ngx_connection_tcp_nopush_e */
unsigned need_last_buf:1;
#if (NGX_HAVE_IOCP)
unsigned accept_context_updated:1;
#endif
/*
#if (NGX HAVE AIO- SENDFILE)
//標志位,為1時表示使用異步I/O的方式將磁盤上文件發送給網絡連接的另一端
unsigned aio一sendfile:l;
//使用異步I/O方式發送的文件,busy_sendfile緩沖區保存待發送文件的信息
ngx_buf_t *busy_sendf ile;
#endif
*/
#if (NGX_HAVE_AIO_SENDFILE)
unsigned busy_count:2;
#endif
#if (NGX_THREADS)
ngx_thread_task_t *sendfile_task;
#endif
};