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 增加 SPDY 支持并测试

安装spdy nginx从1.5开始是支持spdy格式的。  http://nginx.org/en/docs/http/ngx_http_spdy_module.html#example 最新的n...
  • yangguangmeng
  • yangguangmeng
  • 2015年11月09日 11:38
  • 934

网站漏洞整理

1,xss Cross Site Script 恶意攻击者在某web页面植入恶意的html代码,当用户浏览该页面时,恶意的html代码会被执行,从而达到恶意用户的特殊目的, 比如窃取用户的用户名...
  • u013393981
  • u013393981
  • 2014年09月08日 18:22
  • 1457

nginx lua 安装spdy

关于spdy摘自 http://zh.wikipedia.org/wiki/SPDYSPDYSPDY是Google开发的基于传输控制协议(TCP)的应用层协议 。Google最早是在Chromium中...
  • freewebsys
  • freewebsys
  • 2015年02月05日 19:24
  • 1674

安装支持spdy的nginx并且通过wireshark解密spdy协议

第一步,编译安装openssl
  • chq123456
  • chq123456
  • 2014年11月02日 16:00
  • 7988

Nginx源代码分析之accept(十六)

accpet 的初始化和调用可以从work进程的启动开始讲起 在unix系平台上,work进程的主函数是ngx_worker_process_cycle ngx_worker_process_cy...
  • namelcx
  • namelcx
  • 2016年09月01日 15:05
  • 281

Nginx源代码分析之HTTP2.0(二十)

http/2在谷歌的推动下几乎就是spdy的升级,nginx目前已经加入了http2的代码,结构跟spdy有一定相似度。 其入口函数是ngx_http_v2_init...
  • namelcx
  • namelcx
  • 2016年09月13日 17:53
  • 1020

nginx 增加 spdy 支持并测试

安装spdynginx从1.5开始是支持spdy格式的。 http://nginx.org/en/docs/http/ngx_http_spdy_module.html#example最新的ngin...
  • freewebsys
  • freewebsys
  • 2015年02月05日 19:06
  • 2109

Nginx源码分析 - 主流程篇 - 解析配置文件

Nginx源码中比较重要的一块就是配置文件的解析。一般是解析/usr/local/nginx/conf/nginx.conf文件中的配置信息。前一篇文章,我们介绍了Nginx的模块化。Nginx的功能...
  • initphp
  • initphp
  • 2016年07月14日 18:47
  • 3361

CentOS 7.4源码编译nginx1.12 并且隐藏nginx的版本

1.下载      [root@localhost ~]# cd /usr/local/src/      [root@localhost src]#  wget   http://nginx.org...
  • z13615480737
  • z13615480737
  • 2017年12月26日 21:59
  • 31

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

。。。
  • namelcx
  • namelcx
  • 2016年08月09日 13:45
  • 1386
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Nginx源代码分析之spdy(十五)
举报原因:
原因补充:

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