nginx平台初探(100%)

http://tengine.taobao.org/book/chapter\_02.html

初探nginx架构(100%)¶

明明,nginx性能强,而nginx的强性能及该架构是劈不上马之。那么nginx究竟是何等的也?这无异节约咱们事先来新识一下nginx框架吧。

nginx在起步后,在unix系统被见面为daemon的法门在后台运行,后台进程包含一个master进程和多单worker进程。我们啊可手动地合后台模式,让nginx在前台运行,并且通过部署于nginx取消master进程,从而得以要nginx以单进程方式运行。很明显,生产条件下我们必然不见面这么做,所以关闭后台模式,一般是为此来调节用的,在后的回中,我们会详细地讲学如何调节nginx。所以,我们得望,nginx是坐差不多进程的点子来工作的,当然nginx也是永葆多线程的法门的,只是我们主流的章程还是大抵进程的方,也是nginx的默认方式。nginx采用多进程的道来为数不少功利,所以自己就算重大教授nginx的大多进程模式吧。

才讲到,nginx在起步后,会发出一个master进程和多单worker进程。master进程主要为此来保管worker进程,包含:接收来自外的信号,向每worker进程发送信号,监控worker进程的周转状态,当worker进程退出后(异常情况下),会自动重新起动新的worker进程。而基本的纱事件,则是置身worker进程中来处理了。多单worker进程之间是针对性顶之,他们平竞争来自客户端的要,各进程并行之间是独自的。一个请,只可能在一个worker进程中拍卖,一个worker进程,不可能处理其他进程的乞求。worker进程的个数是可设置的,一般我们会装以及机具cpu核数一致,这中的来头和nginx的经过模型与事件处理模型是劈不开的。nginx的历程模型,可以由下图来表示:

图片 1

以nginx启动后,如果我们如果操作nginx,要怎么开呢?从上文中我们得以看到,master来保管worker进程,所以我们无非待跟master进程通信就尽了。master进程会接收来自外界发来之信号,再根据信号做不同的作业。所以我们若控制nginx,只需要经kill向master进程发送信号就执行了。比如kill
-HUP
pid,则是喻nginx,从容地还开nginx,我们一般用者信号来重新开nginx,或重新加载配置,因为凡从容地重新开,因此服务是匪间断的。master进程在收到及HUP信号后是怎么开的啊?首先master进程在收信号后,会预先还加载配置文件,然后重新起步新的worker进程,并向有镇的worker进程发送信号,告诉她们好体面退休了。新的worker在启动后,就开始接受新的呼吁,而尽的worker在吸纳来自master的信号后,就不再收取新的要,并且在手上经过遭到之兼具非处理了的请求处理完成后,再脱离。当然,直接为master进程发送信号,这是较一直的操作办法,nginx在0.8版后,引入了扳平文山会海令执行参数,来方便我们管理。比如,./nginx
-s reload,就是来更开nginx,./nginx -s
stop,就是来已nginx的周转。如何好的呢?我们还是拿reload来说,我们视,执行命令时,我们是启动一个新的nginx进程,而新的nginx进程在条分缕析及reload参数后,就懂得我们的目的是控制nginx来还加载配置文件了,它见面于master进程发送信号,然后对接下的动作,就和我们直接向master进程发送信号一致了。

当今,我们明白了当我们在操作nginx的时光,nginx内部做了头什么业务,那么,worker进程又是什么样处理要的也罢?我们前有涉嫌,worker进程之间是相同之,每个过程,处理要的空子吗是同样的。当我们提供80端口的http服务经常,一个一连要过来,每个过程都发或处理是连续,怎么做到的也罢?首先,每个worker进程都是从master进程fork过来,在master进程里面,先树好用listen的socket(listenfd)之后,然后再度fork出多独worker进程。所有worker进程的listenfd会在新连至时移得而读,为保险单独发生一个过程处理该连,所有worker进程在注册listenfd读事件前抢accept_mutex,抢到互斥锁的特别进程注册listenfd读事件,在念事件里调用accept接受该连。当一个worker进程在accept这个连续之后,就从头读取请求,解析呼吁,处理要,产生多少后,再回到给客户端,最后才断开连接,这样一个完的伸手虽是这么的了。我们可见到,一个告,完全是因为worker进程来处理,而且只是以一个worker进程中拍卖。

那,nginx采用这种经过模型有啊补也?当然,好处一定会多了。首先,对于每个worker进程来说,独立的进程,不欲加锁,所以省掉了锁带来的开发,同时于编程以及问题找时,也会有益于广大。其次,采用独立的过程,可以被交互之间莫会见潜移默化,一个历程退出后,其它进程还在办事,服务不会见半途而废,master进程则快启动新的worker进程。当然,worker进程的死去活来退出,肯定是先后来bug了,异常退出,会招致当前worker上之具备请求失败,不过未见面影响及拥有请求,所以下降了高风险。当然,好处还有好多,大家好逐步体会。

地方说了很多关于nginx的经过模型,接下,我们来看望nginx是哪处理事件的。

有人也许只要问了,nginx采用多worker的方来处理要,每个worker里面只出一个主线程,那能处理的连发数很单薄啊,多少个worker就能够处理多少只冒出,何来高并发呢?非也,这就是nginx的英明的处在,nginx采用了异步非阻塞的方式来拍卖要,也就是说,nginx是可以而且处理过剩只请求的。想想apache的常用工作章程(apache也发生异步非阻塞版本,但以该及自带某些模块冲突,所以无常用),每个请求会占据一个工作线程,当并发数上及几千常常,就同时发生几千之线程在处理要了。这对准操作系统来说,是单不聊之挑战,线程带来的内存占用非常非常,线程的上下文切换带来的cpu开销很怪,自然属性就达到不失去了,而这些开销了是无意思之。

怎nginx可以动用异步非阻塞的艺术来拍卖为,或者异步非阻塞到底是怎么回事呢?我们事先回到原点,看看一个呼吁的整体经过。首先,请求过来,要树立连接,然后再次接收数据,接收数据后,再发送数据。具体到系统底层,就是读写事件,而当读写事件尚无备选好经常,必然不可操作,如果未用非阻塞的章程来调用,那就是得死调用了,事件尚未备选好,那就不得不当了,等事件准备好了,你又持续吧。阻塞调用会进入本等待,cpu就会于出去被旁人用了,对单线程的worker来说,显然不合适,当网络事件更是多时,大家还在等待也,cpu空闲下来没有人因此,cpu利用率自然上未去了,更别说话高并作了。好吧,你说长程数,这同apache的线程模型有什么区别,注意,别多无谓的上下文切换。所以,在nginx里面,最忌讳阻塞的系统调用了。不要阻塞,那即便非阻塞喽。非阻塞就是,事件没有备选好,马上回到EAGAIN,告诉你,事件还未曾准备好也,你生什么,过会再来吧。好吧,你了一会,再来检查一下事件,直到事件准备好了竣工,在当下里面,你不怕好事先失举行其他业务,然后又来看望事件好了从未有过。虽然未死了,但你得时时地回复检查一下事件的状态,你可做重新多之作业了,但带的支付啊是未小之。所以,才见面来了异步非阻塞的事件处理机制,具体到系统调用就是诸如select/poll/epoll/kqueue这样的系调用。它们提供了同等种植体制,让您得以监控多个事件,调用他们是死的,但好安装过时间,在逾期时间之内,如果生事件准备好了,就回到。这种体制刚解决了我们地方的有限只问题,拿epoll为条例(在后边的例证中,我们差不多坐epoll为例子,以象征立即无异近似函数),当事件没有准备好时,放到epoll里面,事件准备好了,我们尽管夺念写,当读写返回EAGAIN时,我们用她重投入到epoll里面。这样,只要来事件准备好了,我们虽去处理它,只有当有事件还尚未准备好时,才于epoll里面等在。这样,我们就是足以并作处理大量的并作了,当然,这里的出现请求,是因非处理终结的要,线程只发一个,所以还要能处理的请当然就生一个了,只是在呼吁中进行不断地切换而已,切换为是坐异步事件无备好,而主动让出的。这里的切换是没其他代价,你得解吧循环处理多独备选好的波,事实上就是是这般的。与多线程相比,这种事件处理方式是产生好特别之优势的,不需创造线程,每个请求占用的内存为深少,没有上下文切换,事件处理非常之轻量级。并发数再多吧不会见造成无谓的资源浪费(上下文切换)。更多的连发数,只是碰头占用更多的内存而已。
我前发生对连日数进行了测试,在24G内存的机械上,处理的并发请求数达到了200万。现在的网络服务器基本还施用这种办法,这也是nginx性能高效之基本点缘由。

咱俩前面说过,推荐设置worker的个数为cpu的核数,在此地虽颇爱懂了,更多之worker数,只会促成进程来竞争cpu资源了,从而拉动不必要之上下文切换。而且,nginx为了更好的采取基本上核特性,提供了cpu亲缘性的绑定选项,我们得以某一个进程绑定以某一个核上,这样便无见面坐经过的切换带来cache的失灵。像这种小的优化在nginx中甚广泛,同时也认证了nginx作者的苦心。比如,nginx在开4独字节的字符串比较常,会将4个字符转换成为一个int型,再作比较,以调减cpu的指令数等等。

今天,知道了nginx为什么会挑这么的进程模型和事件模型了。对于一个为主的web服务器来说,事件便发生三种植档次,网络事件、信号、定时器。从点的讲解中懂得,网络事件经异步非阻塞可以很好的化解掉。如何处理信号及定时器?

首先,信号的处理。对nginx来说,有一对特定的信号,代表在一定的含义。信号会中断掉程序当前的运转,在改变状态后,继续执行。如果是系调用,则可能会见导致系统调用的破产,需要重入。关于信号的处理,大家可以学习有专业书籍,这里不多说。对于nginx来说,如果nginx正在等候事件(epoll_wait时),如果程序收到信号,在信号处理函数处理完毕后,epoll_wait会返回错误,然后程序可更上epoll_wait调用。

除此以外,再来看看定时器。由于epoll_wait等函数在调用的时光是可以安装一个超时时间的,所以nginx借助这个超时时间来贯彻定时器。nginx里面的定时器事件是位于同粒维护定时器的吉黑树间,每次在进epoll_wait前,先由该红黑树里面用到拥有定时器事件之极端小时间,在测算出epoll_wait的超时时间晚进入epoll_wait。所以,当没事件有,也从没刹车信号时,epoll_wait会超时,也就是说,定时器事件及了。这时,nginx会检查有的晚点事件,将她们之状态设置为过,然后再次错过处理网络事件。由此可看出,当我们描绘nginx代码时,在拍卖网络事件之回调函数时,通常做的第一独事情虽是判断超时,然后再度错过处理网络事件。

咱得以据此同段伪代码来总结一下nginx的事件处理模型:

 

while (true) {
    for t in run_tasks:
        t.handler();
    update_time(&now);
    timeout = ETERNITY;
    for t in wait_tasks: /* sorted already */
        if (t.time <= now) {
            t.timeout_handler();
        } else {
            timeout = t.time - now;
            break;
        }
    nevents = poll_function(events, timeout);
    for i in nevents:
        task t;
        if (events[i].type == READ) {
            t.handler = read_handler;
        } else { /* events[i].type == WRITE */
            t.handler = write_handler;
        }
        run_tasks_add(t);
}

吓,本节咱们说了经过模型,事件模型,包括网络事件,信号,定时器事件。

 

nginx基础概念(100%)¶

 

connection¶

于nginx中connection就是本着tcp连接的包,其中囊括连续的socket,读事件,写事件。利用nginx封装的connection,我们可很有利之采取nginx来处理和连接相关的工作,比如,建立连接,发送和受多少等。而nginx中的http请求的拍卖便是白手起家以connection之上的,所以nginx不仅可以当做一个web服务器,也得以当作邮件服务器。当然,利用nginx提供的connection,我们得跟其他后端服务打交道。

