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

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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值