9.3 Nginx连接的定义

作为Web服务器,每一个用户请求至少对应着一个TCP连接,为了及时处理这个连接,至少需要一个读事件和一个写事件,使得epoll可以有效地根据触发的事件调度相应模块读取请求或者发送响应。因此,Nginx中定义了基本的数据结构ngx_connection_t来表示连接,这个连接表示是客户端主动发起的、Nginx服务器被动接受的TCP连接,我们可以简单称其为被动连接。同时,在有些请求的处理过程中,Nginx会试图主动向其他上游服务器建立连接,并以此连接与上游服务器通信,因此,这样的连接与ngx_connection_t又是不同的,Nginx定义了ngx_peer_connection_t结构体来表示主动连接,当然,ngx_peer_connection_t主动连接是以ngx_connection_t结构体为基础实现的。本节将说明这两种连接中各字段的意义,同时需要注意的是,这两种连接都不可以随意创建,必须从连接池中获取,在9.3.3节中会说明连接池的用法。

9.3.1 被动连接

本章中未加修饰提到的“连接”都是指客户端发起的、服务器被动接受的连接,这样的连接都是使用ngx_connection_t结构体表示的,其定义如下。


typedef struct ngx_connection_s ngx_connection_t;

struct ngx_connection_s{

/连接未使用时,data成员用于充当连接池中空闲连接链表中的next指针。当连接被使用时,data的意义由使用它的Nginx模块而定,如在HTTP框架中,data指向ngx_http_request_t请求/

void*data;

//连接对应的读事件

ngx_event_t*read;

//连接对应的写事件

ngx_event_t*write;

//套接字句柄

ngx_socket_t fd;

//直接接收网络字符流的方法

ngx_recv_pt recv;

//直接发送网络字符流的方法

ngx_send_pt send;

//以ngx_chain_t链表为参数来接收网络字符流的方法

ngx_recv_chain_pt recv_chain;

//以ngx_chain_t链表为参数来发送网络字符流的方法

ngx_send_chain_pt send_chain;

/这个连接对应的ngx_listening_t监听对象,此连接由listening监听端口的事件建立/

ngx_listening_t*listening;

//这个连接上已经发送出去的字节数

off_t sent;

//可以记录日志的ngx_log_t对象

ngx_log_t*log;

/内存池。一般在accept一个新连接时,会创建一个内存池,而在这个连接结束时会销毁内存池。注意,这里所说的连接是指成功建立的TCP连接,所有的ngx_connection_t结构体都是预分配的。这个内存池的大小将由上面的listening监听对象中的pool_size成员决定/

ngx_pool_t*pool;

//连接客户端的sockaddr结构体

struct sockaddr*sockaddr;

//sockaddr结构体的长度

socklen_t socklen;

//连接客户端字符串形式的IP地址

ngx_str_t addr_text;

/本机的监听端口对应的sockaddr结构体,也就是listening监听对象中的sockaddr成员/

struct sockaddr*local_sockaddr;

/用于接收、缓存客户端发来的字符流,每个事件消费模块可自由决定从连接池中分配多大的空间给buffer这个接收缓存字段。例如,在HTTP模块中,它的大小决定于client_header_buffer_size配置项/

ngx_buf_t*buffer;

/该字段用来将当前连接以双向链表元素的形式添加到ngx_cycle_t核心结构体的reusable_connections_queue双向链表中,表示可以重用的连接/

ngx_queue_t queue;

/连接使用次数。ngx_connection_t结构体每次建立一条来自客户端的连接,或者用于主动向后端服务器发起连接时(ngx_peer_connection_t也使用它),number都会加1/

ngx_atomic_uint_t number;

//处理的请求次数

ngx_uint_t requests;

/*缓存中的业务类型。任何事件消费模块都可以自定义需要的标志位。这个buffered字段有8位,最多可以同时表示8个不同的业务。第三方模块在自定义buffered标志位时注意不要与可能使用的模块定义的标志位冲突。目前openssl模块定义了一个标志位:

define NGX_SSL_BUFFERED

0x01

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

0x08

同时,对于HTTP模块而言,buffered的低4位要慎用,在实际发送响应的ngx_http_write_filter_module过滤模块中,低4位标志位为1则意味着Nginx会一直认为有HTTP模块还需要处理这个请求,必须等待HTTP模块将低4位全置为0才会正常结束请求。检查低4位的宏如下:

define NGX_LOWLEVEL_BUFFERED 0x0f

*/

unsigned buffered:8;

/*本连接记录日志时的级别,它占用了3位,取值范围是0~7,但实际上目前只定义了5个值,由ngx_connection_log_error_e枚举表示,如下:

typedef enum{

NGX_ERROR_ALERT=0,

NGX_ERROR_ERR,

NGX_ERROR_INFO,

NGX_ERROR_IGNORE_ECONNRESET,

NGX_ERROR_IGNORE_EINVAL

}ngx_connection_log_error_e;

*/

unsigned log_error:3;

/标志位,为1时表示独立的连接,如从客户端发起的连接;为0时表示依靠其他连接的行为而建立起来的非独立连接,如使用upstream机制向后端服务器建立起来的连接/

unsigned single_connection:1;

//标志位,为1时表示不期待字符流结束,目前无意义

unsigned unexpected_eof:1;

//标志位,为1时表示连接已经超时

unsigned timedout:1;

//标志位,为1时表示连接处理过程中出现错误

unsigned error:1;

/标志位,为1时表示连接已经销毁。这里的连接指是的TCP连接,而不是ngx_connection_t结构体。当destroyed为1时,ngx_connection_t结构体仍然存在,但其对应的套接字、内存池等已经不可用/

unsigned destroyed:1;

/标志位,为1时表示连接处于空闲状态,如keepalive请求中两次请求之间的状态/

unsigned idle:1;

//标志位,为1时表示连接可重用,它与上面的queue字段是对应使用的

unsigned reusable:1;

//标志位,为1时表示连接关闭

unsigned close:1;

//标志位,为1时表示正在将文件中的数据发往连接的另一端

unsigned sendfile:1;

/标志位,如果为1,则表示只有在连接套接字对应的发送缓冲区必须满足最低设置的大小阈值时,事件驱动模块才会分发该事件。这与上文介绍过的ngx_handle_write_event方法中的lowat参数是对应的/

unsigned sndlowat:1;

/*标志位,表示如何使用TCP的nodelay特性。它的取值范围是下面这个枚举类型ngx_connection_tcp_nodelay_e:

typedef enum{

NGX_TCP_NODELAY_UNSET=0,

NGX_TCP_NODELAY_SET,

NGX_TCP_NODELAY_DISABLED

}ngx_connection_tcp_nodelay_e;

*/

unsigned tcp_nodelay:2;

/*标志位,表示如何使用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;

*/

unsigned tcp_nopush:2;

if(NGX_HAVE_AIO_SENDFILE)

//标志位,为1时表示使用异步I/O的方式将磁盘上文件发送给网络连接的另一端

unsigned aio_sendfile:1;

//使用异步I/O方式发送的文件,busy_sendfile缓冲区保存待发送文件的信息

ngx_buf_t*busy_sendfile;

endif

};


链表中的recv、send、recv_chain、send_chain这4个关于接收、发送网络字符流的方法原型定义如下。


typedef ssize_t(ngx_recv_pt)(ngx_connection_tc,u_char*buf,size_t size);

typedef ssize_t(ngx_recv_chain_pt)(ngx_connection_tc,ngx_chain_t*in);

typedef ssize_t(ngx_send_pt)(ngx_connection_tc,u_char*buf,size_t size);

typedef ngx_chain_tngx_send_chain_pt)(ngx_connection_tc,ngx_chain_tin,off_t limit);


这4个成员以方法指针的形式出现,说明每个连接都可以采用不同的接收方法,每个事件消费模块都可以灵活地决定其行为。不同的事件驱动机制需要使用的接收、发送方法多半是不一样的,在9.6节中,读者可以看到ngx_epoll_module模块是如何定义这4种方法的。