重组一个tcp连接的生命周期,我们省nginx是什么样处理一个老是的。首先,nginx在开行时,会分析配置文件,得到需要监听的端口和ip地址,然后以nginx的master进程里面,先初始化好这个监控之socket(创建socket,设置addrreuse等选,绑定到指定的ip地址端口,再listen),然后再fork出多独分支进程出来,然后子进程会竞争accept新的接连。此时,客户端就可以为nginx发起连接了。当客户端和服务端通过三不好握手建立好一个连连后,nginx的之一一个子进程会accept成功,得到这个建立好之连日的socket,然后创建nginx对连续的包装,即ngx_connection_t结构体。接着,设置读写事件处理函数并补充加读写事件来和客户端进行多少的置换。最后,nginx或客户端来积极关闭连接,到是,一个总是就寿终正告一段落了。

理所当然,nginx也是好作为客户端来请求其它server的数码的(如upstream模块),此时,与其余server创建的连日,也封装在ngx_connection_t中。作为客户端,nginx先拿走一个ngx_connection_t结构体,然后创建socket,并设置socket的性质(
比如非阻塞)。然后又经过长读写事件,调用connect/read/write来调用连接,最后关掉连接,并释放ngx_connection_t。

当nginx中,每个过程会时有发生一个总是数之极端特别上限,这个上限和系统针对fd的限定不雷同。在操作系统中,通过ulimit
-n,我们得抱一个历程所能打开的fd的无限深累,即nofile,因为每个socket连接会占用少一个fd,所以马上吗会见限制我们经过的极致大连接数,当然也会见直接影响及我们先后所能支撑之顶可怜并发数,当fd用完后,再创socket时,就会砸。nginx通过安装worker_connectons来装每个过程支持之极端大连接数。如果该值大于nofile,那么实际上的卓绝大连接数是nofile,nginx会来警告。nginx在落实时,是透过一个连接池来管理之,每个worker进程都发一个独立的连接池,连接池的大小是worker_connections。这里的连接池里面保存之莫过于不是动真格的的接连,它才是一个worker_connections大小的一个ngx_connection_t结构的数组。并且,nginx会通过一个链表free_connections来保存有的空ngx_connection_t,每次得到一个总是时,就由闲暇连接链表中拿走一个,用完后,再放开回空闲连接链表里面。

每当此处,很多丁会晤误解worker_connections这个参数的意,认为是价就是是nginx所能够立连接的顶特别价值。其实不然,这个价值是象征每个worker进程所能成立连接的绝可怜价值,所以,一个nginx能建之太大连接数,应该是worker_connections
*
worker_processes。当然,这里说的凡极其大连接数,对于HTTP请求当地资源来说,能够支持的不过可怜出现数量是worker_connections
*
worker_processes,而设是HTTP作为反向代理来说,最酷出现数量应该是worker_connections
*
worker_processes/2。因为作为反向代理服务器,每个并发会建立和客户端的连接和跟后端服务的连续,会占少个连。

那么,我们面前来说了一个客户端连接过来后,多只空之长河,会竞争是连续,很爱见到,这种竞争会招致未公平,如果某个进程取得accept的时机比多,它的空连接很快便就此完了,如果未提前做有决定,当accept到一个新的tcp连接后,因为无法取空闲连接,而且无法拿这个连续转交给另外进程,最终会造成这tcp连接得不交拍卖,就暂停掉了。很明朗,这是免公道的,有的经过来暇连接,却绝非处理时,有的经过因为从没空闲连接,却人为地废弃连接。那么,如何缓解此问题吧?首先,nginx的处理得预打开accept_mutex选项,此时,只有获得了accept_mutex的过程才会错过添加accept事件,也就是说,nginx会控制过程是否添加accept事件。nginx使用一个被ngx_accept_disabled的变量来决定是否去竞争accept_mutex锁。在第一段子代码中,计算ngx_accept_disabled的价,这个价是nginx单进程的备连接总数的八分之一,减去多余的空余连接数量,得到的是ngx_accept_disabled有一个法则,当剩余连接数小于总连接数的八分之一时,其值才大于0,而且剩余的接连数更是聊,这个价更充分。再拘留第二段落代码,当ngx_accept_disabled大于0时,不见面失去品尝获得accept_mutex锁,并且将ngx_accept_disabled减1,于是,每次执行到这里时,都见面错过减1,直到小于0。不错过得到accept_mutex锁,就是当让出获取连接的机遇,很明确好看出,当空余连接越少时,ngx_accept_disable越老,于是让出的火候便越来越多,这样任何进程取得锁之机会呢不怕更老。不去accept,自己的连续就决定下了,其它进程的连接池就会见赢得利用,这样,nginx就控制了多进程之中一连的平衡了。

 

ngx_accept_disabled = ngx_cycle->connection_n / 8
    - ngx_cycle->free_connection_n;

if (ngx_accept_disabled > 0) {
    ngx_accept_disabled--;

} else {
    if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
        return;
    }

    if (ngx_accept_mutex_held) {
        flags |= NGX_POST_EVENTS;

    } else {
        if (timer == NGX_TIMER_INFINITE
                || timer > ngx_accept_mutex_delay)
        {
            timer = ngx_accept_mutex_delay;
        }
    }
}

哼了,连接就先行介绍至当下,本章的目的是介绍基本概念,知道当nginx中连连是单什么东西就推行了,而且连续是属于比较高级的用法,在后头的模块出高档篇会起特别的区块来讲学连接和事件之贯彻和使用。

 

request¶

顿时节咱们讲request,在nginx中我们依靠的是http请求,具体到nginx中的数据结构是ngx_http_request_t。ngx_http_request_t是对一个http请求的包。
我们明白,一个http请求,包含呼吁求行、请求头、请求体、响应实施、响应头、响应体。

http请求是百里挑一的请-响应类型的之网络协议,而http是文件协议,所以我们在分析请求行与请求头,以及出口响应行与响应头,往往是单排一行的拓处理。如果我们团结来描写一个http服务器,通常以一个连起好后,客户端会发送请求过来。然后我们读取一行数,分析产生要求行中包含的method、uri、http_version信息。然后再次同履一行处理要求头,并基于请求method与请求头的音信来支配是否发生请求求体以及请求体的长,然后又失去读取请求体。得到请求后,我们处理要产生需要输出的数额,然后再生成响应行,响应头以及响应体。在用应发送给客户端后,一个完好无缺的要虽处理了了。当然这是无与伦比简易的webserver的处理方式,其实nginx也是这么做的,只是来一对很小的别,比如,当请求头读取完成后,就从头进行呼吁的拍卖了。nginx通过ngx_http_request_t来保存解析呼吁和输出响应相关的数据。

那接下去,简要讲说nginx是什么处理一个圆的呼吁的。对于nginx来说,一个央是自ngx_http_init_request开始的,在这函数中,会安装读事件吧ngx_http_process_request_line,也就是说,接下去的大网事件,会由于ngx_http_process_request_line来执行。从ngx_http_process_request_line的函数曰,我们好看看,这就是是来处理要求行的,正好跟事先说的,处理要的首先宗事即是拍卖要求行是相同的。通过ngx_http_read_request_header来读取请求数据。然后调用ngx_http_parse_request_line函数来分析呼吁求行。nginx为提高效率,采用状态机来解析呼吁求行,而且在进行method的比较常,没有一直利用字符串比较,而是将季单字符转换成为一个整型,然后同糟糕比较以减小cpu的指令数,这个前面有说罢。很多总人口唯恐大懂一个请求行包含呼吁的方,uri,版本,却无明了其实以求行中,也是好蕴涵有host的。比如一个求GET   
http://www.taobao.com/uri
HTTP/1.0如此一个请求求行也是法定的,而且host是www.taobao.com,这个时段,nginx会忽略请求头中之host域,而以求求行中的这吧按来探寻虚拟主机。另外,对于对http0.9本来说,是匪支持请求头的,所以这边呢是要特别的拍卖。所以,在末端解析呼吁求头时,协议版本都是1.0要1.1。整个请求行解析到的参数,会保留及ngx_http_request_t结构当中。

以条分缕析完请求行后,nginx会设置读事件之handler为ngx_http_process_request_headers,然后继续的请求虽于ngx_http_process_request_headers中开展读取与分析。ngx_http_process_request_headers函数用来读取请求头,跟请求行一样,还是调用ngx_http_read_request_header来读取请求头,调用ngx_http_parse_header_line来分析一行要求头,解析及之请头会保存至ngx_http_request_t的域headers_in中,headers_in是一个链表结构,保存有的恳求头。而HTTP中有些要是索要特地处理的,这些请求头与请求处理函数存放于一个映射表里面,即ngx_http_headers_in,在初始化时,会转变一个hash表,当各国解析及一个伸手求头后,就见面事先在是hash表中找,如果起找到,则调用相应的处理函数来拍卖这请求头。比如:Host头的处理函数是ngx_http_process_host。

当nginx解析及片个回车换行符时,就表示求求头的利落,此时即令会见调用ngx_http_process_request来拍卖要了。ngx_http_process_request会设置当前的连的读写事件处理函数为ngx_http_request_handler,然后重新调用ngx_http_handler来真的开始拍卖一个完全的http请求。这里或许比较奇怪,读写事件处理函数都是ngx_http_request_handler,其实当此函数中,会因当下事件是朗诵事件或者写事件,分别调用ngx_http_request_t中的read_event_handler或者是write_event_handler。由于这,我们的伸手求头已经读博就了,之前有说了,nginx的做法是优先不念博请求body,所以马上其中我们设置read_event_handler为ngx_http_block_reading,即无读博多少了。刚才说到,真正开始拍卖多少,是在ngx_http_handler这个函数里面,这个函数会设置write_event_handler为ngx_http_core_run_phases,并执行ngx_http_core_run_phases函数。ngx_http_core_run_phases这个函数将实行多号要处理,nginx将一个http请求的处理分为多独号,那么这函数就是行这些号来发多少。因为ngx_http_core_run_phases最后会起多少,所以我们便老大爱懂,为什么设置写事件之处理函数为ngx_http_core_run_phases了。在此,我大概说明了瞬间函数的调用逻辑,我们得理解最终是调用ngx_http_core_run_phases来拍卖要,产生的应头会放在ngx_http_request_t的headers_out中,这同一有的内容,我会在要处理流程中去道。nginx的各种阶段会对要进行处理,最后会调用filter来过滤数据,对数码进行加工,如truncked传输、gzip压缩等。这里的filter包括header
filter与body
filter,即针对响应头或响应体进行拍卖。filter是一个链表结构,分别产生header
filter与body filter,先实行header filter中的备filter,然后重新实践body
filter中之持有filter。在header
filter中的最后一个filter,即ngx_http_header_filter,这个filter将会遍历所有的响应头,最后索要输出的响应头在一个一连的内存,然后调用ngx_http_write_filter进行输出。ngx_http_write_filter是body
filter中之尾声一个,所以nginx首先的body信息,在经同系列之body
filter之后,最后吧会调用ngx_http_write_filter来拓展输出(有图来说明)。

