12.3 与上游服务器建立连接

upstream机制与上游服务器是通过TCP建立连接的,众所周知,建立TCP连接需要三次握手,而三次握手消耗的时间是不可控的。为了保证建立TCP连接这个操作不会阻塞进程,Nginx使用无阻塞的套接字来连接上游服务器。图12-2的第5步调用的ngx_http_upstream_connect方法就是用来连接上游服务器的,由于使用了非阻塞的套接字,当方法返回时与上游之间的TCP连接未必会成功建立,可能还需要等待上游服务器返回TCP的SYN/ACK包。因此,ngx_http_upstream_connect方法主要负责发起建立连接这个动作,如果这个方法没有立刻返回成功,那么需要在epoll中监控这个套接字,当它出现可写事件时,就说明连接已经建立成功了。

在图12-3中可以看到,如果连接立刻成功建立,在第9步就会开始向上游服务器发送请求,如果连接没有马上建立成功,在第8步就会将这个连接的写事件加入到epoll中,等待连接上的可写事件被触发后,回调ngx_http_upstream_send_request方法发送请求给上游服务器。

12.3 与上游服务器建立连接 - 图1

图 12-3 ngx_http_upstream_connect方法的流程图

下面详细说明图12-3中每个步骤的意义。

1)调用socket方法建立一个TCP套接字,同时,这个套接字需要设置为非阻塞模式。

2)由于Nginx的事件框架要求每个连接都由一个ngx_connection_t结构体来承载,因此这一步将调用ngx_get_connection方法,由ngx_cycle_t核心结构体中free_connections指向的空闲连接池处获取到一个ngx_connection_t结构体,作为承载Nginx与上游服务器间的TCP连接。

3)第9章我们介绍过事件模块的ngx_event_actions接口,其中的add_conn方法可以将TCP套接字以期待可读、可写事件的方式添加到事件搜集器中。对于epoll事件模块来说,add_conn方法就是把套接字以期待EPOLLIN|EPOLLOUT事件的方式加入epoll中,这一步即调用add_conn方法把刚刚建立的套接字添加到epoll中,表示如果这个套接字上出现了预期的网络事件,则希望epoll能够回调它的handler方法。

4)调用connect方法向上游服务器发起TCP连接,作为非阻塞套接字,connect方法可能立刻返回连接建立成功,也可能告诉用户继续等待上游服务器的响应,对connect连接是否建立成功的检查会在第7步之后进行。注意,这里并没有涉及connect返回失败的情形,读者可以参考第11章中这种系统调用失败后的处理,本章不会讨论细节。

5)将这个连接ngx_connection_t上的读/写事件的handler回调方法都设置为ngx_http_upstream_handler。下文会介绍ngx_http_upstream_handler方法。

6)将upstream机制的write_event_handler方法设为ngx_http_upstream_send_request_handler。write_event_handler和read_event_handler的用法参见下面将要介绍的ngx_http_upstream_handler方法。这一步骤实际上决定了向上游服务器发送请求的方法是ngx_http_upstream_send_request_handler。

7)设置upstream机制的read_event_handler方法为ngx_http_upstream_process_header,也就是由ngx_http_upstream_process_header方法接收上游服务器的响应。

现在开始检查在第4步中调用connect方法连接上游服务器是否成功,如果已经连接成功,则跳到第9步执行;如果尚未收到上游服务器连接建立成功的应答,则跳到第8步执行。

8)这一步处理非阻塞的连接尚未成功建立时的动作。实际上,在第3步中,套接字已经加入到epoll中监控了,因此,这一步将调用ngx_add_timer方法把写事件添加到定时器中,超时时间就是12.1.3节中介绍的ngx_http_upstream_conf_t结构体中的connect_timeout成员,这是在设置建立TCP连接的超时时间。

9)如果已经成功建立连接,则调用ngx_http_upstream_send_request方法向上游服务器发送请求。注意,在第6步中设置的发送请求方法为ngx_http_upstream_send_request_handler,它与ngx_http_upstream_send_request方法的不同之处将在12.4节中介绍。

以上的第5、第6、第7步都与ngx_http_upstream_handler方法相关,同时我们又看到了类似ngx_http_request_t结构体中write_event_handler、read_event_handler的同名方法。实际上,ngx_http_upstream_handler方法与图11-7展示的ngx_http_request_handler方法也非常相似,下面看看它到底做了些什么。


static void ngx_http_upstream_handler(ngx_event_t*ev)

{

ngx_connection_t*c;

ngx_http_request_t*r;

ngx_http_upstream_t*u;

/*由事件的data成员取得ngx_connection_t连接。注意,这个连接并不是Nginx与客户端的连接,

而是Nginx与上游服务器间的连接*/

c=ev->data;

//由连接的data成员取得ngx_http_request_t结构体

r=c->data;

/由请求的upstream成员取得表示upstream机制的Ngx_http_upstream_t结构体/

u=r->upstream;

/注意,ngx_http_request_t结构体中的这个connection连接是客户端与Nginx间的连接/

c=r->connection;

if(ev->write){

/当Nginx与上游服务器间TCP连接的可写事件被触发时,upstream的write_event_handler方法会被调用/

u->write_event_handler(r,u);

}else{

/当Nginx与上游服务器间TCP连接的可读事件被触发时,upstream的read_event_handler方法会被调用/

u->read_event_handler(r,u);

}

/ngx_http_run_posted_requests方法正是第11章图11-12所说的方法。注意,这个参数c是来自客户端的连接,post请求的执行也与图11-12完全一致/

ngx_http_run_posted_requests(c);

}


其实,ngx_http_upstream_handler方法与第11章中介绍的ngx_http_request_handler方法几乎是一样的,它们的最后一步都是调用相同的方法执行post请求,区别只是前者将调用ngx_http_upstream_t结构体中的读写回调方法,而后者是调用ngx_http_request_t结构体中的读写回调方法。本章以下小节中都会通过ngx_http_upstream_t结构体中的write_event_handler和read_event_handler,设置与上游之间对应的读/写事件出现时的回调方法。