处理连接请求

2016-1-14 chenhui Nginx

首先 Nginx 要知道监听的 IP 和 Port,这两个是在配置文件中定义的,并在 ngx_http_core_listen() 中解析,最后调用 ngx_http_add_listen() 把这些信息保存在 http 核心配置的 ngx_http_core_main_conf_t 的 ports 数组字段内。

如果不进行配置,那么他会使用默认的 80 端口和主机上的任意IP地址来进行监听。

得到 IP 和端口后,就要创建 socket 了,也就是 socket()、sockopt()、bind()、listen()。


创建完 socket 后,就要开始创建工作进程了,他们将继承刚才根据配置创建的套接字,并为这些套接字创建 connect 连接对象,并取出这个连接对象的 read 字段得到事件对象(因为要读出客户端的连接请求),这个事件对象的 handler 回调指向 ngx_event_accept() 函数,这样当这个事件对象被触发时(读对象,又是一开始创建的套接字,所以是当客户端请求连接时就会触发他),这个 ngx_event_accept() 函数会被调用,而这个函数就会连接客户端。


所谓的一个事件,就是客户端发出的一个连接或请求,Nginx 中使用 ngx_event_t 描述一个请求,ngx_event_t 是 ngx_event_s 的 typedef,所以下面看下 ngx_event_s 的声明。


struct ngx_event_s {
    
    void            *data;
    unsigned         write:1;
    unsigned         accept:1;
    unsigned         instance:1;
    unsigned         active:1;
    unsigned         disabled:1;
    unsigned         ready:1;//数据是否就绪
    unsigned         oneshot:1;
    unsigned         complete:1;

	... //还有一大堆类似上面的字段省略
	
    ngx_event_handler_pt  handler;

	...
};

struct ngx_event_s 中,handler 字段是最重要的,他是一个回调函数,在事件的各种状态下会指向不同的函数,就像用来接收客户端请求的套接字会指向 ngx_event_accept()。


连接客户端后,就要看是不是有多个工作进程,如果只有一个,就要调用 ngx_add_event() 把这个连接后的 socket 添加到监控队列,这个 ngx_add_event() 是个宏,指向不同的异步 IO,epoll 情况下指向 ngx_epoll_add_event() 。


如果有多个工作进程,那么就要考虑每个进程的负载均衡,即每个进程所维持的连接必须差不多,否则一个进程压力太大,另一个太小,会对效率产生影响。负载均衡的实现方式是,先看看自己的请求数是否超出一定的数量,如果超出,则不处理他,如果没超出,就要和其他进程抢占一个锁,抢到锁的就把这个 socket 添加到自己的监控队列里。没有抢到锁的,就放弃监听他,即直接返回了。


这里要注意,Nginx 使用水平触发(默认的),如果 A 进程没有对连接请求进行处理,那么 A 进程也好,其他进程也罢,在他返回到开头的 epoll_wait() 时,他会重新监听到这个新请求的存在,这样其他进程或自己会重新进行处理,防止出错。比如一个套接字来了两个新请求时,只会有一个进程会得到锁,但一个进程只会 accept 一次,所以第二个请求会被搁置,这是水平触发就发挥威力了,当 A 进程或其他进程返回到 epoll_wait() 时,会重新监听到第二个请求的存在。

p.s. 如果配置了 multi_accept on; 那么工作进程在 accept 客户端时,会一直不停地 accept 直到没有客户端连接请求为止。


一旦 accept() 成功连接了一个客户端连接请求,那么他就会调用 ngx_get_connection() 申请对应的连接对象,并做一些初始赋值。


一旦添加完毕后,工作进程就可以接收到这个客户端继续发过来的 HTTP 请求,并根据每一个步骤来设置 handler 回调来处理不同阶段的处理方式。










发表评论:

Copyright ©2015-2016 freehui All rights reserved