这边而留意的是,nginx会将通请求头都居一个buffer里面,这个buffer的轻重通过部署项client_header_buffer_size来设置,如果用户的要头最怪,这个buffer装不生,那nginx就会见重新分配一个新的还特别的buffer来装请求头,这个大buffer可以通过large_client_header_buffers来设置,这个large_buffer这等同组buffer,比如配置4
8k,就是象征来四单8k高低的buffer可以据此。注意,为了保存要求行或请求求头的完整性,一个圆的呼吁行抑要求头,需要在一个连续的内存里面,所以,一个完好无损的求行要要求头,只见面保留于一个buffer里面。这样,如果请求行大于一个buffer的分寸,就见面返回414荒谬,如果一个呼吁头大小大于一个buffer大小,就会回去400破绽百出。在了解了这些参数的价,以及nginx实际的做法之后,在采用场景,我们就需要根据实际的需要来调动这些参数,来优化我们的次了。

拍卖流程图:

图片 2

如上这些,就是nginx中一个http请求的生命周期了。我们更望和请求相关的组成部分定义吧。

 

keepalive¶

本,在nginx中,对于http1.0以及http1.1也是支持添加连的。什么是增长连为?我们领略,http请求是依据TCP协议之上的,那么,当客户端在提倡呼吁前,需要先与服务端建立TCP连接,而各一样次于的TCP连接是需要三坏握手来确定的,如果客户端和服务端之间网络差一点,这三软相消费之工夫会较多,而且三浅相也会带来网络流量。当然,当连接断开后,也会见产生四糟的竞相,当然对用户体验来说就无重大了。而http请求是请许答式的,如果我们能懂得每个请求头与响应体的长,那么我们是得当一个连接上面执行多个请求的,这就是是所谓的长连,但前提条件是咱们先得规定要求头与响应体的尺寸。对于要来说,如果手上请需要来body,如POST请求,那么nginx就得客户端在求求头中指定content-length来表明body的尺寸,否则回400误。也就是说,请求体的长短是确定的,那么响应体的长度为?先来探http协议中有关响应body长度的确定:

  1. 对http1.0商以来,如果响应头中产生content-length头,则坐content-length的长短就可领略body的长短了,客户端在收body时,就足以按照本条尺寸来接收数据,接收了后,就代表此请完成了。而使没content-length头,则客户端会一直接收数据,直到劳动端主动断开连接,才代表body接收了了。
  2. 如于http1.1共谋以来,如果响应头中之Transfer-encoding为chunked传输,则表示body是流式输出,body会被分成多单片,每块的开会标识出当前块的尺寸,此时,body不欲经长来指定。如果是非chunked传输,而且发生content-length,则按content-length来接收数据。否则,如果是非chunked,并且没有content-length,则客户端接收数据,直到劳动端主动断开连接。

打者,我们好看看,除了http1.0不带来content-length以及http1.1非chunked不带来content-length外,body的尺寸是会的。此时,当服务端在出口完body之后,会可以设想下长连。能否采用长连,也是发生原则限制的。如果客户端的要头着之connection为close,则意味客户端需要关闭长连,如果也keep-alive,则客户端需要开拓长连,如果客户端的恳求被从来不connection这个腔,那么根据商事,如果是http1.0,则默认为close,如果是http1.1,则默认为keep-alive。如果结果为keepalive,那么,nginx在输出完响应体后,会设置当前接连的keepalive属性,然后等待客户端下同样次于呼吁。当然,nginx不可能直接待下,如果客户端直接未发多少恢复,岂不是直接占有是连续?所以当nginx设置了keepalive等待下一样浅的要时,同时为会安装一个极其可怜待时,这个日子是经挑选项keepalive_timeout来安排的,如果安排为0,则表示关掉keepalive,此时,http版本无论是1.1尚是1.0,客户端的connection不管是close还是keepalive,都见面强制为close。

而服务端最后的操纵是keepalive打开,那么以应的http头里面,也会见包含有connection头域,其值是”Keep-Alive”,否则即是”Close”。如果connection值为close,那么以nginx响应了数据后,会再接再厉关闭连接。所以,对于请求量比较好之nginx来说,关掉keepalive最后会发比多的time-wait状态的socket。一般的话,当客户端的一模一样潮访,需要数拜与一个server时,打开keepalive的优势很好,比如图片服务器,通常一个网页会蕴藏很多只图片。打开keepalive也会见大量抽time-wait的数目。

 

pipe¶

每当http1.1受,引入了相同种新的特性,即pipeline。那么什么是pipeline呢?pipeline其实就是流程作业,它可以看作为keepalive的如出一辙种提高,因为pipeline也是冲长连的,目的就是是行使一个总是做往往请。如果客户端要提交多单请求,对于keepalive来说,那么第二只请求,必须使对等交第一独请求的响应接收了后,才会倡导,这跟TCP的停止等待协议是一致的,得到两只响应的流年至少为2*RTT。而对pipeline来说,客户端不必等交第一独请求处理完后,就好即时发起第二个请求。得到两单响应的时空可能能够达到1*RTT。nginx是直接支持pipeline的,但是,nginx对pipeline中之大半只请求的处理也休是互的,依然是一个请接一个请的处理,只是在拍卖第一单请求的上,客户端就得倡导第二独请求。这样,nginx利用pipeline减少了处理了一个求后,等待第二单请求的伸手头数据的时刻。其实nginx的做法十分粗略,前面说交,nginx在读取数据时,会将读取的数量放到一个buffer里面,所以,如果nginx在处理完前一个呼吁后,如果发现buffer里面还有数目,就认为剩下的数码是生一个求的上马,然后便接通下去处理下一个要,否则便安装keepalive。

 

lingering_close¶

lingering_close,字面意思就是是推关闭,也就是说,当nginx要关连接时,并非就关闭连接,而是先关tcp连接的刻画,再伺机一段时间后再度关连接的诵读。为什么要这样啊?我们先来瞧这样一个观。nginx在收到客户端的乞求时,可能是因为客户端或劳动端起错了,要这响应错误信息给客户端,而nginx在响应错误信息后,大分部情况下是得关闭时连连。nginx执行完write()系统调用把错误信息发送给客户端,write()系统调用返回成功并无意味数据已发送到客户端,有或还于tcp连接的write
buffer里。接着要直白执行close()系统调用关闭tcp连接,内核会首先检查tcp的read
buffer里有没有发生客户端发送过来的多少留于内核态没有给用户态进程读取,如果出则发送给客户端RST报文来关闭tcp连接丢弃write
buffer里的数量,如果无则等待write
buffer里的数发送了,然后再次经过正规的4糟分离报文断开连接。所以,当以某些场景下出现tcp
write
buffer里的数额以write()系统调用之后至close()系统调用执行前未曾发送完,且tcp
read
buffer里面还有多少尚未读,close()系统调用会招客户端收到RST报文且不会见用到服务端发送过来的错误信息数据。那客户端肯定会想,这服务器好霸道,动不动就reset我之连日,连个错误信息都无。

当上头这情景被,我们好看看,关键点是劳动端给客户端发送了RST包,导致自己发送的数目以客户端忽略掉了。所以,解决问题的关键是,让服务端别发RST包。再思索,我们发送RST是盖我们关掉了连,关掉连接是因咱们无思更处理这连续了,也非会见发出任何数据产生了。对于全双工的TCP连接来说,我们只有待关闭写就推行了,读好继续进行,我们无非待丢掉读到的其余数就是尽了,这样的话,当我们关掉连接后,客户端再发作过来的数量,就非会见另行接受RST了。当然最终我们要要关闭这个读端的,所以我们会安装一个过期时间,在斯时以后,就关闭读,客户端再发送数据来即使不管了,作为劳动端我会觉得,都这么长时了,发给你的错误信息也应该读到了,再慢就不关我事了,要充分就不行而RP不好了。当然,正常的客户端,在读取到数量后,会关闭连接,此时服务端就会见于过时间内关闭读端。这些正是lingering_close所做的事情。协议栈提供
SO_LINGER
这个选项,它的一致种植配备情况就是来处理lingering_close的情状的,不过nginx是温馨实现之lingering_close。lingering_close存在的含义就是来读取剩下的客户端发来的数,所以nginx会产生一个读超时时间,通过lingering_timeout选项来装,如果在lingering_timeout时间外还无收受多少,则直接关闭连接。nginx还支持设置一个究竟的读取时间,通过lingering_time来安,这个时空也尽管是nginx在闭馆写之后,保留socket的时间,客户端需要以这时刻外发送完所有的多寡,否则nginx在斯时过后,会一直关闭连接。当然,nginx是支撑配置是否打开lingering_close选项的,通过lingering_close选项来配置。
那么,我们以实际采用中,是否当开辟lingering_close呢?这个就算从未稳定的推荐值了,如Maxim
Dounin所说,lingering_close的严重性意图是维持更好之客户端兼容性,但是也用吃又多之附加资源(比如总是会一直占据在)。

即时节,我们介绍了nginx中,连接和请求的基本概念,下节,我们谈话基本的数据结构。

 

主干数据结构(99%)¶

nginx的撰稿人也追极致之高速,自己实现了无数深具有特色之nginx风格的数据结构以及公共函数。比如,nginx提供了带动长的字符串,根据编译器选项优化了的字符串拷贝函数ngx_copy等。所以,在我们描绘nginx模块时,应该尽可能调用nginx提供的api,尽管稍api只是针对glibc的宏定义。本节,我们介绍string、list、buffer、chain等一律多级最中心的数据结构及有关api的运用技巧与注意事项。

 

ngx_str_t(100%)¶

以nginx源码目录的src/core下面的ngx_string.h|c里面,包含了字符串的包裹以及字符串相关操作的api。nginx提供了一个带来长的字符串结构ngx_str_t,它的原型如下:

 

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

于结构体当中,data因于字符串数据的第一个字符,字符串的完结用长来代表,而休是由’\0’来代表了。所以,在描写nginx代码时,处理字符串的法与咱们平素使用产生非常非常的非一致,但若天天牢记,字符串不为’\0’结束,尽量使用nginx提供的字符串操作的api来操作字符串。
那么,nginx这样做来什么便宜也?首先,通过长来代表字符串长度,减少计算字符串长度的次数。其次,nginx可以重引用一段字符串内存,data可以针对任意内存,长度表示了,而无用去copy一卖好的字符串(因为要要以’\0’结束,而非可知更改原字符串,所以肯定要copy一截字符串)。我们于ngx_http_request_t结构体的积极分子被,可以找到多字符串引用一段子内存的例子,比如request_line、uri、args等等,这些字符串的data部分,都是据为于接收数据时创造buffer所指向的内存中,uri,args就没必要copy一客出来。这样的话,减少了许多非必要之内存分配和拷贝。
正是因此特性,在nginx中,必须小心之夺修改一个字符串。在修改字符串时需认真的错过考虑:是否好修改该字符串;字符串修改后,是否会见对其它的援造成影响。在后边介绍ngx_unescape_uri函数的当儿,就会见看这或多或少。但是,使用nginx的字符串会发部分题材,glibc提供的众系api函数大多是透过’\0’来表示字符串的了断,所以我们当调用系统api时,就未可知直接传入str->data了。此时,通常的做法是创建同截str->len
+
1大小的内存,然后copy字符串,最后一个字节置为’\0’。比较hack的做法是,将字符串最后一个字符的晚一个字符backup一个,然后设置也’\0’,在开完调用后,再由backup改回来,但前提条件是,你得规定这字符是足以修改的,而且是发出内存分配,不会见越界,但貌似不建议如此做。
接下来,看看nginx提供的操作字符串相关的api。

#define ngx_string(str)     { sizeof(str) - 1, (u_char *) str }

ngx_string(str)是一个庞大,它经过一个因为’\0’结尾的平凡字符串str构造一个nginx的字符串,鉴于其中使用sizeof操作符计算字符串长度,因此参数必须是一个常量字符串。

#define ngx_null_string     { 0, NULL }

概念变量时,使用ngx_null_string初始化字符串为空字符串,符串的尺寸也0,data也NULL。

