Chinaunix首页 | 论坛 | 博客
  • 博客访问: 434138
  • 博文数量: 138
  • 博客积分: 4114
  • 博客等级: 上校
  • 技术积分: 1341
  • 用 户 组: 普通用户
  • 注册时间: 2007-10-14 20:41
文章分类

全部博文(138)

文章存档

2014年(1)

2013年(2)

2012年(78)

2011年(13)

2010年(34)

2009年(10)

我的朋友

分类: LINUX

2012-03-13 15:34:50

本文链接地址:

这次主要来看nginx如何处理一个http的流程,也就是接收请求,解析,然后接收完毕,然后开始发送数据,这一系列是如何流转起来的,通过上2篇,我们知道了nginx初始化完毕之后会休眠在epoll(或者kqueue等等).
下面就是nginx的事件处理流程图.


然后我们就来看ngx_event_accept函数,这个函数被调用是当listen 句柄有可读事件之后才被调用,它会accept到一个新的句柄,然后设置新的句柄的回调,然后再次返回。而这个新的句柄的回调将会进入nginx的整个http的处理机。

这个函数也是比较长的,我们来一段段的看。

首先下面这段就是accpet 句柄 ,这里我要得瑟一下,accept4 这个系统调用,是我给nginx发的patch,然后被igor吸收加到nginx的0.9 develop里面的。

        socklen = NGX_SOCKADDRLEN;
//开始accept句柄
#if (NGX_HAVE_ACCEPT4)
        s = accept4(lc->fd, (struct sockaddr *) sa, &socklen, SOCK_NONBLOCK);
#else
        s = accept(lc->fd, (struct sockaddr *) sa, &socklen);
#endif

接下来就是从连接池取得连接,然后创建连接里面包含的数据结构。

 

        c = ngx_get_connection(s, ev->log);
 
        if (c == NULL) {
            if (ngx_close_socket(s) == -1) {
                ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
                              ngx_close_socket_n " failed");
            }
 
            return;
        }
 
#if (NGX_STAT_STUB)
        (void) ngx_atomic_fetch_add(ngx_stat_active, 1);
#endif
//创建内存池
        c->pool = ngx_create_pool(ls->pool_size, ev->log);
        if (c->pool == NULL) {
            ngx_close_accepted_connection(c);
            return;
        }
//分配客户端地址
        c->sockaddr = ngx_palloc(c->pool, socklen);
        if (c->sockaddr == NULL) {
            ngx_close_accepted_connection(c);
            return;
        }
 
        ngx_memcpy(c->sockaddr, sa, socklen);
//分配log
        log = ngx_palloc(c->pool, sizeof(ngx_log_t));
        if (log == NULL) {
            ngx_close_accepted_connection(c);
            return;
        }

接下来就是初始化从来连接池取出来的连接。主要是发送,接受回调,以及客户端地址等等。

 

        *log = ls->log;
//设置读取的回调,这里依赖于操作系统。以后会详细介绍nginx的event框架
        c->recv = ngx_recv;
        c->send = ngx_send;
        c->recv_chain = ngx_recv_chain;
        c->send_chain = ngx_send_chain;
 
        c->log = log;
        c->pool->log = log;
//设置client的ip地址
        c->socklen = socklen;
        c->listening = ls;
        c->local_sockaddr = ls->sockaddr;
 
        c->unexpected_eof = 1;
 
#if (NGX_HAVE_UNIX_DOMAIN)
        if (c->sockaddr->sa_family == AF_UNIX) {
            c->tcp_nopush = NGX_TCP_NOPUSH_DISABLED;
            c->tcp_nodelay = NGX_TCP_NODELAY_DISABLED;
#if (NGX_SOLARIS)
            /* Solaris's sendfilev() supports AF_NCA, AF_INET, and AF_INET6 */
            c->sendfile = 0;
#endif
        }
#endif
//准备设置读写的结构
        rev = c->read;
        wev = c->write;
 
        wev->ready = 1;
 
        if (ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT)) {
            /* rtsig, aio, iocp */
            rev->ready = 1;
        }
 
        if (ev->deferred_accept) {
            rev->ready = 1;
#if (NGX_HAVE_KQUEUE)
            rev->available = 1;
#endif
        }
//设置log
        rev->log = log;
        wev->log = log;

最后一部分就是最关键的一部分,到达这里说明连接已经初始化完毕,句柄已经取到,这个时候就需要将连接加入到事件驱动器中,并且需要设置新的句柄的 一些回调处理函数。这里可以看到是调用ls->handler(c),而这个handler 前两篇blog 我们知道是被初始化为ngx_http_init_connection,也就是最终会调用这个函数。

 

        if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
//将连接加入到epoll
            if (ngx_add_conn(c) == NGX_ERROR) {
                ngx_close_accepted_connection(c);
                return;
            }
        }
 
        log->data = NULL;
        log->handler = NULL;
