nginx

来源:互联网 发布:工厂生产数据表格 编辑:程序博客网 时间:2024/05/20 00:53

request

:在nginx中我们指的是http请求,具体到nginx的数据结构是ngx_http_request;ngx_http_request_t是对一个http请求的封装;我们知道一个http请求包含请求行,请求头,请求体,响应行,响应头,响应体。
http请求是典型的请求-响应类型的网络协议,而http是文件协议。所以我们在分析请求行与请求头,以及响应头和响应行,往往是一行一行进行处理。如果自己建立http服务器,通常在一个连接建立起来之后,客户端发送过来请求。我们读取一行数据,分析出请求行中包含的method、url、http_version信息,然后一行一行处理请求体。得到请求后,我们处理请求产需要输出的数据,然后生成响应行,响应头以及响应体。在将响应发给客户端之后,一个完整的请求就处理完了。当请求读取完成,就开始进行请求的处理。nginx通过ngx_http_request来保存生的解析请求与输出响应相关的数据。

请求来到:nginx->

  • ngx_http_init_request开始,在这个函数中会设置读事件为ngx_http_process_request_line,也就是说接下来的网络事件会由ngx_http_process_request_line来执行,从名字上来看,是处理请求行的。也与处理的第一件事是处理请求请求行是一致的
  • 通过ngx_http_read_request_header来读取请求数据
  • 然后调用ngx_http_parse_request_line来解析请求行
  • nginx为了提高效率,采用状态机来解析请求行,而且在进行method比较的时候,没有直接使用字符串,而是将4个字符转换成一个整型,然后比较一次以减少cpu指令数目。比如一个请求GET http://www.taobao.com/uri HTPP/1.0 ,这个时候,nginx会忽略头中的host域,而以请求行中的这个为准来查找虚拟主机
  • 整个请求行解析到的参数,会保存到ngx_http_request_t结构体中
  • 解析请求行之后,设置读事件handler为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
  • 当解析到两个回车换行符,就表示请求头的结束,此时就调用ngx_http_process_request来处理请求
  • ngx_http_process_requesthi设置当前连接的读写事件处理函数为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_request_t的headers_input中。nginx的各种阶段会对请求进行处理,最后调用filter函数来过滤数据,对数据进行加工,如truncated传输,gzip压缩;这里的filte包括header filter和body filter,即响应头或响应提处理。filter是一个链表结构,分别有header filter和body filter,先执行所有的header 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;

    keepalive

    content-length来表明body的大小,否则返回400;也就是说请求体长度是固定的,那么响应体的长度呢

  • 在http/1.0协议来说,如果响应头中有content-length,则以content-length就可以知道body长度,客户端在接收body时候,就可以依照这个长度来接收数据,当接收完后,就表示请求完成。如果没有这个,则客户端就会一直接收数据,直到服务器端断开连接,才表示body接收完成
  • 在http/1.1协议来说,如果响应头中有Transfer-encoding位chunked传输,则表示body是流式输出。body会被分多个块,每个块的开始会标识出当前块长度,此时body不需要长度来指定。如果是非chunked传输,而且有content-length,则以content-length来接收数据。否则,如果是非chunked,并且没有content-length,则客户端就会一直接收数据,直到服务器端断开连接
  • 能否使用长连接,有条件限制:如果客户端请求头connection为close,则表示客户端需要关掉长连接;如果为keep-alive,则客户端需要打开长连接;如没有的话,在http/1.0默认是closed,http/1.1默认是keep-alive
  • 如果是keepalive的话,那么nginx在输出完响应体之后,会设置当前连接keepalive属性,等待客户端下一次请求;同时设置一个最大请求时间,这个时间是通过keepalive_timeout来配置,如果是0则表示关闭掉keepalive,无论http版本,都会强制close;
    如果服务器端最后的决定是keepalive打开,那么响应的http头里面,也会包含connection头域,其值是keep-alive。所以对于请求量比较大的nginx来说,关掉keepalive最后会产生比较多的time_wait
    状态的socket。所以当客户端的一次访问,需要多次访问同一个server时,打开keepalive的优势很大。比如图片服务器,通过一个而网友包含很多图片,打开keepalive会大量减少time-wait数量

    pipeline

    目的是一个连接做多次请求;如果客户需要提交读个请求,那么对于keepalive来说,第二个请求必须等到第一个请求响应接收完全后,才能发起,这和tcp的停止等到协议是一样的,得到响应的时间至少为2*RTT。而对于pipeline来说,不需要等到第一个请求处理完成,马上可以发起第二个请求,时间可以达到1*RTT。nginx可以直接支持pipeline。如果nginx在处理完前一个请求后,如果发现buffer里面还有数据,就认为是下一个请求的开始,然处理下一个请求,否则就设置keepalive。

    lingering_close

    延时关闭。当nginx关闭连接时候,并不是立即关闭,而是先关闭tcp连接的写,在等待一段时间后在关闭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报文且不会拿到服务器发送过来的错误信息数据。

    ngx_str_t

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

    data指向字符串数据的第一个字符,字符串结束用长度来表示,而不是’\0’来表示;

    1.通过长度来表示字符串长度,减少计算字符串的次数
    2.nginx可以重复引用一段字符串内存,而不能更改原来的字符串,长度表示结束,而不是去cp一份自己的字符串(因为如果’\0’结束,而不能更改原来字符串,所以势必要cp一端字符串);如request_line /uri/args等,这些字符串的data部分,都是指向在接收数据时创建的buffer所指向的内存,uri,args没有必要cp一份出来,减少了不必要的内存分配与拷贝。正是基于此,nginx必须谨慎的修改一个字符串。

    0 0