前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >nginx的timeout(基于nginx1.17.9)

nginx的timeout(基于nginx1.17.9)

作者头像
theanarkh
发布2020-06-19 11:01:35
7220
发布2020-06-19 11:01:35
举报
文章被收录于专栏:原创分享原创分享

nginx中使用timeout的地方非常多,本文主要分析客户端和nginx通信时涉及到的几个timeout。

  1. 连接建立成功,接收业务数据超时
  2. 接收http报文的超时

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端口。下面看看该函数的主要逻辑。

代码语言:javascript
复制
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回调。

代码语言:javascript
复制
// 有连接到来时的处理函数
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报文到来时被执行。

代码语言:javascript
复制
// 连接上有请求到来时的处理函数
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报文。

代码语言:javascript
复制
// 处理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。

代码语言:javascript
复制
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中很多地方都使用了定时器,后面有空再分析。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档