Nginx源代码分析之spdy(十五)

原创 2016年08月30日 18:02:32

nginx作为服务端,在建立socket并listen之后,会设置accept返回的异步回调,代码在ngx_http_add_listening中:

 ls->handler = ngx_http_init_connection;


在ngx_event_accept.c的ngx_event_accept函数中会执行这个回调,再来看看ngx_http_init_connection,再前面一节已经介绍过,这里会注册读取数据的异步回调ngx_http_wait_request_handler,但如果使用的是spdy,则会用spdy的函数来覆盖这个函数指针,代码如下:

    rev = c->read;
    rev->handler = ngx_http_wait_request_handler;
    c->write->handler = ngx_http_empty_handler;


#if (NGX_HTTP_SPDY)
    if (hc->addr_conf->spdy) {
        rev->handler = ngx_http_spdy_init;
    }
#endif

ngx_http_spdy_init会初始化与spdy相关的所有配置,

ngx_http_spdy_init的最后会设置ngx_http_spdy_read_handler作为读回调,等待连接数据,如果已经有数据,则立即执行。而在ngx_http_spdy_read_handler中,首先调用异步recv请求,如果已经收到完整数据,则会执行sc->handler,这就是之前注册的ngx_http_spdy_state_head。

最后如果sc->processing 大于0,表示当前connection之上仍然有spdy流,此时直接返回,如果小于等于0,则调用ngx_http_spdy_handle_connection,这个函数用于判断是否结束当前连接,由于spdy属于长连接,在当前连接没发生错误的情况下,会设置读回调ngx_http_spdy_keepalive_handler,并保留当前的连接。


现在回头说说spdy真正的入口,即ngx_http_spdy_state_head,他会解析来自客户端的spdy请求的头部,然后根据frame_type字段来识别请求的类型,spdy的请求分成2大类,控制帧和数据帧。具体的在代码中可见:

    if (ngx_spdy_ctl_frame_check(head)) {
        type = ngx_spdy_ctl_frame_type(head);


        switch (type) {


        case NGX_SPDY_SYN_STREAM:
            return ngx_http_spdy_state_syn_stream(sc, pos, end);


        case NGX_SPDY_SYN_REPLY:
            ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
                          "client sent unexpected SYN_REPLY frame");
            return ngx_http_spdy_state_protocol_error(sc);


        case NGX_SPDY_RST_STREAM:
            return ngx_http_spdy_state_rst_stream(sc, pos, end);


        case NGX_SPDY_SETTINGS:
            return ngx_http_spdy_state_settings(sc, pos, end);


        case NGX_SPDY_PING:
            return ngx_http_spdy_state_ping(sc, pos, end);


        case NGX_SPDY_GOAWAY:
            return ngx_http_spdy_state_skip(sc, pos, end); /* TODO */


        case NGX_SPDY_HEADERS:
            ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
                          "client sent unexpected HEADERS frame");
            return ngx_http_spdy_state_protocol_error(sc);


        case NGX_SPDY_WINDOW_UPDATE:
            return ngx_http_spdy_state_window_update(sc, pos, end);


        default:
            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                           "spdy control frame with unknown type %ui", type);
            return ngx_http_spdy_state_skip(sc, pos, end);
        }
    }


    if (ngx_spdy_data_frame_check(head)) {
        sc->stream = ngx_http_spdy_get_stream_by_id(sc, head);
        return ngx_http_spdy_state_data(sc, pos, end);
    }


    ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
                  "client sent invalid frame");


    return ngx_http_spdy_state_protocol_error(sc);
}


      先来看看spdy的流的创建,当对方发送的是SYN_STREAM帧的话,,调用ngx_http_spdy_state_syn_stream来创建一个新的流,,这个函数包含大量细节,不再细说,真正创生产spdy流的是ngx_http_spdy_create_stream,它返回一个ngx_http_spdy_stream_t的结构,代表了一个流。一些关键代码在函数末尾:

    r->spdy_stream = stream;


    stream->id = id;
    stream->request = r;
    stream->connection = sc;


    stream->send_window = sc->init_window;
    stream->recv_window = NGX_SPDY_STREAM_WINDOW;


    stream->priority = priority;


    sscf = ngx_http_get_module_srv_conf(r, ngx_http_spdy_module);


    index = ngx_http_spdy_stream_index(sscf, id);


    stream->index = sc->streams_index[index];
    sc->streams_index[index] = stream;


    sc->processing++;

作用主要是把stream保存到ngx_http_spdy_stream_t的成员stgreams_index中,这就是说:1个tcp连接可以同时发起多个并行的spdy流,同时设置当前流的优先级,这个优先级是从客户端的请求包中解析出来的,还同时把当前流的指针赋值给http_request的spdy_stream,最后把processing加一。

       这个函数之后,再调用ngx_http_spdy_state_headers,spdy的头部是加密的,spdy把HTTP Header中常见的单词定义了字典,通过Zlib对Header进行压缩,压缩以帧为单位,每次压缩后做SYNC_FLUSH,但是不reset,所以压缩状态能够保存,从第二个包含Header的帧开始,压缩率大幅提升,因为HTTP请求和响应的Header重复内容很多。对于Body,spdy协议本身并不压缩,不过它规定从它这经过的HTTP请求必须强制开启GZip压缩。

