nginx中使用timeout的地方非常多,本文主要分析客户端和nginx通信时涉及到的几个timeout。
1 连接建立成功,接收业务数据超时 这个逻辑从ngx_event_accept函数开始分析,ngx_event_accept是nginx在监听某个端口时,底层建立tcp连接成功后回调的函数。我们首先需要了解,在nginx中。一个连接是使用ngx_connection_t表示。每个ngx_connection_t对应两个ngx_event_t结构体,一个读,一个写。他们之前有内在的字段关联起来。另外一个ngx_connection_t也会关联到一个ngx_listening_t结构体,ngx_listening_t是用于表示一个被监听的ip端口,比如我们设置的127.0.0.1 80。ngx_connection_t指向的ngx_listening_t,表示该连接来自哪个监听的ip端口。下面看看该函数的主要逻辑。
void ngx_event_accept(ngx_event_t *ev)
{
socklen_t socklen;
ngx_err_t err;
ngx_log_t *log;
ngx_uint_t level;
ngx_socket_t s;
ngx_event_t *rev, *wev;
ngx_sockaddr_t sa;
ngx_listening_t *ls;
ngx_connection_t *c, *lc;
ngx_event_conf_t *ecf;
ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);
// 对应的ngx_connection_t
lc = ev->data;
// 对应的ngx_listening_t,即来自哪个监听的socket
ls = lc->listening;
ev->ready = 0;
do {
socklen = sizeof(ngx_sockaddr_t);
s = accept(lc->fd, &sa.sockaddr, &socklen);
// 为通信socket,获取一个connection结构体数组
c = ngx_get_connection(s, ev->log);
// TCP
c->type = SOCK_STREAM;
// 一个连接分配一个内存池
c->pool = ngx_create_pool(ls->pool_size, ev->log);
// 为通信socket设置读写数据的函数,由事件驱动模块提供
c->recv = ngx_recv;
c->send = ngx_send;
c->recv_chain = ngx_recv_chain;
c->send_chain = ngx_send_chain;
// 标识该连接来自哪个监听的socket,即ngx_listening_t
c->listening = ls;
// 读写事件的结构体
rev = c->read;
wev = c->write;
/*
执行连接到来时的回调,对于http模块是ngx_http_init_connection,
会注册等待读事件,等待数据到来
*/
ls->handler(c);
} while (ev->available); // 是否连续accept(尽可能),可能会没有等待accept的节点了
}
ngx_event_accept会分配一个ngx_connection_t去表示一个连接,然后执行ngx_http_init_connection回调。
// 有连接到来时的处理函数
void ngx_http_init_connection(ngx_connection_t *c)
{
ngx_uint_t i;
ngx_event_t *rev;
struct sockaddr_in *sin;
ngx_http_port_t *port;
ngx_http_in_addr_t *addr;
ngx_http_log_ctx_t *ctx;
ngx_http_connection_t *hc;
// 读事件结构体
rev = c->read;
// 设置新的回调函数,在http报文到达时执行
rev->handler = ngx_http_wait_request_handler;
c->write->handler = ngx_http_empty_handler;
/*
建立连接后,post_accept_timeout这么长时间还没有数据到来则超时,
post_accept_timeout的值等于nginx.conf中client_header_timeout字段的值
*/
ngx_add_timer(rev, c->listening->post_accept_timeout);
// 注册读事件,等到http报文到来
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
}
ngx_http_init_connection函数做了两个事情 1 设置定时器,如果超时后还没有收到http报文,则关闭连接。 2 设置下一步回调函数为ngx_http_wait_request_handler。 我们继续来到ngx_http_wait_request_handler函数。该函数在http报文到来时被执行。
// 连接上有请求到来时的处理函数
static void ngx_http_wait_request_handler(ngx_event_t *rev)
{
u_char *p;
size_t size;
ssize_t n;
ngx_buf_t *b;
ngx_connection_t *c;
ngx_http_connection_t *hc;
ngx_http_core_srv_conf_t *cscf;
c = rev->data;
// 已经超时,http报文还没有到达,关闭连接
if (rev->timedout) {
ngx_http_close_connection(c);
return;
}
hc = c->data;
cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
// 分配内存接收数据,client_header_buffer_size来自nginx.conf
size = cscf->client_header_buffer_size;
b = c->buffer;
// 创建一个buf用于接收http报文
if (b == NULL) {
b = ngx_create_temp_buf(c->pool, size);
c->buffer = b;
}
// 接收数据,即读取http报文
n = c->recv(c, b->last, size);
// 更新可写指针的位置,n是读取的http报文字节数
b->last += n;
// 创建一个ngx_http_request_t结构体表示本次http请求
c->data = ngx_http_create_request(c);
// 开始处理http报文
rev->handler = ngx_http_process_request_line;
ngx_http_process_request_line(rev);
}
该函数主要是 1 在超时时仍然没有收到http报文,则处理超时。 2 分配结构体ngx_http_request_t用于表示一个http请求 3 分配保存http报文的buffer,然后开始解析http报文 刚才分析了建立连接和收到http报文间有一个超时时间。nginx还定义了另外一个超时。就是尽管有部分http报文已经到达,但是如果整个http报文到达过慢,也可能会导致超时问题。现在我们继续来看一下ngx_http_process_request_line函数。该函数用于解析http报文。
// 处理http数据包的请求报文
static void ngx_http_process_request_line(ngx_event_t *rev)
{
ssize_t n;
ngx_int_t rc, rv;
ngx_str_t host;
ngx_connection_t *c;
ngx_http_request_t *r;
c = rev->data;
r = c->data;
// 超时了,关闭请求
if (rev->timedout) {
c->timedout = 1;
ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
return;
}
rc = NGX_AGAIN;
for ( ;; ) {
// 读取http报文到r的buffer里
n = ngx_http_read_request_header(r);
// 解析http请求行,r->header_in的内容由ngx_http_read_request_header设置
rc = ngx_http_parse_request_line(r, r->header_in);
// 请求行解析完
if (rc == NGX_OK) {
ngx_list_init(&r->headers_in.headers, r->pool, 20, sizeof(ngx_table_elt_t))
// 开始处理http头
rev->handler = ngx_http_process_request_headers;
ngx_http_process_request_headers(rev);
break;
}
}
}
ngx_http_process_request_line函数主要是处理http报文到达超时的问题和读取http请求行,然后解析。那我们看一下这个timeout字段是怎么设置的。我们看读取http报文的函数ngx_http_read_request_header。
static ssize_t ngx_http_read_request_header(ngx_http_request_t *r)
{
ssize_t n;
ngx_event_t *rev;
ngx_connection_t *c;
ngx_http_core_srv_conf_t *cscf;
c = r->connection;
rev = c->read;
// 读取http报文
n = c->recv(c, r->header_in->last, r->header_in->end - r->header_in->last);
// 没有数据可读
if (n == NGX_AGAIN) {
// 还没有设置定时器(还没有插入定时器红黑树)
if (!rev->timer_set) {
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
// 根据nginx配置设置读取头部的超时时间,client_header_timeout来自nginx.conf
ngx_add_timer(rev, cscf->client_header_timeout);
}
// 注册读事件
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
return NGX_AGAIN;
}
// 读取成功,更新last指针
r->header_in->last += n;
return n;
}
ngx_http_read_request_header读取http报文的时候,如果没有数据可读则设置超时时间。超时时会执行ngx_http_process_request_line关闭请求。 总结:本文介绍了两个timeout,在nginx中很多地方都使用了定时器,后面有空再分析。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有