//调用回调
        ls->handler(c);
 
        if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
            ev->available--;
        }

接下来就是ngx_http_init_connection函数了,这个函数主要是设置当前句柄的读handler,如果数据可读,则直接调用request handler,如果数据不可读,则设置定时器(超时定时器),并将这个句柄挂载到事件处理器上。
这 里有一个需要注意的地方,那就是如果使用了ngx_use_accept_mutex锁的话,那么就不能够立即处理request,因为处理 request是一个非常耗时的操作,而现在在锁里面,所以此时之需要将这个读事件挂载到ngx_posted_events队列,等退出锁之后再进行处 理。

 

ngx_http_init_connection(ngx_connection_t *c)
{
    ngx_event_t         *rev;
.....................................................
 
    c->log_error = NGX_ERROR_INFO;
 
    rev = c->read;
//设置读handler.
    rev->handler = ngx_http_init_request;
    c->write->handler = ngx_http_empty_handler;
 
#if (NGX_STAT_STUB)
    (void) ngx_atomic_fetch_add(ngx_stat_reading, 1);
#endif
//如果接收准备好了,则直接调用ngx_http_init_request,如果
    if (rev->ready) {
        /* the deferred accept(), rtsig, aio, iocp */
//如果使用了mutex锁,则post 这个event,然后返回。
        if (ngx_use_accept_mutex) {
            ngx_post_event(rev, &ngx_posted_events);
            return;
        }
 
        ngx_http_init_request(rev);
        return;
    }
//添加定时器
    ngx_add_timer(rev, c->listening->post_accept_timeout);
//将事件挂载到事件处理器
    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
#if (NGX_STAT_STUB)
        (void) ngx_atomic_fetch_add(ngx_stat_reading, -1);
#endif
        ngx_http_close_connection(c);
        return;
    }
}

然后就是ngx_http_init_request,进入这个函数,说明客户端有请求过来了,此时我们就需要进入http的协议解析部分了,因此在这个函数主要就是初始化request结构,初始化完毕后进入解析处理。

 

static void
ngx_http_init_request(ngx_event_t *rev)
{
............................................
//设置handler回调
   rev->handler = ngx_http_process_request_line;
    r->read_event_handler = ngx_http_block_reading;
......................................................
//进入解析request 部分。
    rev->handler(rev);
}

最后来看ngx_http_process_request_line,这个函数就是对request line进行解析,而解析部分在我以前的blog已经分析过了,这里就不详细分析了。这里要注意一个地方。 我们知道nginx 使用的是epoll的ET模式,而et模式的话,就需要能够判断这次读取的数据是否读完,这里nginx是这样判断的,那就是根据协议来判断,也就是协议 驱动,由协议来判断是否有读取完毕。

 

    for ( ;; ) {
 
        if (rc == NGX_AGAIN) {
//读取request.
            n = ngx_http_read_request_header(r);
 
            if (n == NGX_AGAIN || n == NGX_ERROR) {
                return;
            }
        }
//然后开始parse
        rc = ngx_http_parse_request_line(r, r->header_in);
.....................................
}

然后当parse结束后通过rc来判断解析的结果,如果是NGX_OK则说明header解析完毕,如果是NGX_AGAIN,则说明header只解析了一部分。我们这里主要来看NGX_OK的情况,就是当request line完全解析完毕时,nginx做什么。

 

        if (rc == NGX_OK) {
........................................
            c->log->action = "reading client request headers";
//设置并调用ngx_http_process_request_headers执行后续操作(解析header)
            rev->handler = ngx_http_process_request_headers;
            ngx_http_process_request_headers(rev);
}

ngx_http_process_request_headers这个函数主要是解析http的request header,

 

static void
ngx_http_process_request_headers(ngx_event_t *rev)
{
........................................................
   rc = NGX_AGAIN;
 
    for ( ;; ) {
 
        if (rc == NGX_AGAIN) {
..........................................
            n = ngx_http_read_request_header(r);
 
            if (n == NGX_AGAIN || n == NGX_ERROR) {
                return;
            }
        }
        rc = ngx_http_parse_header_line(r, r->header_in,
                                        cscf->underscores_in_headers);
...................................................
        if (rc == NGX_OK) {
//这个判断里面会设置request中的预制header(cookie, id_modify_since等等)
....................................
        }
        if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
//到达这里说明header解析完毕.
                     r->request_length += r->header_in->pos - r->header_in->start;
 
            r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
//这里主要是进行一些头的校验
            rc = ngx_http_process_request_header(r);
 
            if (rc != NGX_OK) {
                return;
            }
//然后进入nginx的http处理(进入phase处理)
            ngx_http_process_request(r);
 
            return;
         }
.........................
}

ngx_http_process_request就不分析了,这个函数在以前分析sub request以及nginx的handler处理的时候都有介绍过。它会调用nginx的handler phase处理链表。

 


阅读(868) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~