#define ngx_str_set(str, text)                                               \
    (str)->len = sizeof(text) - 1; (str)->data = (u_char *) text

ngx_str_set用于安装字符串str为text,由于采取sizeof计算长度,故text必须为常量字符串。

#define ngx_str_null(str)   (str)->len = 0; (str)->data = NULL

ngx_str_null用于安装字符串str为空白,长度也0,data也NULL。

上面这四只函数,使用时必然要小心,ngx_string与ngx_null_string是“{}”格式的,故只好用于赋值时初始化,如:

 

ngx_str_t str = ngx_string("hello world");
ngx_str_t str1 = ngx_null_string;

若果向下面这样以,就会见起题目,这里涉及到c语言中对结构体变量赋值操作的语法规则,在此不开牵线。

ngx_str_t str, str1;
str = ngx_string("hello world");    // 编译出错
str1 = ngx_null_string;                // 编译出错

这种情形,可以调用ngx_str_set与ngx_str_null这点儿个函数来开:

 

ngx_str_t str, str1;
ngx_str_set(&str, "hello world");
ngx_str_null(&str1);

依C99标准,您吗得这样做:

 

ngx_str_t str, str1;
str  = (ngx_str_t) ngx_string("hello world");
str1 = (ngx_str_t) ngx_null_string;

另外如专注的凡,ngx_string与ngx_str_set在调用时,传进的字符串一定是常量字符串,否则会获得意想不到的左(因为ngx_str_set内部使用了sizeof(),如果传入的是u_char*,那么合算的是这指针的长度,而未是字符串的长)。如:

ngx_str_t str;
u_char *a = "hello world";
ngx_str_set(&str, a);    // 问题产生

另外,值得注意的凡,由于ngx_str_set与ngx_str_null实际上是鲜执行语句子,故在if/for/while等告知句被独采取得因此花括号括起来,例如:

ngx_str_t str;
if (cond)
   ngx_str_set(&str, "true");     // 问题产生
else
   ngx_str_set(&str, "false");    // 问题产生

 

void ngx_strlow(u_char *dst, u_char *src, size_t n);

拿src的前n个字符转换成为稍写存放于dst字符串当中,调用者需要保证dst指向的空中大于等于n,且对的空间要可写。操作不见面对原字符串产生变动。如一旦改成原字符串,可以:

 

ngx_strlow(str->data, str->data, str->len);

 

ngx_strncmp(s1, s2, n)

区别轻重缓急写的字符串比较,只于前面n个字符。

 

ngx_strcmp(s1, s2)

分轻重缓急写的非带长的字符串比较。

 

ngx_int_t ngx_strcasecmp(u_char *s1, u_char *s2);

无区分轻重缓急写的未带长的字符串比较。

 

ngx_int_t ngx_strncasecmp(u_char *s1, u_char *s2, size_t n);

勿分轻重缓急写的带来长的字符串比较,只比较前面n个字符。

 

u_char * ngx_cdecl ngx_sprintf(u_char *buf, const char *fmt, ...);
u_char * ngx_cdecl ngx_snprintf(u_char *buf, size_t max, const char *fmt, ...);
u_char * ngx_cdecl ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...);

地方立三单函数用于字符串格式化,ngx_snprintf的亚独参数max指明buf的半空中大小,ngx_slprintf则透过last来指明buf空间的深浅。推荐用第二独或第三个函数来格式化字符串,ngx_sprintf函数还是比较危险的,容易生出缓冲区溢起纰漏。在即时同多样函数中,nginx在兼容glibc中格式化字符串的款型外,还上加了有的福利格式化nginx类型的有些转义字符,比如%V用于格式化ngx_str_t结构。在nginx源文件之ngx_string.c中出征:

 

/*
 * supported formats:
 *    %[0][width][x][X]O        off_t
 *    %[0][width]T              time_t
 *    %[0][width][u][x|X]z      ssize_t/size_t
 *    %[0][width][u][x|X]d      int/u_int
 *    %[0][width][u][x|X]l      long
 *    %[0][width|m][u][x|X]i    ngx_int_t/ngx_uint_t
 *    %[0][width][u][x|X]D      int32_t/uint32_t
 *    %[0][width][u][x|X]L      int64_t/uint64_t
 *    %[0][width|m][u][x|X]A    ngx_atomic_int_t/ngx_atomic_uint_t
 *    %[0][width][.width]f      double, max valid number fits to %18.15f
 *    %P                        ngx_pid_t
 *    %M                        ngx_msec_t
 *    %r                        rlim_t
 *    %p                        void *
 *    %V                        ngx_str_t *
 *    %v                        ngx_variable_value_t *
 *    %s                        null-terminated string
 *    %*s                       length and string
 *    %Z                        '\0'
 *    %N                        '\n'
 *    %c                        char
 *    %%                        %
 *
 *  reserved:
 *    %t                        ptrdiff_t
 *    %S                        null-terminated wchar string
 *    %C                        wchar
 */

这里特别而提示的是,我们最常用于格式化ngx_str_t结构,其相应的转义符是%V,传于函数的大势所趋要是依赖针类型,否则程序即使见面coredump掉。这吗是咱最为易犯之错。比如:

ngx_str_t str = ngx_string("hello world");
char buffer[1024];
ngx_snprintf(buffer, 1024, "%V", &str);    // 注意,str取地址

 

void ngx_encode_base64(ngx_str_t *dst, ngx_str_t *src);
ngx_int_t ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src);

顿时点儿单函数用于对str进行base64编码和解码,调用前,需要保证dst中生出足够的上空来存放在结果,如果不晓具体尺寸,可事先调用ngx_base64_encoded_length与ngx_base64_decoded_length来预估最充分占用空间。

 

uintptr_t ngx_escape_uri(u_char *dst, u_char *src, size_t size,
    ngx_uint_t type);

针对src进行编码,根据type来仍不同之不二法门展开编码,如果dst为NULL,则赶回需要转义的字符的数码,由这而收获需要之上空大小。type的类别可以是:

#define NGX_ESCAPE_URI         0
#define NGX_ESCAPE_ARGS        1
#define NGX_ESCAPE_HTML        2
#define NGX_ESCAPE_REFRESH     3
#define NGX_ESCAPE_MEMCACHED   4
#define NGX_ESCAPE_MAIL_AUTH   5

 

void ngx_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type);

针对src进行反编码,type可以是0、NGX_UNESCAPE_URI、NGX_UNESCAPE_REDIRECT这三只价。如果是0,则象征src中的富有字符都使拓展转码。如果是NGX_UNESCAPE_URI与NGX_UNESCAPE_REDIRECT,则遇到’?’后便得了了,后面的字符就凭了。而NGX_UNESCAPE_URI与NGX_UNESCAPE_REDIRECT之间的区分是NGX_UNESCAPE_URI对于遇到的消转码的字符,都见面转码,而NGX_UNESCAPE_REDIRECT则光见面针对非可见字符进行转码。

 

uintptr_t ngx_escape_html(u_char *dst, u_char *src, size_t size);

对html标签进行编码。

本,我这里只介绍了一些常用之api的采取,大家可优先熟悉一下,在实质上应用过程被,遇到不理解的,最抢最直白的办法就是是失去看源码,看api的兑现或押nginx自身调用api的地方是怎开的,代码就是不过好的文档。

 

ngx_pool_t(100%)¶

ngx_pool_t是一个分外重大的数据结构,在许多要之场所都生使用,很多根本之数据结构也都于利用其。那么其究竟是一个呀东西啊?简单的游说,它提供了一样种机制,帮助管理均等多重之资源(如内存,文件等),使得对这些资源的以以及假释统一进行,免除了采用过程被考虑到对形形色色资源的哎时候释放,是否遗漏了放的顾虑。

如对于内存的管理,如果我们要用内存,那么连打一个ngx_pool_t的对象吃落内存,在结尾的某部时刻,我们销毁之ngx_pool_t对象,所有这些内存都被假释了。这样我们不怕不必要对针对这些内存进行malloc和free的操作,不用操心是不是某块被malloc出来的内存没有受放出。因为当ngx_pool_t对象被销毁之时光,所有自这目标中分红出来的内存都见面为合释放掉。

复按照我们如果用相同密密麻麻之公文,但是咱开拓之后,最终用还关,那么我们即便将这些文件统一登记及一个ngx_pool_t对象中,当这个ngx_pool_t对象被销毁之时光,所有这些文件还以见面吃关闭。

起上面举的星星点点独例证中我们好看看,使用ngx_pool_t这个数据结构的时节,所有的资源的释放都以斯目标为销毁之随时,统一进行了放,那么就是见面带动一个题材,就是这些资源的活着周期(或者说被占用的时日)是跟ngx_pool_t的生周期基本一致(ngx_pool_t也提供了少量操作可以提前出狱资源)。从最高效的角度来说,这并无是无与伦比好之。比如,我们需要各个使用A,B,C三单资源,且以完B的上,A就无见面重新让下了,使用C的时段A和B都未会见为使用到。如果不使用ngx_pool_t来管理就三个资源,那我们恐怕从系统里头申请A,使用A,然后以释放A。接着申请B,使用B,再自由B。最后申请C,使用C,然后释放C。但是当我们下一个ngx_pool_t对象来保管就三单资源的早晚,A,B和C的放出是当终极一块发出的,也就算是以使完C以后。诚然,这在合理上添了次于一段时间的资源使用量。但是及时吗减轻了程序员分别管理三个资源的生命周期的行事。这也就是有所得,必有所失之理。实际上是一个增选的题目,要看以切实可行的景况下,你更在乎的凡孰。

可以关押一下于nginx里面一个榜首的运用ngx_pool_t的情景,对于nginx处理的每个http
request, nginx会生成一个ngx_pool_t对象与这个http
request关联,所有处理过程中需报名之资源且由之ngx_pool_t对象中落,当以此http
request处理得之后,所有以处理过程中申请之资源,都以趁着这个涉及的ngx_pool_t对象的灭绝而自由。

ngx_pool_t相关结构及操作为定义在文书src/core/ngx_palloc.h|c中。

 

typedef struct ngx_pool_s        ngx_pool_t;

struct ngx_pool_s {
    ngx_pool_data_t       d;
    size_t                max;
    ngx_pool_t           *current;
    ngx_chain_t          *chain;
    ngx_pool_large_t     *large;
    ngx_pool_cleanup_t   *cleanup;
    ngx_log_t            *log;
};

从ngx_pool_t的相似使用者的角度来说,可不要关注ngx_pool_t结构中每字段作用。所以这边呢不见面进行详细的分解,当然在验证某些操作函数的使的时,如发必不可少,会展开认证。

下我们来分别诠释下ngx_pool_t的相干操作。

 

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);

始建一个发端节点大小为size的pool,log为后续在拖欠pool上拓展操作时输出日志的目标。
需要证明的是size的选料,size的高低要低于等于NGX_MAX_ALLOC_FROM_POOL,且务必大于sizeof(ngx_pool_t)。

选料超NGX_MAX_ALLOC_FROM_POOL的价值会招致浪费,因为过该限量的上空不见面让用到(只是说以首先个由ngx_pool_t对象管理之外存块上的内存,后续的分红要第一个内存块上之闲暇部分已经因此完,会再分配的)。

选料小于sizeof(ngx_pool_t)的值会招致程序崩溃。由于起大小的内存块中设用有些来囤ngx_pool_t这个消息本身。

当一个ngx_pool_t对象被创造以后,该对象的max字段于赋值为size-sizeof(ngx_pool_t)和NGX_MAX_ALLOC_FROM_POOL这两边中比较小之。后续的由这pool中分红的外存块,在首先块内存以完之后,如果一旦累分配的话语,就待连续由操作系统申请内存。当内存的大小小于等于max字段的时节,则分配新的外存块,链接以d这个字段(实际上是d.next字段)管理之同修链表上。当要分配的内存块是较max大之,那么自从网遭到申请的内存是给挂接在large字段管理之一律长长的链表上。我们聊把此号称大块内存链和不怎么片内存链。

 

