nginx http header [Connection: keep-alive]的实现原理

http keep-alive 进化史

  • 在http1.0之前,是没有[Connection: keep-alive]头的,也就是说那个时候每个http请求都会打开一个tpc连接,这样是相当消耗服务端资源(内存,cpu)。
  • 在http1.0时,工程师们绝对http不够持久,浪费资源,遇到密集的http请求时性能低下,于是有些浏览器在请求时,在header里增加了一个非标准的Connection关键字,当[Connection: keep-alive]时,就表示客户端要求服务端的tcp建立后不要立即关闭,而是复用该tcp连接,当[Connection: close]时,就和以前一样了。
  • 在http1.1及以后,W3C组织(http协议制定者)觉得这个头很有用,就纳入了http标准中,并且要求http客户端和服务端默认支持keep-alive

keep-alive实现原理

这里需要注意的是,http keep-alive实现了对同一tcp连接的复用,即可以在一个TCP连接中发送多个HTTP请求,这种技术叫做HTTP复用(HTTP Multiplexing),与TCP连接复用是完全不同的技术,不要混淆了。

还有就是HTTP复用(对同一tcp连接的复用)只是针对同一个客户端来说的,对于不同的客户端是无法做到的,要想实现就需要TCP复用技术了。

实现原理图如下:
在这里插入图片描述
可以看到,要想实现keep-alive,客户端和服务端都需要做相应的支持!!!!

针对 keep-alive 网络抓包分析

环境:
客户端=》火狐浏览器
ajax 请求

 $.ajax({
                    contentType: 'application/json; charset=utf-8;',
                    url: URL,
                    timeout: timeOut,
                    data: JSON.stringify(data),
                    method: "POST",
                    dataType: 'json',
                    success: function (response) {
                        window.rsp_data = response;
         
                        // JSON-Formatter: https://github.com/mohsen1/json-formatter-js
                        var formatter = new JSONFormatter(response, exp, {
                            hoverPreviewEnabled: true,
                            hoverPreviewArrayCount: 100,
                            hoverPreviewFieldCount: 5,
                            theme: '',
                            animateOpen: true,
                            animateClose: true
                        });
                        $('#area_callback')
                            .html(formatter.render());
                        console.log(response);
                        // To the bottom.
                        /*$("html, body").animate({ scrollTop: $(document).height()-$(window).height() }, 500);*/
                    },
                    error: function (xhr, status, err) {                   
                        console.log(xhr)
                        console.log(status)
                        console.log(err)
                    }

                });

服务端=》nginx
配置如下:

 #keepalive_disable msie6;
 keepalive_timeout  65;#表示一个tcp连接空闲65s后就会被关闭
 #keepalive_requests 1000;#表示一个tcp连接处理1000个请求后就会被关闭

抓包如下:
【火狐浏览器抓包】
火狐浏览器抓包
明显看到tcp连接被服用了,并且可以观察到keepalive_timeout的含义如下:

  1. 在keepalive_timeout时间内,若有新的请求进来,使用了该连接,timeout时间将会被刷新,重新计数;
  2. 在keepalive_timeout时间内,若一直没有新的请求,一直处理空闲状态,则在超时后,nginx会主动断开该连接,即发送FIN包。
    【keepalive_requests 含义也类似,有兴趣的同学可以自己试验下】。

另外,可以看到火狐浏览器的心跳包是10s一个,但是用谷歌浏览器测试,就是45s一个,可见不同浏览器的心跳包间隔是不同的,如下:
【谷歌浏览器抓包】
谷歌浏览器抓包对比就会发现,服务端socket空闲到达keepalive_timeout,主动发了FIN包后,二者的行为也不同,火狐的是发出ACK包后紧跟着也发出FIN包,而谷歌的就不会,而是继续发送心跳包,直到服务端发出RST包。

小疑问: 使用keep-alive后,客户端是如何知道服务端数据传输完成了呢?

  1. 用户在代码里主动计算返回资源(HTTP Response Body)大小(单位:B),并设置Content-Length: <length>header中;
    在这里插入图片描述

  2. 交给http服务软件(nginx,apache)处理,HTTP头部使用Transfer-Encoding: chunked来代替Content-Length,他表示数据是分块传输的,最后一块是空的,表示数据传输完了
    在这里插入图片描述

  3. 等待服务端主动关闭socket

不过测试中,如果设置了Content-Length,就会导致keep-alive失效,因为浏览器会主动关闭socket,【火狐,谷歌都这样,猜测这个行为没有纳入http协议中,但是已经作为浏览器业界的默认行为了】抓包如下:

在这里插入图片描述

nginx 对keep-alive的实现原理

首先去官网扒拉了下,还真发现有相关解释:
在这里插入图片描述
于是下载了nginx一份源码,顺着该文档追查了下:

