在Nginx中,主要包括了链接与处理两部分。node
在src/core文件夹下包含有connection的源文件,Ngx_connection.h/Ngx_connection.c中能够找到SOCK_STREAM,也就是说Nginx是基于TCP链接的。linux
对于应用程序,首先第一步确定是加载并解析配置文件,Nginx一样如此,这样能够得到须要监听的端口和IP地址。以后,Nginx就要建立master进程,并创建socket,这样就能够建立多个worker进程来,每一个worker进程均可以accept链接请求。当经过三次握手成功创建一个链接后,nginx的某一个worker进程会accept成功,获得这个创建好的链接的socket,而后建立ngx_connection_t结构体,存储客户端相关内容。nginx
这样创建好链接后,服务器和客户端就能够正常进行读写事件了。链接完成后就能够释放掉ngx_connection_t结构体了。数组
一样,Nginx也能够做为客户端,这样就须要先建立一个ngx_connection_t结构体,而后建立socket,并设置socket的属性( 好比非阻塞)。而后再经过添加读写事件,调用connect/read/write来调用链接,最后关掉链接,并释放ngx_connection_t。服务器
struct ngx_connection_s { void *data; ngx_event_t *read; ngx_event_t *write; ngx_socket_t fd; ngx_recv_pt recv; ngx_send_pt send; ngx_recv_chain_pt recv_chain; ngx_send_chain_pt send_chain; ngx_listening_t *listening; off_t sent; ngx_log_t *log; ngx_pool_t *pool; struct sockaddr *sockaddr; socklen_t socklen; ngx_str_t addr_text; #if (NGX_SSL) ngx_ssl_connection_t *ssl; #endif struct sockaddr *local_sockaddr; ngx_buf_t *buffer; ngx_queue_t queue; ngx_atomic_uint_t number; ngx_uint_t requests; unsigned buffered:8; unsigned log_error:3; /* ngx_connection_log_error_e */ unsigned unexpected_eof:1; unsigned timedout:1; unsigned error:1; unsigned destroyed:1; unsigned idle:1; unsigned reusable:1; unsigned close:1; unsigned sendfile:1; unsigned sndlowat:1; unsigned tcp_nodelay:2; /* ngx_connection_tcp_nodelay_e */ unsigned tcp_nopush:2; /* ngx_connection_tcp_nopush_e */ #if (NGX_HAVE_IOCP) unsigned accept_context_updated:1; #endif #if (NGX_HAVE_AIO_SENDFILE) unsigned aio_sendfile:1; ngx_buf_t *busy_sendfile; #endif #if (NGX_THREADS) ngx_atomic_t lock; #endif };
在linux系统中,每个进程可以打开的文件描述符fd是有限的,而每建立一个socket就会占用一个fd,这样建立的socket就会有限的。在Nginx中,采用链接池的方法,能够避免这个问题。网络
Nginx在实现时,是经过一个链接池来管理的,每一个worker进程都有一个独立的链接池,链接池的大小是worker_connections。这里的链接池里面保存的其实不是真实的链接,它只是一个worker_connections大小的一个ngx_connection_t结构的数组。而且,nginx会经过一个链表free_connections来保存全部的空闲ngx_connection_t,每次获取一个链接时,就从空闲链接链表中获取一个,用完后,再放回空闲链接链表里面(这样就节省了建立与销毁connection结构的开销)。数据结构
因此对于一个Nginx服务器来讲,它所能建立的链接数也就是socket链接数目能够达到worker_processes(worker数)*worker_connections。socket
对于多个worker进程同时accpet时产生的竞争,有可能致使某一worker进程accept了大量的链接,而其余worker进程却没有几个链接,这样就致使了负载不均衡,对于负载重的worker进程中的链接响应时间必然会增大。很显然,这是不公平的,有的进程有空余链接,却没有处理机会,有的进程由于没有空余链接,却人为地丢弃链接。tcp
nginx中存在accept_mutex选项,只有得到了accept_mutex的进程才会去添加accept事件,也就是说,nginx会控制进程是否添加accept事件。nginx使用一个叫ngx_accept_disabled的变量来控制进程是否去竞争accept_mutex锁。ide
ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n; //能够看出来随着空余链接的增长,disabled的值下降
if (ngx_use_accept_mutex) { if (ngx_accept_disabled > 0) { //当disabled的值大于0时,禁止竞争,但每次-1 ngx_accept_disabled--; } else { if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) { return; } if (ngx_accept_mutex_held) { flags |= NGX_POST_EVENTS; } else { if (timer == NGX_TIMER_INFINITE || timer > ngx_accept_mutex_delay) { timer = ngx_accept_mutex_delay; } } }
}
在nginx中,request是http请求,具体到nginx中的数据结构是ngx_http_request_t。ngx_http_request_t是对一个http请求的封装。
struct ngx_http_request_s { uint32_t signature; /* "HTTP" */ ngx_connection_t *connection; void **ctx; void **main_conf; void **srv_conf; void **loc_conf; ngx_http_event_handler_pt read_event_handler; ngx_http_event_handler_pt write_event_handler; #if (NGX_HTTP_CACHE) ngx_http_cache_t *cache; #endif ngx_http_upstream_t *upstream; ngx_array_t *upstream_states; /* of ngx_http_upstream_state_t */ ngx_pool_t *pool; ngx_buf_t *header_in; ngx_http_headers_in_t headers_in; ngx_http_headers_out_t headers_out; ngx_http_request_body_t *request_body; time_t lingering_time; time_t start_sec; ngx_msec_t start_msec; ngx_uint_t method; ngx_uint_t http_version; ngx_str_t request_line; ngx_str_t uri; ngx_str_t args; ngx_str_t exten; ngx_str_t unparsed_uri; ngx_str_t method_name; ngx_str_t http_protocol; ngx_chain_t *out; ngx_http_request_t *main; ngx_http_request_t *parent; ngx_http_postponed_request_t *postponed; ngx_http_post_subrequest_t *post_subrequest; ngx_http_posted_request_t *posted_requests; ngx_int_t phase_handler; ngx_http_handler_pt content_handler; ngx_uint_t access_code; ngx_http_variable_value_t *variables; #if (NGX_PCRE) ngx_uint_t ncaptures; int *captures; u_char *captures_data; #endif size_t limit_rate; /* used to learn the Apache compatible response length without a header */ size_t header_size; off_t request_length; ngx_uint_t err_status; ngx_http_connection_t *http_connection; #if (NGX_HTTP_SPDY) ngx_http_spdy_stream_t *spdy_stream; #endif ngx_http_log_handler_pt log_handler; ngx_http_cleanup_t *cleanup; unsigned subrequests:8; unsigned count:8; unsigned blocked:8; unsigned aio:1; unsigned http_state:4; /* URI with "/." and on Win32 with "//" */ unsigned complex_uri:1; /* URI with "%" */ unsigned quoted_uri:1; /* URI with "+" */ unsigned plus_in_uri:1; /* URI with " " */ unsigned space_in_uri:1; unsigned invalid_header:1; unsigned add_uri_to_alias:1; unsigned valid_location:1; unsigned valid_unparsed_uri:1; unsigned uri_changed:1; unsigned uri_changes:4; unsigned request_body_in_single_buf:1; unsigned request_body_in_file_only:1; unsigned request_body_in_persistent_file:1; unsigned request_body_in_clean_file:1; unsigned request_body_file_group_access:1; unsigned request_body_file_log_level:3; unsigned subrequest_in_memory:1; unsigned waited:1; #if (NGX_HTTP_CACHE) unsigned cached:1; #endif #if (NGX_HTTP_GZIP) unsigned gzip_tested:1; unsigned gzip_ok:1; unsigned gzip_vary:1; #endif unsigned proxy:1; unsigned bypass_cache:1; unsigned no_cache:1; /* * instead of using the request context data in * ngx_http_limit_conn_module and ngx_http_limit_req_module * we use the single bits in the request structure */ unsigned limit_conn_set:1; unsigned limit_req_set:1; #if 0 unsigned cacheable:1; #endif unsigned pipeline:1; unsigned chunked:1; unsigned header_only:1; unsigned keepalive:1; unsigned lingering_close:1; unsigned discard_body:1; unsigned internal:1; unsigned error_page:1; unsigned ignore_content_encoding:1; unsigned filter_finalize:1; unsigned post_action:1; unsigned request_complete:1; unsigned request_output:1; unsigned header_sent:1; unsigned expect_tested:1; unsigned root_tested:1; unsigned done:1; unsigned logged:1; unsigned buffered:4; unsigned main_filter_need_in_memory:1; unsigned filter_need_in_memory:1; unsigned filter_need_temporary:1; unsigned allow_ranges:1; #if (NGX_STAT_STUB) unsigned stat_reading:1; unsigned stat_writing:1; #endif /* used to parse HTTP headers */ ngx_uint_t state; ngx_uint_t header_hash; ngx_uint_t lowcase_index; u_char lowcase_header[NGX_HTTP_LC_HEADER_LEN]; u_char *header_name_start; u_char *header_name_end; u_char *header_start; u_char *header_end; /* * a memory that can be reused after parsing a request line * via ngx_http_ephemeral_t */ u_char *uri_start; u_char *uri_end; u_char *uri_ext; u_char *args_start; u_char *request_start; u_char *request_end; u_char *method_end; u_char *schema_start; u_char *schema_end; u_char *host_start; u_char *host_end; u_char *port_start; u_char *port_end; unsigned http_minor:16; unsigned http_major:16; };
这里须要复习下Http协议了。
http请求是典型的请求-响应类型的的网络协议,须要一行一行的分析请求行与请求头,以及输出响应行与响应头。
Request 消息分为3部分,第一部分叫请求行requset line, 第二部分叫http header, 第三部分是body. header和body之间有个空行。
Response消息的结构, 和Request消息的结构基本同样。 一样也分为三部分,第一部分叫response line, 第二部分叫response header,第三部分是body. header和body之间也有个空行。
分别为Request和Response消息结构图:
worker进程负责业务处理。在worker进程中有一个函数ngx_worker_process_cycle(),执行无限循环,不断处理收到的来自客户端的请求,并进行处理,直到整个nginx服务被中止。
一个HTTP Request的处理过程:
一个phase handler的执行过程:
这里直接上taobao团队的给出的Nginx流程图了。
从这个图中能够清晰的看到解析http消息每一个部分的不一样模块。
长链接的定义:所谓长链接,指在一个链接上能够连续发送多个数据包,在链接保持期间,若是没有数据包发送,须要双方发链路检测包。
在这里,http请求是基于TCP协议之上的,因此创建须要三次握手,关闭须要四次握手。而http请求是请求应答式的,若是咱们能知道每一个请求头与响应体的长度,那么咱们是能够在一个链接上面执行多个请求的,这就须要在请求头中指定content-length来代表body的大小。在http1.0与http1.1中稍有不一样,具体状况以下:
对于http1.0协议来讲,若是响应头中有content-length头,则以content-length的长度就能够知道body的长度了,客户端在接收body时,就能够依照这个长度来接收数据,接收完后,就表示这个请求完成了。而若是没有content-length头,则客户端会一直接收数据,直到服务端主动断开链接,才表示body接收完了。
而对于http1.1协议来讲,若是响应头中的Transfer-encoding为chunked传输,则表示body是流式输出,body会被分红多个块,每块的开始会标识出当前块的长度,此时,body不须要经过长度来指定。若是是非chunked传输,并且有content-length,则按照content-length来接收数据。不然,若是是非chunked,而且没有content-length,则客户端接收数据,直到服务端主动断开链接。
当客户端的一次访问,须要屡次访问同一个server时,打开keepalive的优点很是大,好比图片服务器,一般一个网页会包含不少个图片。打开keepalive也会大量减小time-wait的数量。
管道技术是基于长链接的,目的是利用一个链接作屡次请求。
keepalive采用的是串行方式,而pipeline也不是并行的,可是它能够减小两个请求间的等待的事件。nginx在读取数据时,会将读取的数据放到一个buffer里面,因此,若是nginx在处理完前一个请求后,若是发现buffer里面还有数据,就认为剩下的数据是下一个请求的开始,而后就接下来处理下一个请求,不然就设置keepalive。
当Nginx要关闭链接时,并不是当即关闭链接,而是再等待一段时间后才真正关掉链接。目的在于读取客户端发来的剩下的数据。
若是服务器直接关闭,恰巧客户端刚发送消息,那么就不会有ACK,致使出现没有任何错误信息的提示。
Nginx经过设置一个读取客户数据的超时事件lingering_timeout来防止以上问题的发生。