ngx_event.c :這個文件主要放置Nginx事件event模塊的核心代碼。
包含:進程事件分發器(ngx_process_events_and_timers)、事件模塊的模塊和配置、模塊初始化/配置初始化等事件模塊初始化的核心函數。
ngx_event_timer.c:定時器事件管理。主要放置定時器的代碼。
ngx_event_posted.c:主要用於 拿到accept鎖的進程 處理accept和read事件的回調函數。
ngx_event_pipe.c:主要用於處理管道
ngx_event_openssl.c:主要用於處理SSL通道。HTTPS協議
ngx_event_connect.c:主要用於處理TCP的連接通道。
ngx_event_accept.c:核心是ngx_event_accept和ngx_event_recvmsg,主要是處理accept事件的回調函數handler。
而后續的read事件被ngx_event_accept中回調ngx_listen_t結構中的ls->handler回調函數回調,並且將rev->handler修改成ngx_http_wait_request_handler方法。
modules/xxxx.c:主要封裝了各種平台的事件模型。這邊主要看ngx_epoll_module.c模塊。
重要數據結構
下面幾個數據結構會在event模塊中非常常見,必須得非常熟悉。
ngx_listening_s:主要是監聽套接字結構,存放socket的信息;ngx_listening_t結構體代表着Nginx服務器監聽的一個端口;實際上這些ngx_listening_s結構體是從 cycle->listening.elts中來的,見ngx_event_process_init
ngx_connection_s:存儲連接有關的信息和讀寫事件。一個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
ngx_event_s:主要存放事件的數據結構。cycle->read_events和cycle->write_events這兩個數組存放的是ngx_event_s,他們是對應的,見ngx_event_process_init ngx_event_t事件和ngx_connection_t連接是一一對應的
ngx_event_t事件和ngx_connection_t連接是處理TCP連接的基礎數據結構, 在Nginx中,每一個事件都由ngx_event_t結構體來表示
1.ngx_event_s可以是普通的epoll讀寫事件(參考ngx_event_connect_peer->ngx_add_conn或者ngx_add_event),通過讀寫事件觸發
2.也可以是普通定時器事件(參考ngx_cache_manager_process_handler->ngx_add_timer(ngx_event_add_timer)),通過ngx_process_events_and_timers中的
epoll_wait返回,可以是讀寫事件觸發返回,也可能是因為沒獲取到共享鎖,從而等待0.5s返回重新獲取鎖來跟新事件並執行超時事件來跟新事件並且判斷定
時器鏈表中的超時事件,超時則執行從而指向event的handler,然后進一步指向對應r或者u的->write_event_handler read_event_handler
3.也可以是利用定時器expirt實現的讀寫事件(參考ngx_http_set_write_handler->ngx_add_timer(ngx_event_add_timer)),觸發過程見2,只是在handler中不會執行write_event_handler read_event_handler
ngx_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 };
ngx_listening_s:
struct ngx_listening_s { //初始化及賦值見ngx_http_add_listening 熱升級nginx的時候,繼承源master listen fd在ngx_set_inherited_sockets ngx_socket_t fd; //socket套接字句柄 //賦值見ngx_open_listening_sockets struct sockaddr *sockaddr; //監聽sockaddr地址 socklen_t socklen; /* size of sockaddr */ //sockaddr地址長度 //存儲IP地址的字符串addr_text最大長度,即它指定了addr_text所分配的內存大小 size_t addr_text_max_len; // //如果listen 80;或者listen *:80;則該地址為0.0.0.0 ngx_str_t addr_text;//以字符串形式存儲IP地址和端口 例如 A.B.C.D:E 3.3.3.3:23 賦值見ngx_set_inherited_sockets int type;//套接字類型。例如,當type是SOCK_STREAM時,表示TCP //TCP實現監聽時的backlog隊列,它表示允許正在通過三次握手建立TCP連接但還沒有任何進程開始處理的連接最大個數,默認NGX_LISTEN_BACKLOG int backlog; // int rcvbuf;//內核中對於這個套接字的接收緩沖區大小 int sndbuf;//內核中對於這個套接字的發送緩沖區大小 #if (NGX_HAVE_KEEPALIVE_TUNABLE) int keepidle; int keepintvl; int keepcnt; #endif /* handler of accepted connection */ //當新的TCP accept連接成功建立后的處理方法 ngx_connection_handler_pt糞型的handler成員表示在這個監聽端口上成功建立新的TCP連接后,就會回調handler方法 ngx_connection_handler_pt handler; //賦值為ngx_http_init_connection,見ngx_http_add_listening。該handler在ngx_event_accept中執行 /* 實際上框架並不使用servers指針,它更多是作為一個保留指針,目前主要用於HTTP或者mail等模塊,用於保存當前監聽端口對應着的所有主機名 */ void *servers; /* array of ngx_http_in_addr_t ngx_http_port_t, for example */ //賦值見ngx_http_init_listening,指向ngx_http_port_t結構 //log和logp都是可用的日志對象的指針 ngx_log_t log; //見ngx_http_add_listening ngx_log_t *logp; size_t pool_size;//如果為新的TCP連接創建內存池,則內存池的初始大小應該是pool_size 見ngx_http_add_listening /* should be here because of the AcceptEx() preread */ size_t post_accept_buffer_size; /* should be here because of the deferred accept */ /* TCP_DEFER ACCEPT選項將在建立TCP連接成功且接收到用戶的請求數據后,才向對監聽套接字感興趣的進程發送事件通知,而連接建立成功后, 如果post_accept_timeout秒后仍然沒有收到的用戶數據,則內核直接丟棄連接 */ //ls->post_accept_timeout = cscf->client_header_timeout; "client_header_timeout"設置 ngx_msec_t post_accept_timeout; //見ngx_http_add_listening /* 前一個ngx_listening_t結構,多個ngx_listening_t結構體之間由previous指針組成單鏈表 */ ngx_listening_t *previous; //這個表示在熱啟動nginx進程的時候,在最新啟動前的上一個nginx的所有listen信息 //當前監聽句柄對應着的ngx_connection_t結構體 ngx_connection_t *connection; ngx_uint_t worker; //下面這些標志位一般在ngx_init_cycle中初始化賦值 /* 標志位,為1則表示在當前監聽句柄有效,且執行ngx- init—cycle時不關閉監聽端口,為0時則正常關閉。該標志位框架代碼會自動設置 */ unsigned open:1; /* 標志位,為1表示使用已有的ngx_cycle_t來初始化新的ngx_cycle_t結構體時,不關閉原先打開的監聽端口,這對運行中升級程序很有用, remaln為o時,表示正常關閉曾經打開的監聽端口。該標志位框架代碼會自動設置,參見ngx_init_cycle方法 */ unsigned remain:1; /* 標志位,為1時表示跳過設置當前ngx_listening_t結構體中的套接字,為o時正常初始化套接字。該標志位框架代碼會自動設置 */ unsigned ignore:1; //表示是否已經綁定。實際上目前該標志位沒有使用 unsigned bound:1; /* already bound */ /* 表示當前監聽句柄是否來自前一個進程(如升級Nginx程序),如果為1,則表示來自前一個進程。一般會保留之前已經設置好的套接字,不做改變 */ unsigned inherited:1; /* inherited from previous process */ //說明是熱升級過程 unsigned nonblocking_accept:1; //目前未使用 //lsopt.bind = 1;這里面存的是bind為1的配置才會有創建ngx_http_port_t unsigned listen:1; //標志位,為1時表示當前結構體對應的套接字已經監聽 賦值見ngx_open_listening_sockets unsigned nonblocking:1;//表素套接字是否阻塞,目前該標志位沒有意義 unsigned shared:1; /* shared between threads or processes */ //目前該標志位沒有意義 //標志位,為1時表示Nginx會將網絡地址轉變為字符串形式的地址 見addr_text 賦值見ngx_http_add_listening,當在ngx_create_listening把listen的IP地址轉換為字符串地址后置1 unsigned addr_ntop:1; #if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) unsigned ipv6only:1; #endif #if (NGX_HAVE_REUSEPORT) //賦值見ngx_http_add_listening //master進程執行ngx_clone_listening中如果配置了多worker,監聽80端口會有worker個listen賦值,master進程在ngx_open_listening_sockets //中會監聽80端口worker次,那么子進程創建起來后,不是每個字進程都關注這worker多個 listen事件了嗎?為了避免這個問題,nginx通過 //在子進程運行ngx_event_process_init函數的時候,通過ngx_add_event來控制子進程關注的listen,最終實現只關注master進程中創建的一個listen事件 unsigned reuseport:1; unsigned add_reuseport:1; #endif unsigned keepalive:2; #if (NGX_HAVE_DEFERRED_ACCEPT) unsigned deferred_accept:1;//SO_ACCEPTFILTER(freebsd所用)設置 TCP_DEFER_ACCEPT(LINUX系統所用) unsigned delete_deferred:1; unsigned add_deferred:1; //SO_ACCEPTFILTER(freebsd所用)設置 TCP_DEFER_ACCEPT(LINUX系統所用) #ifdef SO_ACCEPTFILTER char *accept_filter; #endif #endif #if (NGX_HAVE_SETFIB) int setfib; #endif #if (NGX_HAVE_TCP_FASTOPEN) int fastopen; #endif };
ngx_event_t:
struct ngx_event_s { /* 事件相關的對象。通常data都是指向ngx_connection_t連接對象,見ngx_get_connection。開啟文件異步I/O時,它可能會指向ngx_event_aio_t(ngx_file_aio_init)結構體 */ void *data; //賦值見ngx_get_connection //標志位,為1時表示事件是可寫的。通常情況下,它表示對應的TCP連接目前狀態是可寫的,也就是連接處於可以發送網絡包的狀態 unsigned write:1; //見ngx_get_connection,可寫事件ev默認為1 讀ev事件應該默認還是0 //標志位,為1時表示為此事件可以建立新的連接。通常情況下,在ngx_cycle_t中的listening動態數組中,每一個監聽對象ngx_listening_t對 //應的讀事件中的accept標志位才會是l ngx_event_process_init中置1 unsigned accept:1; /* 這個標志位用於區分當前事件是否是過期的,它僅僅是給事件驅動模塊使用的,而事件消費模塊可不用關心。為什么需要這個標志位呢? 當開始處理一批事件時,處理前面的事件可能會關閉一些連接,而這些連接有可能影響這批事件中還未處理到的后面的事件。這時, 可通過instance標志位來避免處理后面的已經過期的事件。將詳細描述ngx_epoll_module是如何使用instance標志位區分 過期事件的,這是一個巧妙的設計方法 instance標志位為什么可以判斷事件是否過期?instance標志位的使用其實很簡單,它利用了指針的最后一位一定 是0這一特性。既然最后一位始終都是0,那么不如用來表示instance。這樣,在使用ngx_epoll_add_event方法向epoll中添加事件時,就把epoll_event中 聯合成員data的ptr成員指向ngx_connection_t連接的地址,同時把最后一位置為這個事件的instance標志。而在ngx_epoll_process_events方法中取出指向連接的 ptr地址時,先把最后一位instance取出來,再把ptr還原成正常的地址賦給ngx_connection_t連接。這樣,instance究竟放在何處的問題也就解決了。 那么,過期事件又是怎么回事呢?舉個例子,假設epoll_wait -次返回3個事件,在第 1個事件的處理過程中,由於業務的需要,所以關閉了一個連接,而這個連接恰好對應第3個事件。這樣的話,在處理到第3個事件時,這個事件就 已經是過期辜件了,一旦處理必然出錯。既然如此,把關閉的這個連接的fd套接字置為一1能解決問題嗎?答案是不能處理所有情況。 下面先來看看這種貌似不可能發生的場景到底是怎么發生的:假設第3個事件對應的ngx_connection_t連接中的fd套接字原先是50,處理第1個事件 時把這個連接的套接字關閉了,同時置為一1,並且調用ngx_free_connection將該連接歸還給連接池。在ngx_epoll_process_events方法的循環中開始處 理第2個事件,恰好第2個事件是建立新連接事件,調用ngx_get_connection從連接池中取出的連接非常可能就是剛剛釋放的第3個事件對應的連接。由於套 接字50剛剛被釋放,Linux內核非常有可能把剛剛釋放的套接字50又分配給新建立的連接。因此,在循環中處理第3個事件時,這個事件就是過期的了!它對應 的事件是關閉的連接,而不是新建立的連接。 如何解決這個問題?依靠instance標志位。當調用ngx_get_connection從連接池中獲取一個新連接時,instance標志位就會置反 */ /* used to detect the stale events in kqueue and epoll */ unsigned instance:1; //ngx_get_connection從連接池中獲取一個新連接時,instance標志位就會置反 //見ngx_get_connection /* * the event was passed or would be passed to a kernel; * in aio mode - operation was posted. */ /* 標志位,為1時表示當前事件是活躍的,為0時表示事件是不活躍的。這個狀態對應着事件驅動模塊處理方式的不同。例如,在添加事件、 刪除事件和處理事件時,active標志位的不同都會對應着不同的處理方式。在使用事件時,一般不會直接改變active標志位 */ //ngx_epoll_add_event中也會置1 在調用該函數后,該值一直為1,除非調用ngx_epoll_del_event unsigned active:1; //標記是否已經添加到事件驅動中,避免重復添加 在server端accept成功后, //或者在client端connect的時候把active置1,見ngx_epoll_add_connection。第一次添加epoll_ctl為EPOLL_CTL_ADD,如果再次添加發 //現active為1,則epoll_ctl為EPOLL_CTL_MOD /* 標志位,為1時表示禁用事件,僅在kqueue或者rtsig事件驅動模塊中有效,而對於epoll事件驅動模塊則無意義,這里不再詳述 */ unsigned disabled:1; /* the ready event; in aio mode 0 means that no operation can be posted */ /* 標志位,為1時表示當前事件已經淮備就緒,也就是說,允許這個事件的消費模塊處理這個事件。在 HTTP框架中,經常會檢查事件的ready標志位以確定是否可以接收請求或者發送響應 ready標志位,如果為1,則表示在與客戶端的TCP連接上可以發送數據;如果為0,則表示暫不可發送數據。 */ //如果來自對端的數據內核緩沖區沒有數據(返回NGX_EAGAIN),或者連接斷開置0,見ngx_unix_recv //在發送數據的時候,ngx_unix_send中的時候,如果希望發送1000字節,但是實際上send只返回了500字節(說明內核協議棧緩沖區滿,需要通過epoll再次促發write的時候才能寫),或者鏈接異常,則把ready置0 unsigned ready:1; //在ngx_epoll_process_events中置1,讀事件觸發並讀取數據后ngx_unix_recv中置0 /* 該標志位僅對kqueue,eventport等模塊有意義,而對於Linux上的epoll事件驅動模塊則是無意叉的,限於篇幅,不再詳細說明 */ unsigned oneshot:1; /* aio operation is complete */ //aio on | thread_pool方式下,如果讀取數據完成,則在ngx_epoll_eventfd_handler(aio on)或者ngx_thread_pool_handler(aio thread_pool)中置1 unsigned complete:1; //表示讀取數據完成,通過epoll機制返回獲取 ,見ngx_epoll_eventfd_handler //標志位,為1時表示當前處理的字符流已經結束 例如內核緩沖區沒有數據,你去讀,則會返回0 unsigned eof:1; //見ngx_unix_recv //標志位,為1時表示事件在處理過程中出現錯誤 unsigned error:1; //標志位,為I時表示這個事件已經超時,用以提示事件的消費模塊做超時處理 /*讀客戶端連接的數據,在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流程*/ /* 讀超時是指的讀取對端數據的超時時間,寫超時指的是當數據包很大的時候,write返回NGX_AGAIN,則會添加write定時器,從而判斷是否超時,如果發往 對端數據長度小,則一般write直接返回成功,則不會添加write超時定時器,也就不會有write超時,寫定時器參考函數ngx_http_upstream_send_request */ unsigned timedout:1; //定時器超時標記,見ngx_event_expire_timers //標志位,為1時表示這個事件存在於定時器中 unsigned timer_set:1; //ngx_event_add_timer ngx_add_timer 中置1 ngx_event_expire_timers置0 //標志位,delayed為1時表示需要延遲處理這個事件,它僅用於限速功能 unsigned delayed:1; //限速見ngx_http_write_filter /* 標志位,為1時表示延遲建立TCP連接,也就是說,經過TCP三次握手后並不建立連接,而是要等到真正收到數據包后才會建立TCP連接 */ unsigned deferred_accept:1; //通過listen的時候添加 deferred 參數來確定 /* the pending eof reported by kqueue, epoll or in aio chain operation */ //標志位,為1時表示等待字符流結束,它只與kqueue和aio事件驅動機制有關 //一般在觸發EPOLLRDHUP(當對端已經關閉,本端寫數據,會引起該事件)的時候,會置1,見ngx_epoll_process_events unsigned pending_eof:1; /* if (c->read->posted) { //刪除post隊列的時候需要檢查 ngx_delete_posted_event(c->read); } */ unsigned posted:1; //表示延遲處理該事件,見ngx_epoll_process_events -> ngx_post_event 標記是否在延遲隊列里面 //標志位,為1時表示當前事件已經關閉,epoll模塊沒有使用它 unsigned closed:1; //ngx_close_connection中置1 /* to test on worker exit */ //這兩個該標志位目前無實際意義 unsigned channel:1; unsigned resolver:1; unsigned cancelable:1; #if (NGX_WIN32) /* setsockopt(SO_UPDATE_ACCEPT_CONTEXT) was successful */ unsigned accept_context_updated:1; #endif #if (NGX_HAVE_KQUEUE) unsigned kq_vnode:1; /* the pending errno reported by kqueue */ int kq_errno; #endif /* * kqueue only: * accept: number of sockets that wait to be accepted * read: bytes to read when event is ready * or lowat when event is set with NGX_LOWAT_EVENT flag * write: available space in buffer when event is ready * or lowat when event is set with NGX_LOWAT_EVENT flag * * iocp: TODO * * otherwise: * accept: 1 if accept many, 0 otherwise */ //標志住,在epoll事件驅動機制下表示一次盡可能多地建立TCP連接,它與multi_accept配置項對應,實現原理基見9.8.1節 #if (NGX_HAVE_KQUEUE) || (NGX_HAVE_IOCP) int available; #else unsigned available:1; //ngx_event_accept中 ev->available = ecf->multi_accept; #endif /* 每一個事件最核心的部分是handler回調方法,它將由每一個事件消費模塊實現,以此決定這個事件究竟如何“消費” */ /* 1.event可以是普通的epoll讀寫事件(參考ngx_event_connect_peer->ngx_add_conn或者ngx_add_event),通過讀寫事件觸發 2.也可以是普通定時器事件(參考ngx_cache_manager_process_handler->ngx_add_timer(ngx_event_add_timer)),通過ngx_process_events_and_timers中的 epoll_wait返回,可以是讀寫事件觸發返回,也可能是因為沒獲取到共享鎖,從而等待0.5s返回重新獲取鎖來跟新事件並執行超時事件來跟新事件並且判斷定 時器鏈表中的超時事件,超時則執行從而指向event的handler,然后進一步指向對應r或者u的->write_event_handler read_event_handler 3.也可以是利用定時器expirt實現的讀寫事件(參考ngx_http_set_write_handler->ngx_add_timer(ngx_event_add_timer)),觸發過程見2,只是在handler中不會執行write_event_handler read_event_handler */ //這個事件發生時的處理方法,每個事件消費模塊都會重新實現它 //ngx_epoll_process_events中執行accept /* 賦值為ngx_http_process_request_line ngx_event_process_init中初始化為ngx_event_accept 如果是文件異步i/o,賦值為ngx_epoll_eventfd_handler //當accept客戶端連接后ngx_http_init_connection中賦值為ngx_http_wait_request_handler來讀取客戶端數據 在解析完客戶端發送來的請求的請求行和頭部行后,設置handler為ngx_http_request_handler */ //一般與客戶端的數據讀寫是 ngx_http_request_handler; 與后端服務器讀寫為ngx_http_upstream_handler(如fastcgi proxy memcache gwgi等) /* ngx_event_accept,ngx_http_ssl_handshake ngx_ssl_handshake_handler ngx_http_v2_write_handler ngx_http_v2_read_handler ngx_http_wait_request_handler ngx_http_request_handler,ngx_http_upstream_handler ngx_file_aio_event_handler */ ngx_event_handler_pt handler; //由epoll讀寫事件在ngx_epoll_process_events觸發 #if (NGX_HAVE_IOCP) ngx_event_ovlp_t ovlp; #endif //由於epoll事件驅動方式不使用index,所以這里不再說明 ngx_uint_t index; //可用於記錄error_log日志的ngx_log_t對象 ngx_log_t *log; //可以記錄日志的ngx_log_t對象 其實就是ngx_listening_t中獲取的log //賦值見ngx_event_accept //定時器節點,用於定時器紅黑樹中 ngx_rbtree_node_t timer; //見ngx_event_timer_rbtree /* the posted queue */ /* post事件將會構成一個隊列再統一處理,這個隊列以next和prev作為鏈表指針,以此構成一個簡易的雙向鏈表,其中next指向后一個事件的地址, prev指向前一個事件的地址 */ ngx_queue_t queue; };