void *ngx_palloc(ngx_pool_t *pool, size_t size);

自这个pool中分配一块吧size大小的内存。注意,此函数分配的内存的原初地址按NGX_ALIGNMENT进行了针对同。对齐操作会提高系统处理的速,但会招少量内存的荒废。

 

void *ngx_pnalloc(ngx_pool_t *pool, size_t size);

自夫pool中分配一片啊size大小的内存。但是此函数分配的内存并没有像上面的函数那样进行过针对性合。

 

void *ngx_pcalloc(ngx_pool_t *pool, size_t size);

欠函数也是分配size大小的内存,并且对分配的内存块进行了清零。内部实际上是转调用ngx_palloc实现的。

 

void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);

照指定对齐大小alignment来报名一片大小为size的内存。此处获取之内存不管大小都拿为坐大内存块链中管理。

 

ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);

对让停放大块内存链,也便是被large字段管理的等同排内存中的某块进行自由。该函数的实现是逐一遍历large管理的大块内存链表。所以效率比较低下。如果当这个链表中找到了这块内存,则放,并返NGX_OK。否则回NGX_DECLINED。

是因为这个操作效率比较低下,除非必要,也就是说这块内存非常深,确应及时放出,否则一般不欲调用。反正内存在这pool被销毁之时段,总归会都放出掉的呗!

 

ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);

ngx_pool_t中之cleanup字段管理着一个特的链表,该链表的各一样码都记录着一个异的要释放的资源。对于这个链表中每个节点所包含的资源如何去放,是自说明的。这吗尽管提供了异常非常之八面玲珑。意味着,ngx_pool_t不仅仅可以管理内存,通过之机制,也堪管理任何索要自由的资源,例如,关闭文件,或者去除文件等等。下面我们看一下是链表每个节点的类:

 

typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;
typedef void (*ngx_pool_cleanup_pt)(void *data);

struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;
    void                 *data;
    ngx_pool_cleanup_t   *next;
};
data: 指明了该节点所对应的资源。
handler: 是一个函数指针,指向一个可以释放data所对应资源的函数。该函数只有一个参数,就是data。
next: 指向该链表中下一个元素。

看到此,ngx_pool_cleanup_add这个函数的用法,我深信不疑大家还应该发生有知情了。但是这参数size是由呀打算的吧?这个
size就是如存储这个data字段所对的资源的深浅,该函数会为data分配size大小的上空。

仍我们得最终去一个文书。那我们在调用这个函数的早晚,把size指定为存储文件称的字符串的轻重,然后调用这个函数给cleanup链表中增加一起。该函数会返回新加上的斯节点。我们接下来将这节点受到的data字段拷贝为文件称。把hander字段赋值为一个勾文件的函数(当然该函数的原型要按照void
(*ngx_pool_cleanup_pt)(void *data))。

 

void ngx_destroy_pool(ngx_pool_t *pool);

欠函数就是刑满释放pool中具有的持有内存,以及各个调用cleanup字段所管理的链表中每个元素的handler字段所对的函数,来刑释解教掉所有拖欠pool管理的资源。并且将pool指向的ngx_pool_t也释放掉了,完全无可用了。

 

void ngx_reset_pool(ngx_pool_t *pool);

该函数释放pool中存有大块内存链表上的内存,小片内存链上之外存块都改也可用。但是非见面失去处理cleanup链表上之花色。

 

ngx_array_t(100%)¶

ngx_array_t是nginx内部使用的数组结构。nginx的数组结构在囤上及大家认知的C语言内置的数组有相似性,比如实际上存储数据的区域啊是相同老大块连续的内存。但是数组除了存储数据的内存以外还包含有头信息来叙述相关的一对信。下面我们由数组的概念及来详细的垂询一下。ngx_array_t的概念在src/core/ngx_array.c|h里面。

 

typedef struct ngx_array_s       ngx_array_t;
struct ngx_array_s {
    void        *elts;
    ngx_uint_t   nelts;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *pool;
};
elts: 指向实际的数据存储区域。
nelts: 数组实际元素个数。
size: 数组单个元素的大小,单位是字节。
nalloc: 数组的容量。表示该数组在不引发扩容的前提下,可以最多存储的元素的个数。当nelts增长到达nalloc 时,如果再往此数组中存储元素,则会引发数组的扩容。数组的容量将会扩展到原有容量的2倍大小。实际上是分配新的一块内存,新的一块内存的大小是原有内存大小的2倍。原有的数据会被拷贝到新的一块内存中。
pool: 该数组用来分配内存的内存池。

脚介绍ngx_array_t相关操作函数。

 

ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);

开创一个初的数组对象,并回到这个目标。

p: 数组分配内存使用的内存池;
n: 数组的初始容量大小,即在不扩容的情况下最多可以容纳的元素个数。
size: 单个元素的大小,单位是字节。

 

void ngx_array_destroy(ngx_array_t *a);

销毁该数组对象,并释放其分配的内存回内存池。

 

void *ngx_array_push(ngx_array_t *a);

每当三番五次组a上新添一个因素,并返回指向新因素的指针。需要拿返回的指针动类型转换,转换为具体的项目,然后又为新元素本身要是各级字段(如果频繁组的要素是复杂类型)赋值。

 

void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);

当三番五次组a上平添n个元素,并赶回指为这些增加元素的首只要素的职位的指针。

 

static ngx_inline ngx_int_t ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size);

假如一个数组对象是给分配在积上的,那么当调用ngx_array_destroy销毁后,如果想还使用,就可以调用此函数。

倘一个数组对象是吃分配在栈上的,那么尽管待调用此函数,进行初始化的干活以后,才堪使用。

注意事项:
由于应用ngx_palloc分配内存,数组在扩容时,旧的内存不会见叫放飞,会导致内存的浪费。因此,最好会超前规划好勤组的容量,在创造或者初始化的时段同样破搞定,避免频繁扩容,造成内存浪费。

 

ngx_hash_t(100%)¶

ngx_hash_t是nginx自己之hash表的落实。定义和贯彻在src/core/ngx_hash.h|c中。ngx_hash_t的贯彻啊与数据结构教科书上所描述的hash表的落实是差不多。对于常用的化解冲突之办法有线性探测,二糟糕探测和初步链法等。ngx_hash_t使用的凡极端常用的相同种植,也就是是开始链法,这为是STL中的hash表使用的法。

但是ngx_hash_t的实现而出夫几单醒目的表征:

  1. ngx_hash_t不像任何的hash表的实现,可以插删除元素,它不得不一糟初始化,就构建起任何hash表以后,既无能够再次去,也未克以插入元素了。
  2. ngx_hash_t的开链并无是实在的开端了一个链表,实际上是初步了相同截连接的贮存空间,几乎可用作是一个数组。这是坐ngx_hash_t在初始化的时刻,会经历一样差预计算的长河,提前把每个桶里面会发微元素放进去给计算出来,这样就是提前掌握每个桶的高低了。那么即使不需要用链表,一段连接的存储空间就够用了。这吗打必水平及省了内存的施用。

自从点的描述,我们可以看出来,这个价值更怪,越造成内存的荒废。就有限步,首先是初始化,然后就好以其间进行检索了。下面我们详细来拘禁一下。

ngx_hash_t的初始化。

 

   ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
ngx_uint_t nelts);

第一我们来拘禁一下初始化函数。该函数的率先单参数hinit是初始化的片段参数的一个会师。
names是初始化一个ngx_hash_t所要的富有key的一个数组。而nelts就是key的个数。下面先看一下ngx_hash_init_t类型,该型提供了初始化一个hash表所需要的有着力信息。

 

typedef struct {
    ngx_hash_t       *hash;
    ngx_hash_key_pt   key;

    ngx_uint_t        max_size;
    ngx_uint_t        bucket_size;

    char             *name;
    ngx_pool_t       *pool;
    ngx_pool_t       *temp_pool;
} ngx_hash_init_t;
hash: 该字段如果为NULL,那么调用完初始化函数后,该字段指向新创建出来的hash表。如果该字段不为NULL,那么在初始的时候,所有的数据被插入了这个字段所指的hash表中。
key: 指向从字符串生成hash值的hash函数。nginx的源代码中提供了默认的实现函数ngx_hash_key_lc。
max_size: hash表中的桶的个数。该字段越大,元素存储时冲突的可能性越小,每个桶中存储的元素会更少,则查询起来的速度更快。当然,这个值越大,越造成内存的浪费也越大,(实际上也浪费不了多少)。
bucket_size: 每个桶的最大限制大小,单位是字节。如果在初始化一个hash表的时候,发现某个桶里面无法存的下所有属于该桶的元素,则hash表初始化失败。
name: 该hash表的名字。
pool: 该hash表分配内存使用的pool。
temp_pool: 该hash表使用的临时pool,在初始化完成以后,该pool可以被释放和销毁掉。

脚来拘禁一下存储hash表key的数组的布局。

 

typedef struct {
    ngx_str_t         key;
    ngx_uint_t        key_hash;
    void             *value;
} ngx_hash_key_t;

key和value的意义显而易见,就不要讲了。key_hash是对key使用hash函数计算出来的值。
对当时片只结构分析到位之后,我思大家应都曾知道这个函数应该是怎样利用了吧。该函数成功初始化一个hash表以后,返回NGX_OK,否则回NGX_ERROR。

 

void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len);

以hash里面找key对应之value。实际上这里的key是针对实在的key(也就是是name)计算起底hash值。len是name的长度。

倘若搜索成功,则赶回指向value的指针,否则回NULL。

 

ngx_hash_wildcard_t(100%)¶

nginx为了处理带有通配符的域名之配合问题,实现了ngx_hash_wildcard_t这样的hash表。他可以支持少数种档次的含通配符的域名。一种植是通配符在前边的,例如:“*.abc.com”,也得望略掉星号,直接写成”.abc.com”。这样的key,可以兼容www.abc.com,qqq.www.abc.com之类的。另外一种植是通配符在终极的,例如:“mail.xxx.*”,请特别注意通配符在最后的非像放在开始之通配符可以让看略掉。这样的通配符,可以匹配mail.xxx.com、mail.xxx.com.cn、mail.xxx.net之类的域名。

有一些得证明,就是一个ngx_hash_wildcard_t类型的hash表只能分包通配符在前头之key或者是通配符在继的key。不克同时寓两栽类型的通配符的key。ngx_hash_wildcard_t类型变量的构建是由此函数ngx_hash_wildcard_init完成的,而查询是通过函数ngx_hash_find_wc_head或者ngx_hash_find_wc_tail来做的。ngx_hash_find_wc_head是询问包含通配符在前头之key的hash表的,而ngx_hash_find_wc_tail是询问包含通配符在后的key的hash表的。

脚详细说明及时几乎单函数的用法。

 

ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
    ngx_uint_t nelts);

该函数迎来构建一个足以涵盖通配符key的hash表。

