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