压缩调用的是

z = inflate(&sc->zstream_in, Z_NO_FLUSH);


然后再处理解密的所有头部字段,大致原理是循环判断sc->entries,循环中先调用ngx_http_spdy_parse_header解析出一个name/value块,如果成功,接着调用ngx_http_spdy_handle_request_header,根据这些name/value块找到已经定义的处理函数,这些name/value块类似http的头部字段,每个头部会有一个处理函数:



        for (i = 0; i < NGX_SPDY_REQUEST_HEADERS; i++) {
            sh = &ngx_http_spdy_request_headers[i];


            if (sh->hash != r->header_hash
                || sh->len != r->header_name_end - r->header_name_start
                || ngx_strncmp(sh->header, r->header_name_start, sh->len) != 0)
            {
                continue;
            }


            return sh->handler(r);
        }

每个字段的处理函数是固定在代码中的

static ngx_http_spdy_request_header_t ngx_http_spdy_request_headers[] = {
    { 0, 6, "method", ngx_http_spdy_parse_method },
    { 0, 6, "scheme", ngx_http_spdy_parse_scheme },
    { 0, 4, "host", ngx_http_spdy_parse_host },
    { 0, 4, "path", ngx_http_spdy_parse_path },
    { 0, 7, "version", ngx_http_spdy_parse_version },
};


       这些块经过处理后,就形成了一个普通的ngx_http_request_t的请求,这时在调用ngx_http_spdy_run_request->ngx_http_process_request,这样就进入了普通http的处理流程。因此,spdy实际上是在tcp和http协议间额外加入的一个协议/协议层,主要功能是压缩头部和在同一个页面中发起多个并发的http请求,加快大页面的记载速度。

       另外,一个大页面如何分解成多个并行的spdy请求,这些细节需要看支持spdy的客户端代码,在chromium中可以找到这样的代码,以后有空再分析吧。


为了更清晰的理解spdy源码,可以看看google的spdy协议:

http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-1.2-Definitions

另外有个中文的精简版本:

http://network.51cto.com/art/201509/492693.htm



nginx lua 安装spdy

关于spdy摘自 http://zh.wikipedia.org/wiki/SPDYSPDYSPDY是Google开发的基于传输控制协议(TCP)的应用层协议 。Google最早是在Chromium中...

Nginx下让SSL支持SPDY协议

SPDY介绍 SPDY 是 Google 开发的基于传输控制协议(TCP)的应用层协议,开发组正在推动 SPDY 成为正式标准(现为互联网草案)。SPDY 协议类似于 HTTP,但旨在缩短网页的...

nginx的spdy协议

SPDY 是 Google 开发的基于传输控制协议 (TCP) 的应用层协议 ,开发组正在推动 SPDY 成为正式标准(现为互联网草案)。SPDY 协议旨在通过压缩、多路复用和优先级来缩短网页的加...

Nginx源代码分析之群惊问题(十七)

接上一节,在accept初始化的时候有一个ngx_use_accept_mutex变量,这是用来解决当多个进程在一个套接口上同时调用accept引起的群惊问题的。...
  • namelcx
  • namelcx
  • 2016年09月01日 17:26
  • 396

Nginx源代码分析之connect(四)

static ngx_int_t ngx_iocp_add_event
  • namelcx
  • namelcx
  • 2015年01月18日 22:12
  • 1157

nginx源代码分析 - 启动(五) 调试后台进程和worker进程

nginx的网络模型是,master进程,即daemon进程,负责listen+bind,所有子进程都监听80端口,注册监听fd的读事件,即accept事件。 多进程调试方法 sudo...

nginx源代码分析

Nginx可以开启多个进程,每个进程拥有最大上限128个子线程以及一定的可用连接数。如果你希望使用线程可以在配置文件中设置worker_threads这个参数,但这个参数在Nginx官方手册上没有。只...

Nginx源代码分析之I/O细节(十一)

至于每个平台和模型里面具体I/O的细节,我们简单分析一下,先看看发送的具体实现,我们先以iocp模型来进行具体分析。 在Upstream部分,最后提到真正的发送函数是一个send_chain指针,对...
  • namelcx
  • namelcx
  • 2015年07月31日 22:12
  • 539

Nginx源代码分析之反向代理(十三)

。。。
  • namelcx
  • namelcx
  • 2016年08月09日 13:45
  • 1146

Nginx源代码分析--基本数据结构--hash

Nginx对内存的使用极其苛刻,达到了令人呕吐的地步。         这里仅仅分析一个hash桶的映射过程中的一小步骤:         计算映射一个数组到hash桶的时候,所需要的桶的多少...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Nginx源代码分析之spdy(十五)
举报原因:
原因补充:

(最多只允许输入30个字)