hinit: 构造一个通配符hash表的一些参数的一个集合。关于该参数对应的类型的说明,请参见ngx_hash_t类型中ngx_hash_init函数的说明。
names: 构造此hash表的所有的通配符key的数组。特别要注意的是这里的key已经都是被预处理过的。例如:“*.abc.com”或者“.abc.com”被预处理完成以后,变成了“com.abc.”。而“mail.xxx.*”则被预处理为“mail.xxx.”。为什么会被处理这样?这里不得不简单地描述一下通配符hash表的实现原理。当构造此类型的hash表的时候,实际上是构造了一个hash表的一个“链表”,是通过hash表中的key“链接”起来的。比如:对于“*.abc.com”将会构造出2个hash表,第一个hash表中有一个key为com的表项,该表项的value包含有指向第二个hash表的指针,而第二个hash表中有一个表项abc,该表项的value包含有指向*.abc.com对应的value的指针。那么查询的时候,比如查询www.abc.com的时候,先查com,通过查com可以找到第二级的hash表,在第二级hash表中,再查找abc,依次类推,直到在某一级的hash表中查到的表项对应的value对应一个真正的值而非一个指向下一级hash表的指针的时候,查询过程结束。这里有一点需要特别注意的,就是names数组中元素的value值低两位bit必须为0(有特殊用途)。如果不满足这个条件,这个hash表查询不出正确结果。
nelts: names数组元素的个数。

欠函数执行成功返回NGX_OK,否则NGX_ERROR。

 

void *ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);

该函数查询包含通配符在头里的key的hash表的。

hwc: hash表对象的指针。
name: 需要查询的域名,例如: www.abc.com。
len: name的长度。

该函数返回匹配的通配符对承诺value。如果无查到,返回NULL。

 

void *ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);

该函数查询包含通配符在最终的key的hash表的。
参数和返回值请参加上只函数的求证。

 

ngx_hash_combined_t(100%)¶

结品种hash表,该hash表的概念如下:

 

typedef struct {
    ngx_hash_t            hash;
    ngx_hash_wildcard_t  *wc_head;
    ngx_hash_wildcard_t  *wc_tail;
} ngx_hash_combined_t;

自夫定义显见,该型实际上包含了三独hash表,一个不足为奇hash表,一个含前奔连配符的hash表和一个带有后往过渡配符的hash表。

nginx提供该项目的图,在于提供一个便宜的器皿包含三独品类的hash表,当有隐含通配符的及莫包含通配符的相同组key构建hash表以后,以平等栽好的艺术来查询,你免待还考虑一个key到底是应该到谁项目的hash表里去查了。

布局这样平等组合hash表的下,首先定义一个欠类型的变量,再各自组织其蕴含的老三独子hash表即可。

对此此类型hash表的查询,nginx提供了一个便宜之函数ngx_hash_find_combined。

 

void *ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key,
u_char *name, size_t len);

拖欠函数在这构成hash表中,依次查询其三独子hash表,看是不是配合,一旦找到,立即回到寻找结果,也就是说要发生多单或相当,则光回第一只相当的结果。

hash: 此组合hash表对象。
key: 根据name计算出的hash值。
name: key的具体内容。
len: name的长度。

回来查询的结果,未查看及则回NULL。

 

ngx_hash_keys_arrays_t(100%)¶

大家看看于构建一个ngx_hash_wildcard_t的时段,需要对通配符的哪些key进行先期处理。这个处理起来较麻烦。而当起相同组key,这些中既出管连接配符的key,也生含通配符的key的时光。我们虽需要构建三单hash表,一个分包普通的key的hash表,一个富含前奔过渡配符的hash表,一个暗含后向连配符的hash表(或者也得以拿这三单hash表组合成一个ngx_hash_combined_t)。在这种气象下,为了给大家好之结构这些hash表,nginx提供被了之辅助项目。

拖欠品种以及相关的操作函数也定义在src/core/ngx_hash.h|c里。我们先行来拘禁一下拖欠型的定义。

 

typedef struct {
    ngx_uint_t        hsize;

    ngx_pool_t       *pool;
    ngx_pool_t       *temp_pool;

    ngx_array_t       keys;
    ngx_array_t      *keys_hash;

    ngx_array_t       dns_wc_head;
    ngx_array_t      *dns_wc_head_hash;

    ngx_array_t       dns_wc_tail;
    ngx_array_t      *dns_wc_tail_hash;
} ngx_hash_keys_arrays_t;

hsize:

快要构建的hash表的桶的个数。对于用这结构被含有的信构建的老三种类型的hash表都见面以是参数。

pool:

构建这些hash表使用的pool。

temp_pool:

当构建这个项目以及最终的老三个hash表过程被可能就此到现pool。该temp_pool可以于构建完成以后,被灭绝掉。这里只是存放临时之组成部分内存消耗。

keys:

存有非通配符key的数组。

keys_hash:

旋即是独二维数组,第一个维度代表的是bucket的数码,那么keys_hash[i]倍受存放的是具有的key算出来的hash值对hsize取模以后的值为i的key。假设来3只key,分别是key1,key2和key3假设hash值算出来以后对hsize取模的价值都是i,那么这三独key的价就相继存放于keys_hash[i][0],keys_hash[i][1],
keys_hash[i][2]。该值在调用的进程中之所以来保存及检测是否来冲突之key值,也便是是否出重复。

dns_wc_head:

放前朝过渡配符key被处理就以后的价值。比如:“*.abc.com”
被拍卖完后,变成 “com.abc.” 被寄放于是数组中。

dns_wc_tail:

寄存后为过渡配符key被处理完成后的价值。比如:“mail.xxx.*”
被处理就后,变成 “mail.xxx.” 被存放在于这个数组中。

dns_wc_head_hash:

 

该值在调用的经过遭到因故来保存及检测是否发生冲突的前向通配符的key值,也就是是是否生再度。

dns_wc_tail_hash:

 

该值在调用的进程被之所以来保存与检测是否出冲突之后向通配符的key值,也便是是否发生再。

在概念一个这项目的变量,并对字段pool和temp_pool赋值以后,就好调用函数ngx_hash_add_key把具有的key加入到这组织面临了,该函数会自动实现普通key,带前为过渡配符的key和带后为连配符的key的归类以及自我批评,并将以此来值存放到相应的字段中失,
然后即便得经检查这个结构体中的keys、dns_wc_head、dns_wc_tail三单数组是否也空,来控制是否构建普通hash表,前奔通配符hash表和后朝通配符hash表了(在构建这三个种类的hash表的当儿,可以分级以keys、dns_wc_head、dns_wc_tail三只数组)。

构建有当下三个hash表以后,可以整合以一个ngx_hash_combined_t对象中,使用ngx_hash_find_combined进行检索。或者是还保持三独独立的变量对许立即三单hash表,自己主宰何时以及当哪个hash表中展开询问。

 

ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type);

初始化这个结构,主要是对准这个组织中之ngx_array_t类型的字段进行初始化,成功返回NGX_OK。

ha: 该结构的对象指针。
type: 该字段有2个值可选择,即NGX_HASH_SMALL和NGX_HASH_LARGE。用来指明将要建立的hash表的类型,如果是NGX_HASH_SMALL,则有比较小的桶的个数和数组元素大小。NGX_HASH_LARGE则相反。

 

ngx_int_t ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key,
void *value, ngx_uint_t flags);

一般是循环调用之函数,把同组键值对在到这个结构体中。返回NGX_OK是加入成功。返回NGX_BUSY意味着key值重复。

ha: 该结构的对象指针。
key: 参数名自解释了。
value: 参数名自解释了。
flags: 有两个标志位可以设置,NGX_HASH_WILDCARD_KEY和NGX_HASH_READONLY_KEY。同时要设置的使用逻辑与操作符就可以了。NGX_HASH_READONLY_KEY被设置的时候,在计算hash值的时候,key的值不会被转成小写字符,否则会。NGX_HASH_WILDCARD_KEY被设置的时候,说明key里面可能含有通配符,会进行相应的处理。如果两个标志位都不设置,传0。

有关于这个数据结构的利用,可以参见src/http/ngx_http.c中的ngx_http_server_names函数。

 

ngx_chain_t(100%)¶

nginx的filter模块在处理从别的filter模块或者是handler模块传递过来的多寡(实际上就是是急需发送给客户端的http
response)。这个传递过来的数量是盖一个链表的款型(ngx_chain_t)。而且数量可能于剪切多次传递过来。也就是累累调用filter的处理函数,以不同的ngx_chain_t。

拖欠组织给定义在src/core/ngx_buf.h|c。下面我们来拘禁一下ngx_chain_t的定义。

 

typedef struct ngx_chain_s       ngx_chain_t;

struct ngx_chain_s {
    ngx_buf_t    *buf;
    ngx_chain_t  *next;
};

哪怕2个字段,next指为这个链表的产个节点。buf指于实际的多少。所以于此链表上搭节点也是非常容易,只要拿最终尾元素的next指针指为新的节点,把新节点的next赋值为NULL即可。

 

ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool);

拖欠函数创建一个ngx_chain_t的对象,并赶回指向对象的指针,失败返回NULL。

#define ngx_free_chain(pool, cl)                                             \
    cl->next = pool->chain;                                                  \
pool->chain = cl

该宏释放一个ngx_chain_t类型的目标。如果要是自由全部chain,则迭代此链表,对每个节点使用此宏即可。

注意:
对ngx_chaint_t类型的刑释解教,并无是实在的释放了内存,而一味是将这目标挂于了此pool对象的一个叫作chain的字段对应之chain上,以供下次起这个pool上分红ngx_chain_t类型对象的早晚,快速的自者pool->chain上取下链首元素就回去了,当然,如果这链是拖欠的,才见面真在这pool上采取ngx_palloc函数进行分配。

 

ngx_buf_t(99%)¶

这个ngx_buf_t就是以此ngx_chain_t链表的每个节点的骨子里多少。该组织其实是千篇一律栽浮泛的数据结构,它代表某种现实的数据。这个数量可能是依靠为内存中的某某缓冲区,也可能指于一个文书的某部平等有的,也说不定是有纯元数据(元数据的作用在于指示这个链表的读取者对读取的多少开展不同之拍卖)。

拖欠数据结构位于src/core/ngx_buf.h|c文件被。我们来拘禁一下她的概念。

 

struct ngx_buf_s {
    u_char          *pos;
    u_char          *last;
    off_t            file_pos;
    off_t            file_last;

    u_char          *start;         /* start of buffer */
    u_char          *end;           /* end of buffer */
    ngx_buf_tag_t    tag;
    ngx_file_t      *file;
    ngx_buf_t       *shadow;


    /* the buf's content could be changed */
    unsigned         temporary:1;

    /*
     * the buf's content is in a memory cache or in a read only memory
     * and must not be changed
     */
    unsigned         memory:1;

    /* the buf's content is mmap()ed and must not be changed */
    unsigned         mmap:1;

    unsigned         recycled:1;
    unsigned         in_file:1;
    unsigned         flush:1;
    unsigned         sync:1;
    unsigned         last_buf:1;
    unsigned         last_in_chain:1;

    unsigned         last_shadow:1;
    unsigned         temp_file:1;

