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