1:目录如下,先跑到http目录下看就好
在这里插入图片描述
2:在ngx_http_process_request_headers函数里,

//解析http头
if (rc == NGX_OK) {
.....
	hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,  h->lowcase_key, h->key.len);
	if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
    	return;
	}
......
}
//解析完http头的动作
 if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
  /* a whole header has been parsed successfully */

            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "http header done");

            r->request_length += r->header_in->pos - r->header_name_start;

            r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;

            rc = ngx_http_process_request_header(r);

            if (rc != NGX_OK) {
                return;
            }

            ngx_http_process_request(r);

            return;
 }

3:在ngx_http_process_request函数里,会调用ngx_http_handler函数,该函数如下:

void
ngx_http_handler(ngx_http_request_t *r)
{
    ......
    if (!r->internal) {
        switch (r->headers_in.connection_type) {
        case 0:
            r->keepalive = (r->http_version > NGX_HTTP_VERSION_10);//http1.1默认支持keep-alive
            break;

        case NGX_HTTP_CONNECTION_CLOSE:
            r->keepalive = 0;
            break;

        case NGX_HTTP_CONNECTION_KEEP_ALIVE:
            r->keepalive = 1;
            break;
        }

        r->lingering_close = (r->headers_in.content_length_n > 0
                              || r->headers_in.chunked);
        r->phase_handler = 0;

    } else {
        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
        r->phase_handler = cmcf->phase_engine.server_rewrite_index;
    }

   ......
}

到这里,Connection:keep-alive解析完毕;
4:ngx_http_finalize_connection函数中:

//根据条件是否执行ngx_http_set_keepalive函数
if (!ngx_terminate
         && !ngx_exiting
         && r->keepalive
         && clcf->keepalive_timeout > 0)//可见要想nginx支持keep-alive,配置文件里必须有keepalive_timeout > 0
    {
        ngx_http_set_keepalive(r);
        return;
    }
//关闭该请求,满足执行ngx_http_set_keepalive函数,就不会走到这里了
ngx_http_close_request(r, 0);

5:ngx_http_set_keepalive函数就是根据步骤3的解析结果进行处理了

.......
r->keepalive = 0;
ngx_http_free_request(r, 0);
......
ngx_add_timer(rev, clcf->keepalive_timeout);//添加一个定时任务,并且每个请求都会重新对该定时任务刷新

6:搜索Connection关键字,找到了ngx_http_headers_in数组,是用于配置解析http header头的:

ngx_http_header_t  ngx_http_headers_in[] = {
    { ngx_string("Host"), offsetof(ngx_http_headers_in_t, host),
                 ngx_http_process_host },

    { ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection),
                 ngx_http_process_connection },

    { ngx_string("If-Modified-Since"),
                 offsetof(ngx_http_headers_in_t, if_modified_since),
                 ngx_http_process_unique_header_line },
  ...
}

7:搜索ngx_http_headers_in关键字,找到了ngx_http_init_headers_in_hash函数,该函数的作用就是将ngx_http_headers_in内容放到hash中:

static ngx_int_t
ngx_http_init_headers_in_hash(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
{
    ......

    for (header = ngx_http_headers_in; header->name.len; header++) {
        hk = ngx_array_push(&headers_in);
        if (hk == NULL) {
            return NGX_ERROR;
        }

        hk->key = header->name;
        hk->key_hash = ngx_hash_key_lc(header->name.data, header->name.len);
        hk->value = header;
    }

    hash.hash = &cmcf->headers_in_hash;
    hash.key = ngx_hash_key_lc;
    hash.max_size = 512;
    hash.bucket_size = ngx_align(64, ngx_cacheline_size);
    hash.name = "headers_in_hash";
    hash.pool = cf->pool;
    hash.temp_pool = NULL;
    ......
}

对ngx_http_init_headers_in_hash的调用链进行追踪,会发现有一个ngx_http_block的函数调用,这个函数就牛逼了,所有的http配置都是它在管理。。。,这里就不深究了,有兴趣的可以看看一张脑图说清 Nginx 的主流程

8:接着说ngx_http_headers_in,Connection头的处理函数是ngx_http_process_connection

static ngx_int_t
ngx_http_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h,
    ngx_uint_t offset)
{
    if (ngx_strcasestrn(h->value.data, "close", 5 - 1)) {
        r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;

    } else if (ngx_strcasestrn(h->value.data, "keep-alive", 10 - 1)) {
        r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE;
    }

    return NGX_OK;
}

参考资料

1:nginx 官方文档
2:Nginx 的配置解析流程
3:Http协议之Content-Length
4:HTTP复用与TCP复用
5:Nginx 对 Connection 头的处理过程
6:Nginx使用keep-alive复用tcp连接
7:nginx长连接keep-alive与pipeline
8:理解http层和tcp层的keep-alive

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值