    /* STUB */ int   num;
};
pos: 当buf所指向的数据在内存里的时候,pos指向的是这段数据开始的位置。
last: 当buf所指向的数据在内存里的时候,last指向的是这段数据结束的位置。
file_pos: 当buf所指向的数据是在文件里的时候,file_pos指向的是这段数据的开始位置在文件中的偏移量。
file_last: 当buf所指向的数据是在文件里的时候,file_last指向的是这段数据的结束位置在文件中的偏移量。
start: 当buf所指向的数据在内存里的时候,这一整块内存包含的内容可能被包含在多个buf中(比如在某段数据中间插入了其他的数据,这一块数据就需要被拆分开)。那么这些buf中的start和end都指向这一块内存的开始地址和结束地址。而pos和last指向本buf所实际包含的数据的开始和结尾。
end: 解释参见start。
tag: 实际上是一个void*类型的指针,使用者可以关联任意的对象上去,只要对使用者有意义。
file: 当buf所包含的内容在文件中时,file字段指向对应的文件对象。
shadow: 当这个buf完整copy了另外一个buf的所有字段的时候,那么这两个buf指向的实际上是同一块内存,或者是同一个文件的同一部分,此时这两个buf的shadow字段都是指向对方的。那么对于这样的两个buf,在释放的时候,就需要使用者特别小心,具体是由哪里释放,要提前考虑好,如果造成资源的多次释放,可能会造成程序崩溃!
temporary: 为1时表示该buf所包含的内容是在一个用户创建的内存块中,并且可以被在filter处理的过程中进行变更,而不会造成问题。
memory: 为1时表示该buf所包含的内容是在内存中,但是这些内容却不能被进行处理的filter进行变更。
mmap: 为1时表示该buf所包含的内容是在内存中, 是通过mmap使用内存映射从文件中映射到内存中的,这些内容却不能被进行处理的filter进行变更。
recycled: 可以回收的。也就是这个buf是可以被释放的。这个字段通常是配合shadow字段一起使用的,对于使用ngx_create_temp_buf 函数创建的buf,并且是另外一个buf的shadow,那么可以使用这个字段来标示这个buf是可以被释放的。
in_file: 为1时表示该buf所包含的内容是在文件中。
flush: 遇到有flush字段被设置为1的的buf的chain,则该chain的数据即便不是最后结束的数据(last_buf被设置,标志所有要输出的内容都完了),也会进行输出,不会受postpone_output配置的限制,但是会受到发送速率等其他条件的限制。
sync:  
last_buf: 数据被以多个chain传递给了过滤器,此字段为1表明这是最后一个buf。
last_in_chain: 在当前的chain里面,此buf是最后一个。特别要注意的是last_in_chain的buf不一定是last_buf,但是last_buf的buf一定是last_in_chain的。这是因为数据会被以多个chain传递给某个filter模块。
last_shadow: 在创建一个buf的shadow的时候,通常将新创建的一个buf的last_shadow置为1。
temp_file: 由于受到内存使用的限制,有时候一些buf的内容需要被写到磁盘上的临时文件中去,那么这时,就设置此标志 。

于这个目标的缔造,可以一直当有ngx_pool_t上分红,然后因需要,给相应之字段赋值。也堪下定义好的2个巨大:

#define ngx_alloc_buf(pool)  ngx_palloc(pool, sizeof(ngx_buf_t))
#define ngx_calloc_buf(pool) ngx_pcalloc(pool, sizeof(ngx_buf_t))

随即有限单庞大使用类函数,也是未说明白的。

对此开创temporary字段为1之buf(就是那个情节好给连续之filter模块进行改动),可以一直动用函数ngx_create_temp_buf进行创办。

 

ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size);

该函数创建一个ngx_but_t类型的对象,并返指于这目标的指针,创建失败返回NULL。

对开创的这目标,它的start和end指向新分配内存开始与得了之地方。pos和last都指为这块新分配内存的起来处于,这样,后续之操作可以于这块新分配的内存达到存入数据。

pool: 分配该buf和buf使用的内存所使用的pool。
size: 该buf使用的内存的大小。

为了配合对ngx_buf_t的用,nginx定义了以下的宏方便操作。

#define ngx_buf_in_memory(b)        (b->temporary || b->memory || b->mmap)

回去这个buf里面的始末是否在内存里。

#define ngx_buf_in_memory_only(b)   (ngx_buf_in_memory(b) && !b->in_file)

回这个buf里面的内容是否仅仅在内存里,并且没有于文件里。

#define ngx_buf_special(b)                                                   \
    ((b->flush || b->last_buf || b->sync)                                    \
     && !ngx_buf_in_memory(b) && !b->in_file)

归来该buf是否是一个非常之buf,只包含非常的标志及无包含真正的数据。

#define ngx_buf_sync_only(b)                                                 \
    (b->sync                                                                 \
     && !ngx_buf_in_memory(b) && !b->in_file && !b->flush && !b->last_buf)

回到该buf是否是一个独包含sync标志而非含真正数据的突出buf。

#define ngx_buf_size(b)                                                      \
    (ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos):                      \
                            (b->file_last - b->file_pos))

回来该buf所蕴藏数据的高低,不管这数据是于文书里还是当内存里。

 

ngx_list_t(100%)¶

ngx_list_t顾名思义,看起好像是一个list的数据结构。这样的传教,算对吧不到底对。因为她适合list类型数据结构的一部分风味,比如可互补加元素,实现从增长,不见面如数组类型的数据结构,受到初始设定的数组容量的范围,并且其与咱们周边的list型数据结构也是千篇一律的,内部贯彻以了一个链表。

那么其跟咱们广大的链表实现之list有什么不同为?不同点就在她的节点,它的节点不像我们大的list的节点,只能存放一个素,ngx_list_t的节点实际上是一个一定大小的数组。

于初始化的时,我们要设定元素用占用的长空尺寸,每个节点数组的容量大小。在补充加元素到这list里面的时候,会在无比尾部底节点里之数组上上加元素,如果此节点的数组存满了,就再也追加一个初的节点到这个list里面去。

哼了,看到这里,大家应该多明白这list结构了咔嚓?还免知晓啊远非提到,下面我们来具体看一下它们的概念,这些概念跟有关的操作函数定义在src/core/ngx_list.h|c文件中。

 

typedef struct {
    ngx_list_part_t  *last;
    ngx_list_part_t   part;
    size_t            size;
    ngx_uint_t        nalloc;
    ngx_pool_t       *pool;
} ngx_list_t;
last: 指向该链表的最后一个节点。
part: 该链表的首个存放具体元素的节点。
size: 链表中存放的具体元素所需内存大小。
nalloc: 每个节点所含的固定大小的数组的容量。
pool: 该list使用的分配内存的pool。

哼,我们于羁押一下每个节点的定义。

 

typedef struct ngx_list_part_s  ngx_list_part_t;
struct ngx_list_part_s {
    void             *elts;
    ngx_uint_t        nelts;
    ngx_list_part_t  *next;
};
elts: 节点中存放具体元素的内存的开始地址。
nelts: 节点中已有元素个数。这个值是不能大于链表头节点ngx_list_t类型中的nalloc字段的。
next: 指向下一个节点。

我们来拘禁一下提供的一个操作的函数。

 

ngx_list_t *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size);

该函数创建一个ngx_list_t类型的靶子,并针对性拖欠list的首先个节点分配存放元素的内存空间。

pool: 分配内存使用的pool。
n: 每个节点固定长度的数组的长度。
size: 存放的具体元素的个数。
返回值: 成功返回指向创建的ngx_list_t对象的指针,失败返回NULL。

 

void *ngx_list_push(ngx_list_t *list);

欠函数在加以的list的尾增加一个素,并赶回指向新因素存放空间的指针。如果增加失败,则归NULL。

 

static ngx_inline ngx_int_t
ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size);

该函数是用来ngx_list_t类型的对象已经存在,但是那首先只节点存放元素的内存空间还不分配的情况下,可以调用此函数来为这list的首节点来分配存放元素的内存空间。

这就是说什么时会面世已起了ngx_list_t类型的靶子,而那个首节点存放元素的内存尚未分配的事态为?那就是是ngx_list_t类型的变量并无是通过调用ngx_list_create函数创建的。例如:如果某结构体的一个成员变量是ngx_list_t类型的,那么当这个布局体类型的靶子吃创造出来的时,这个成员变量也受创造出来了,但是它们的首节点的存元素的内存并未受分配。

总之,如果此ngx_list_t类型的变量,如果未是公通过调用函数ngx_list_create创建的,那么就算不能不调用此函数去初始化,否则,你向这list里增加元素即可能引发不可预知的行为,亦要程序会倒!

 

ngx_queue_t(100%)¶

ngx_queue_t是nginx中的双向链表,在nginx源码目录src/core下面的ngx_queue.h|c里面。它的原型如下:

 

typedef struct ngx_queue_s ngx_queue_t;

struct ngx_queue_s {
    ngx_queue_t  *prev;
    ngx_queue_t  *next;
};

不等为课本中以链表节点的数码成员声称在链表节点的结构体中,ngx_queue_t只是声称了前头望同后往指针。在应用的上,我们首先用定义一个哨兵节点(对于后续具体存放数据的节点,我们叫数据节点),比如:

 

ngx_queue_t free;

属下要开展初始化,通过宏ngx_queue_init()来实现:

 

ngx_queue_init(&free);

ngx_queue_init()的宏定义如下:

#define ngx_queue_init(q)     \
    (q)->prev = q;            \
    (q)->next = q;

足见初始的当儿哨兵节点的 prev 和 next
都指向自己,因此实际是一个空链表。ngx_queue_empty()可以为此来判断一个链表是否为空,其实现为很简短,就是:

#define ngx_queue_empty(h)    \
    (h == (h)->prev)

这就是说如何声明一个所有数据元素的链表节点吧?只要以对应的结构体中加上一个
ngx_queue_t
的积极分子即使实施了。比如ngx_http_upstream_keepalive_module中的ngx_http_upstream_keepalive_cache_t:

 

typedef struct {
    ngx_http_upstream_keepalive_srv_conf_t  *conf;

    ngx_queue_t                        queue;
    ngx_connection_t                  *connection;

    socklen_t                          socklen;
    u_char                             sockaddr[NGX_SOCKADDRLEN];
} ngx_http_upstream_keepalive_cache_t;

于每一个这么的数码节点,可以经ngx_queue_insert_head()来上加至链表中,第一只参数是哨兵节点,第二独参数是数额节点,比如:

 

ngx_http_upstream_keepalive_cache_t cache;
ngx_queue_insert_head(&free, &cache.queue);

对应的几独宏定义如下:

#define ngx_queue_insert_head(h, x)                         \
    (x)->next = (h)->next;                                  \
    (x)->next->prev = x;                                    \
    (x)->prev = h;                                          \
    (h)->next = x

#define ngx_queue_insert_after   ngx_queue_insert_head

#define ngx_queue_insert_tail(h, x)                          \
    (x)->prev = (h)->prev;                                   \
    (x)->prev->next = x;                                     \
    (x)->next = h;                                           \
    (h)->prev = x

ngx_queue_insert_head()和ngx_queue_insert_after()都是往头部添加节点,ngx_queue_insert_tail()是于尾部添加节点。从代码可以看哨兵节点的
prev 指向链表的奇据节点,next
指为链表的峰数据节点。另外ngx_queue_head()和ngx_queue_last()这点儿单特大分别可以得头节点和尾节点。

那么如若现在来一个ngx_queue_t *q
指向的是链表中的数量节点的queue成员,如何赢得ngx_http_upstream_keepalive_cache_t的数目为?
nginx提供了ngx_queue_data()宏来得到ngx_http_upstream_keepalive_cache_t的指针,例如:

 

ngx_http_upstream_keepalive_cache_t *cache = ngx_queue_data(q,
                                                 ngx_http_upstream_keepalive_cache_t,
                                                 queue);

也许你已经得以猜到ngx_queue_data是由此地方相互减来获取的:

#define ngx_queue_data(q, type, link)                        \
    (type *) ((u_char *) q - offsetof(type, link))

此外nginx也提供了ngx_queue_remove()宏来从链表中删除一个数据节点,以及ngx_queue_add()用来以一个链表添加至其它一个链表。

 

nginx的配置体系(100%)¶

nginx的安排体系由一个主配置文件及其他一些扶持的布局文件构成。这些部署文件都是纯文本文件,全部放在nginx安装目录下的conf目录下。

布文件中因#开头之执行,或者是前方有几空格或TAB,然后重新跟#的履,都受当是注释,也就是才对编辑查看文件的用户发生义,程序于读取这些注释行的时节,其实际的情是为忽略的。

鉴于除主配置文件nginx.conf以外的公文都是在少数情况下才用的,而只有主配置文件是当其余动静下都为应用的。所以于此处我们不怕以预示配置文件呢条例,来说明nginx的布局体系。

