概述
通过分析Nginx源码中ngx_event_connect.c 和 ngx_event_connect.h了解Nginx是如何通过异步非阻塞方式去处理网络连接,提高服务器并发能力和性能。
整体逻辑梳理
- 连接初始化:创建套接字、设置非阻塞模式、获取对象等
- 事件处理:设置读写事件的处理函数,将连接添加到事件驱动模块中
- 连接处理:调用ngx_connect_peer
- 错误处理:连接失败的时候,清理资源同时记录日志
ngx_event_connect.h
主要定义管理连接所用的数据结构以及相应函数
- 结构体 ngx_event_connect_t :用于保存连接信息,其中包括目标地址、事件处理函数、数据、超时信息以及日志等。
- 函数声明
- ngx_event_connect_peer():客户端向服务端发起连接后,服务端通过该函数对客户端发起的连接进行集中管理和初始化
- ngx_event_connect_peer_failed():处理连接失败的函数
/*
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_CONNECT_H_INCLUDED_
#define _NGX_EVENT_CONNECT_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
typedef struct {
ngx_peer_address_t *peer;
ngx_event_handler_pt handler;
void *data;
ngx_msec_t timeout;
unsigned rcvbuf:1;
unsigned sndbuf:1;
unsigned cached:1;
ngx_log_t *log;
ngx_pool_t *pool;
ngx_resolver_ctx_t *ctx;
} ngx_event_connect_t;
ngx_int_t ngx_event_connect_peer(ngx_event_connect_t *connect);
void ngx_event_connect_peer_failed(ngx_event_connect_t *connect);
#endif /* _NGX_EVENT_CONNECT_H_INCLUDED_ */
ngx_event_connect.c
处理函数分析
- ngx_event_connect_peer
- 创建套接字
- 将该套接字设置成非阻塞模式(目的是提高并发性能)
- 获取并初始化连接对象:从连接池中获取一个连接对象,同时设置其各种属性
- 设置事件日志:处理这些事件的时候记录到日志
- 添加到事件驱动模型中,例如epoll中
- 获取并连接到远程主机
- ngx_event_connect_peer_failed
- 获取连接对象
- 如果套接字存在,则关闭连接
- 将连接对象设置为空
/*
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_connect.h>
ngx_int_t
ngx_event_connect_peer(ngx_event_connect_t *connect)
{
int rc;
ngx_socket_t s;
ngx_event_t *rev, *wev;
ngx_connection_t *c;
s = ngx_socket(connect->peer->sockaddr->sa_family, SOCK_STREAM, 0);
if (s == (ngx_socket_t) -1) {
ngx_log_error(NGX_LOG_ALERT, connect->log, ngx_socket_errno,
ngx_socket_n " failed");
return NGX_ERROR;
}
if (ngx_nonblocking(s) == -1) {
ngx_log_error(NGX_LOG_ALERT, connect->log, ngx_socket_errno,
ngx_nonblocking_n " failed");
if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_ALERT, connect->log, ngx_socket_errno,
ngx_close_socket_n " failed");
}
return NGX_ERROR;
}
c = ngx_get_connection(s, connect->log);
if (c == NULL) {
if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_ALERT, connect->log, ngx_socket_errno,
ngx_close_socket_n " failed");
}
return NGX_ERROR;
}
c->type = SOCK_STREAM;
c->log = connect->log;
c->pool = connect->pool;
c->sockaddr = connect->peer->sockaddr;
c->socklen = connect->peer->socklen;
c->addr_text = connect->peer->name;
c->read->handler = connect->handler;
c->write->handler = connect->handler;
c->data = connect->data;
connect->data = c;
rev = c->read;
wev = c->write;
rev->log = connect->log;
wev->log = connect->log;
if (ngx_add_conn) {
if (ngx_add_conn(c) == NGX_ERROR) {
goto failed;
}
} else {
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
goto failed;
}
if (ngx_add_event(wev, NGX_WRITE_EVENT, NGX_CLEAR_EVENT)
== NGX_ERROR)
{
goto failed;
}
}
rc = connect->peer->get(connect->peer, connect);
if (rc == NGX_ERROR) {
goto failed;
}
if (rc == NGX_DECLINED) {
ngx_log_error(NGX_LOG_ALERT, connect->log, 0,
"no live upstreams");
goto failed;
}
rc = ngx_connect_peer(connect);
if (rc == NGX_ERROR) {
goto failed;
}
if (rc == NGX_AGAIN) {
return NGX_AGAIN;
}
return NGX_OK;
failed:
ngx_close_connection(c);
return NGX_ERROR;
}
void
ngx_event_connect_peer_failed(ngx_event_connect_t *connect)
{
ngx_connection_t *c;
c = connect->data;
if (c->fd != (ngx_socket_t) -1) {
ngx_close_connection(c);
}
connect->data = NULL;
}
连接池
上述中从连接池中获取连接对象,使用的是一种池化技术。Nginx处理连接中,并不是每一次都新创建连接对象,而是预先创建好相应的对象。目的是提高资源利用率和系统性能。
类似于工厂预先制作好相应的工作服,工人来打工时发一套,离职的时候收回工作服给下一个工人使用。
工作原理分析
- 预先创建连接对象
- Nginx启动后,预先创建一组连接对象,这些对象存储在连接池中,等待调用
- 连接对象中包含着连接所需要使用的对应资源
- 复用连接对象
- 建立新连接的时候,Nginx从连接池中获取一个空闲的连接对象
- 目的是减少内存分配和释放的开销,提高连接建立的效率
- 释放和回收连接对象
- 连接关闭的时候,连接对象会被重置信息然后放回连接池,供下一次连接使用