在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了。