当nginx.conf中,包含几配备起。每个配置起由安排指令和下令参数2单部分组成。指令参数为就算是布局指令对应的配置值。

 

命概述¶

配置指令是一个字符串,可以据此单引号或者双引号括起来,也可以不括。但是一旦安排指令包含空格,一定要引起来。

 

令参数¶

一声令下的参数使用一个或者多个空格或TAB字符与指令分开。指令的参数有一个还是基本上只TOKEN串组成。TOKEN串之间由空格或TAB键分隔。

TOKEN串分为简单字符串或者是复合配置块。复合配置块就是由于大括号括起来的平积聚内容。一个复合配置块被或者包含几其他的布指令。

万一一个安排指令的参数全部由于简单字符串构成,也不怕是未包含复合配置块,那么我们便说之布局指令是一个简便布置起,否则称之为复杂配置起。例如下面是是一个概括布置起:

 

error_page   500 502 503 504  /50x.html;

对简易布置,配置起之终极使用分号结束。对于复杂配置起,包含多个TOKEN串的,一般还是简单TOKEN串放在眼前,复合配置块一般在最后,而且其最后,并不需要再添加分号。例如下面这个复杂配置起:

 

location / {
    root   /home/jizhao/nginx-book/build/html;
    index  index.html index.htm;
}

 

命令上下文¶

nginx.conf中的布局信息,根据那逻辑上的含义,对它们进行了归类,也便是分成了大半个作用域,或者叫配置指令上下文。不同之作用域含有一个要基本上个布局起。

此时此刻nginx支持的几乎独令上下文:

main: nginx在运行时与具体业务功能(比如http服务或者email服务代理)无关的一些参数,比如工作进程数,运行的身份等。
http: 与提供http服务相关的一些配置参数。例如:是否使用keepalive啊,是否使用gzip进行压缩等。
server: http服务上支持若干虚拟主机。每个虚拟主机一个对应的server配置项,配置项里面包含该虚拟主机相关的配置。在提供mail服务的代理时,也可以建立若干server.每个server通过监听的地址来区分。
location: http服务中,某些特定的URL对应的一系列配置项。
mail: 实现email相关的SMTP/IMAP/POP3代理时,共享的一些配置项(因为可能实现多个代理,工作在多个监听地址上)。

一声令下上下文,可能发隐含的状态出现。例如:通常http上下文和mail上下文一定是起在main上下文里的。在一个左右文里,可能含另外一栽类型的上下文多次。例如:如果http服务,支持了多独虚拟主机,那么当http上下文里,就会并发多只server上下文。

咱们来拘禁一个演示配置:

 

user  nobody;
worker_processes  1;
error_log  logs/error.log  info;

events {
    worker_connections  1024;
}

http {
    server {
        listen          80;
        server_name     www.linuxidc.com;
        access_log      logs/linuxidc.access.log main;
        location / {
            index index.html;
            root  /var/www/linuxidc.com/htdocs;
        }
    }

    server {
        listen          80;
        server_name     www.Androidj.com;
        access_log      logs/androidj.access.log main;
        location / {
            index index.html;
            root  /var/www/androidj.com/htdocs;
        }
    }
}

mail {
    auth_http  127.0.0.1:80/auth.php;
    pop3_capabilities  "TOP"  "USER";
    imap_capabilities  "IMAP4rev1"  "UIDPLUS";

    server {
        listen     110;
        protocol   pop3;
        proxy      on;
    }
    server {
        listen      25;
        protocol    smtp;
        proxy       on;
        smtp_auth   login plain;
        xclient     off;
    }
}

在这布局中,上面提到个五种配置指令上下文都设有。

存在于main上下文中的部署指令如下:

  • user
  • worker_processes
  • error_log
  • events
  • http
  • mail

存于http上下文中的通令如下:

  • server

存在于mail上下文中的吩咐如下:

  • server
  • auth_http
  • imap_capabilities

是于server上下文被的布局指令如下:

  • listen
  • server_name
  • access_log
  • location
  • protocol
  • proxy
  • smtp_auth
  • xclient

存在叫location上下文中的授命如下:

  • index
  • root

自然,这里只有是一些示范。具体产生什么安排指令,以及这些配置指令可以起于怎样的内外文中,需要参考nginx的运用文档。

 

nginx的模块化体系布局¶

nginx的内部结构是由基本组成部分以及同多重的功能模块所构成。这样划分是为令每个模块的力量相对简单,便于开发,同时为有利于对网开展力量扩展。为了便利描述,下文中我们用用nginx
core来称呼nginx的着力功能有。

nginx提供了web服务器的底蕴作用,同时提供了web服务反向代理,email服务反向代理功能。nginx
core实现了底部的报道协议,为任何模块和nginx进程构建了基本的运作时环境,并且构建了任何各级模块的合作基础。除此之外,或者说大部分以及磋商相关的,或者利用相关的力量还是当这些模块中所实现之。

 

模块概述¶

nginx将各国功能模块组织改为一长条链子,当起要到达的时节,请求依次通过这条链上的有些还是全体模块,进行处理。每个模块实现特定的作用。例如,实现对要解除压缩的模块,实现SSI的模块,实现与上游服务器进行通讯的模块,实现和FastCGI服务开展报道的模块。

产生点儿单模块于突出,他们居于nginx
core和各国功能模块的中间。这点儿独模块就是http模块和mail模块。这2个模块于nginx
core之上实现了另外一叠抽象,处理与HTTP协议及email相关协议(SMTP/POP3/IMAP)有关的波,并且保证这些事件会吃因对的顺序调用其他的一对功能模块。

时下HTTP协议是吃实现以http模块中之,但是出或将来于退至一个独的模块中,以扩展nginx支持SPDY协议。

 

模块的归类¶

nginx的模块根据那效果基本上可以分为以下几种档次:

event module: 搭建了独立于操作系统的事件处理机制的框架,及提供了各具体事件的处理。包括ngx_events_module, ngx_event_core_module和ngx_epoll_module等。nginx具体使用何种事件处理模块,这依赖于具体的操作系统和编译选项。
phase handler: 此类型的模块也被直接称为handler模块。主要负责处理客户端请求并产生待响应内容,比如ngx_http_static_module模块,负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出。
output filter: 也称为filter模块,主要是负责对输出的内容进行处理,可以对输出进行修改。例如,可以实现对输出的所有html页面增加预定义的footbar一类的工作,或者对输出的图片的URL进行替换之类的工作。
upstream: upstream模块实现反向代理的功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回客户端。upstream模块是一种特殊的handler,只不过响应内容不是真正由自己产生的,而是从后端服务器上读取的。
load-balancer: 负载均衡模块,实现特定的算法,在众多的后端服务器中,选择一个服务器出来作为某个请求的转发服务器。

 

nginx的要处理¶

nginx使用一个差不多进程模型来对外提供劳务,其中一个master进程,多独worker进程。master进程负责管理nginx本身和另worker进程。

有着实际上的作业处理逻辑都当worker进程。worker进程中产生一个函数,执行太循环,不断处理收到的起源客户端的请求,并开展处理,直到所有nginx服务为停。

worker进程中,ngx_worker_process_cycle()函数就是这极端循环的处理函数。在这个函数中,一个要的简要处理流程如下:

  1. 操作系统提供的体制(例如epoll, kqueue等)产生相关的风波。
  2. 收到和拍卖这些事件,如是承受到数量,则闹更高层的request对象。
  3. 处理request的header和body。
  4. 出响应,并发送回客户端。
  5. 完成request的处理。
  6. 重初始化定时器及另事件。

 

央的拍卖流程¶

以为大家再好的询问nginx中呼吁处理过程,我们盖HTTP
Request为例,来做一下详细地证明。

自从nginx的内来拘禁,一个HTTP Request的处理过程涉及到以下几单等级。

  1. 初始化HTTP Request(读取来自客户端的数目,生成HTTP
    Request对象,该对象涵盖该要所有的信)。
  2. 拍卖要求头。
  3. 处理要求体。
  4. 苟有的话,调用与这要(URL或者Location)关联的handler。
  5. 各个调用各phase handler进行处理。

于这边,我们要了解一下phase
handler这个定义。phase字面的意思,就是品。所以phase
handlers也就是哼明了,就是包含几只处理阶段的有handler。

以各一个等,包含有好多单handler,再处理及某阶段的时光,依次调用该级的handler对HTTP
Request进行处理。

一般状态下,一个phase
handler对之request进行拍卖,并有局部输出。通常phase
handler是同概念在配置文件被的某部location相关联的。

一个phase handler通常执行以下几件职责:

  1. 获取location配置。
  2. 起适当的应。
  3. 发送response header。
  4. 发送response body。

当nginx读博到一个HTTP
Request的header的时段,nginx首先查找和这个请关联的虚拟主机的布置。如果找到了此虚拟主机的配置,那么一般状态下,这个HTTP
Request将见面由此以下几单等级的处理(phase handlers):

NGX_HTTP_POST_READ_PHASE:

 

读取请求内容等

NGX_HTTP_SERVER_REWRITE_PHASE:

 

Server请求地址重写阶段

NGX_HTTP_FIND_CONFIG_PHASE:

 

布局查找阶段:

NGX_HTTP_REWRITE_PHASE:

 

Location请求地址重写阶段

NGX_HTTP_POST_REWRITE_PHASE:

 

吁地址重写提交等

NGX_HTTP_PREACCESS_PHASE:

 

拜权限检查准备阶段

NGX_HTTP_ACCESS_PHASE:

 

看权限检查等级

NGX_HTTP_POST_ACCESS_PHASE:

 

顾权限检查提交等

NGX_HTTP_TRY_FILES_PHASE:

 

配置项try_files处理阶段

NGX_HTTP_CONTENT_PHASE:

 

情有阶段

NGX_HTTP_LOG_PHASE:

 

日志模块处理阶段

以内容来阶段,为了给一个request产生不利的响应,nginx必须管此request交给一个相当的content
handler去处理。如果此request对应的location在配备文件中为强烈指定了一个content
handler,那么nginx就可由此对location的配合,直接找到这相应的handler,并拿此request交给这content
handler去处理。这样的部署指令包括诸如,perl,flv,proxy_pass,mp4等。

只要一个request对应的location并没直接发生配备的content
handler,那么nginx依次尝试:

  1. 假若一个location里面有布置  random_index 
    on,那么自由选取一个文书,发送给客户端。
  2. 假设一个location里面有部署
    index指令,那么发送index指令指明的文书,给客户端。
  3. 要是一个location里面来部署 autoindex 
    on,那么尽管发送请求地址对应之劳务端路径下的文本列表给客户端。
  4. 万一是request对应之location上发安gzip_static
    on,那么尽管摸是否出照应的.gz文件是,有的话,就发送这个被客户端(客户端支持gzip的景况下)。
  5. 请求的URI如果对应一个静态文件,static
    module就发送静态文件之内容到客户端。

内容来阶段就之后,生成的输出会吃传送及filter模块去开展处理。filter模块也是跟location相关的。所有的fiter模块都于集团改为一长达链子。输出会依次通过所有的filter,直到有一个filter模块的返回值表明已经处理完成。

此间列举几单大的filter模块,例如:

  1. server-side includes。
  2. XSLT filtering。
  3. 图像缩放之类的。
  4. gzip压缩。

于有的filter中,有几个filter模块需要关爱一下。按照调用的依次依次说明如下:

write: 写输出到客户端,实际上是写到连接对应的socket上。
postpone: 这个filter是负责subrequest的,也就是子请求的。
copy: 将一些需要复制的buf(文件或者内存)重新复制一份然后交给剩余的body filter处理。

 

相关文章