nginx 1.18 源代码分析 (四)

1. ngx_listening_t与ngx_connection_t

ngx_listening_t保存监听配置信息,如地址、端口等。Init_cycle.listening[]数组保存了所有的ngx_listening_t实例。

ngx_connection_t保存连接,如socket句柄等。Init_cycle.connections[]数组保存所有的ngx_connection_t实例,init_cycle.free_connections[]数组保存其中空闲的ngx_connection_t实例。init_cycle.connections[]的大小由init_cycle.connection_n指定,这个值缺省为512,也可以在配置文件中改变。

ngx_connection_t使用ngx_event_t保存读写事件的配置,如事件处理函数。

2. ngx_http_optimize_servers()

如前所述,http{}节点的ngx_http_main_conf_t.ports[]数组指向server{}的端口数组。nginx服务器绑定这些端口,监听连接请求。

ngx_http_optimize_servers() 根据从这些端口创建ngx_listening_t实例。

  • 遍历ngx_http_core_main_conf_t.ports[],调用ngx_http_init_listening()。

3. ngx_http_init_listening()

ngx_http_init_listening() 从ngx_http_core_main_conf_t.ports[]创建ngx_listenging_t实例,包括从ip地址到ngx_http_core_srv_conf的映射关系。

在ngx_http_init_listening()中,遍历ngx_http_core_main_conf_t.ports.addrs[],

  • 调用ngx_http_add_listening()。在ngx_http_add_listening()中,

    • 调用ngx_create_listening()。其中调用ngx_array_push()从init_cycle.listening[]数组中得到一个空闲的ngx_listening_t,然后初始化。
    • 将新的ngx_listening_t的请求处理函数设置为ngx_http_init_connection()。当客户端的连接请求到达时,读请求事件ngx_event_t.handler()被调用,这个handler()又调用ngx_http_init_connection()。
  • 给ngx_listeing_t.servers[]分配空间,这是一个ngx_http_port_t结构。

  • 调用ngx_http_add_addrs(),将addrs[]中的元素,加入ngx_http_port_t.addrs[]。这是一个ngx_http_in_addr_t数组。这里将ngx_http_in_addr_t.conf.default_server指向了ngx_http_core_main_conf_t.ports.addrs[].default_server。

4. ngx_open_listening_sockets()

ngx_open_listening_sockets()遍历init_cycle.listening[]数组,创建socket,绑定到指定的地址上,并开始监听。

5. ngx_configure_listening_sockets()

ngx_configure_listening_sockets()遍历init_cycle.listening[],对之前创建的socket继续进行选项配置。

6. ngx_event_process_init()

ngx_event_process_init()前面已经列出。

这里进一步详细说明的是,

  • 调用ngx_palloc()给init_cycle.connections[]数组分配空间。

  • 调用ngx_palloc()为init_cycle.connections[]中的每个ngx_connection_t实例分配两个ngx_event_t,一个用于监听读、一个用于监听写。

  • 遍历init_cycle.connections[],将它与相应的两个ngx_event_t实例绑定到ngx_connection_t.read和ngx_connection_write。

  • 当前init_cycle.connections[]中的ngx_connection_t实例都还没使用,所以将它链入init_cycle.free_connections。

  • 遍历init_cycle.listening[],调用ngx_get_connection(),为每个ngx_listening_t实例绑定一个ngx_connection_t实例,开始监听它。

    • 这里将ngx_connetion_t.read的处理函数指定为ngx_event_accept(),然后调用ngx_add_event()将ngx_connection_t.read加入监听列表。

7. ngx_process_events()

ngx_process_events()前面已经列出。

这里进一步说明的是,调用epoll_wait() 得到的事件epoll_event,与ngx_connetion_t实例相关联。这里遍历event_list[],对其中的epoll_event,

  • 得到关联的ngx_connection_t实例,根据事件类型,得到读/写的ngx_event_t实例。调用ngx_event_t::handler()。对于服务器监听端口的ngx_connection_t实例,是ngx_event_accept()。

8. ngx_event_accept()

服务器监听端口有连接请求到达时,调用ngx_event_accept()。

  • 得到与ngx_event_t关联的ngx_listening_t实例。
  • 调用accept()得到新的连接Socket句柄。
  • 调用ngx_get_connection(),得到可用的ngx_connection_t实例,绑定socket句柄,并与ngx_listening_t实例绑定。
  • 调用ngx_listening_t::handler(),这里是ngx_http_init_connection()。

9. 回顾ngx_http_add_listen()

在继续说明ngx_http_init_connection()之前,先回顾一下ngx_http_add_listen()。

创建server{}的配置数据结构时,调用ngx_http_core_listen()。得到server{}中的绑定地址后,调用ngx_http_add_listen() 加入ngx_http_core_main_conf.ports[]中。

这里涉及的数据结构如下。

  • ngx_http_listen_opt_t保存地址信息,它的成员sockaddr包括协议族、端口和IP地址。
  • ngx_http_conf_addr_t保存ngx_http_listen_opt_t,和对应的ngx_http_core_srv_conf_t实例,包括成员servers[]数组,和一个缺省的default_server。
  • ngx_http_conf_port_t保存一组端口相同的地址信息。它的成员family是协议族,成员port是端口值,成员addrs[]则是一个ngx_http_conf_addr_t的数组。
  • ngx_http_core_main_conf_t的成员ports[]保存一组不同的端口信息,也就是ngx_http_conf_port_t。

10. 回顾ngx_http_init_listening()

在继续说明ngx_http_init_connection()之前,先回顾一下ngx_http_init_listening()。

ngx_http_init_listening() 从ngx_http_core_main_conf_t.ports[]创建ngx_listenging_t实例,包括从ip地址到ngx_http_core_srv_conf的映射关系。

这里涉及的数据结构如下。

  • ngx_http_in_addr_t的成员addr保存地址,成员conf是ngx_http_addr_conf_t,它的成员default_server,又指向ngx_http_core_srv_conf_t实例。
  • ngx_http_port_t保存一组ngx_http_in_addr_t。ngx_listening_t又保存一组ngx_http_port_t。

所以,遍历ngx_listengint_t.servers[],可以根据地址找到对应的ngx_http_core_srv_conf_t实例。

11. ngx_http_init_connection()

ngx_http_init_connection()初始化新的ngx_connection_t实例。

  • 分配ngx_http_connection_t实例,并与ngx_connection_t实例关联。
  • 如前所述,在ngx_http_core_main_conf_t.listening[].servers[]中,保存了地址和对应的ngx_http_core_srv_conf_t实例。遍历这个数组,找到与当前ngx_connection_t对应的ngx_http_in_addr_t实例。将ngx_http_connection_t.addr_conf指向ngx_http_core_main_conf中的相应值。
hc->addr_conf = &addr[i].conf;
hc->conf_ctx = hc->addr_conf->default_server->ctx;
  • 设置ngx_connection_t的用于读的ngx_event_t实例。设置其ngx_event_::handler()为ngx_http_wait_request_handler()。
  • 调用ngx_handle_read_event(),其中调用ngx_add_event(),开始监听ngx_connection_t的读事件。

12. ngx_process_events()

再一次回到ngx_process_events()。这一次当数据连接socket有数据可读时,调用ngx_event_t::handler(),这里是ngx_http_wait_request_handler()。ngx_http_wait_request_handler()就开始处理http请求了。

13. ngx_http_wait_request_handler()

当http请求数据到达时,调用ngx_http_wait_request_handler()。它接收请求数据并处理。

  • 调用ngx_create_temp_buf()分配空间,作为接收数据的缓存。
  • 调用ngx_connect_t::recv()接收数据。这里是ngx_unix_recv()。Ngx_unix_recv()调用recv()。
  • 调用ngx_http_create_request(),创建ngx_http_request_t实例。
  • 调用ngx_http_process_request_line()开始数据处理。

14. ngx_http_create_request()

ngx_http_create_request() 创建ngx_http_request_t实例。它调用ngx_http_alloc_request()分配ngx_http_request_t实例。其中设置ngx_http_request_t.main_conf、ngx_http_request_t.srv_conf、ngx_http_request_t.loc_conf。这是后面的ngx_http_request_t的上下文。

15. ngx_http_request_t

ngx_http_request_t是http请求处理过程中使用的结构。解析请求字符串得到的信息保存在这里。

如下是一个请求字符串的例子。

  • 第一行GET /index.html HTTP/1.1是请求的地址,包括
    • method: GET。保存在ngx_http_request_t.method.
    • uri: /index.html。保存在ngx_http_request.uri_start
    • protocol: HTTP。保存在ngx_http_request.http_protocol
    • http version: 1.1。保存在ngx_http_request.http_major和ngx_http_request_.http_minor。

这行调用ngx_http_parse_request_line()解析。

后续行为header行,每一行是一个header,包括名字和值。这里的header名字有User-Agent、Accept、Host和Connection。保存在ngx_http_request.headers_in.headers。Header行调用ngx_http_parse_header_line()解析。

16. ngx_http_headers_in

全局数组ngx_http_headers_in[]中,保存与header行对应的处理函数。如下面的Host、User-Agent等。

ngx_http_header_t  ngx_http_headers_in[] = {
    { ngx_string("Host"), offsetof(ngx_http_headers_in_t, host),
        ngx_http_process_host },
    { ngx_string("User-Agent"), offsetof(ngx_http_headers_in_t, user_agent),
        ngx_http_process_user_agent },
......
};

17. ngx_http_init_headers_in_hash()

在ngx_http_init_headers_in_hash()中,

  • 遍历ngx_http_headers_in[],对其中的ngx_http_header_t实例,
    • 创建ngx_hash_key_t,保存到本地数组headers_in中。成员key设置为ngx_http_header_t.name;根据key计算成员key_hash;设置成员value指向ngx_http_header_t

ngx_array_t headers_in;

  • 将本地变量hash关联到headers_in。并用它初始化ngx_http_core_main_conf_t.headers_in_hash,这是一个ngx_hash_t结构,也就是hash树。

ngx_hash_init_t hash;

所以最后的结果就是,全局数组ngx_http_headers_in[]的元素保存在ngx_http_core_main_conf_t.headers_in_hash中,访问速度变快了。

18. ngx_http_process_request_line ()

ngx_http_process_request_line()负责处理请求字符串。

在一个循环中,执行如下步骤:

  • 调用ngx_http_read_request_header(),读取更多数据。
  • 调用ngx_http_parse_request_line(),分解请求字符串各部分,并保存在ngx_http_request_t实例中。
  • 调用ngx_http_process_request_headers(),继续处理http请求。

19. ngx_http_process_request_headers()

ngx_http_process_request_headers() 继续处理http请求。

  • 在一个循环中,调用ngx_http_parse_header_line()解析header字符串,得到所有header,保存在ngx_http_request_t.headers_in。至此完成http请求字符串的解析。
  • 调用ngx_hash_find(),根据http请求的名字,从ngx_http_core_main_conf_t.headers_in_hash,查询对应的ngx_http_header_t实例。前面已经说过,这里的headers_in_hash,来自全局变量ngx_http_headers_in[]。
  • 然后调用处理函数ngx_http_header_t.handler()。如header名字为User-Agent,对应的处理函数是ngx_http_process_user_agent()。
  • 调用ngx_http_process_request(),正式处理http请求。

20. ngx_http_process_request()

ngx_http_process_request() 处理http请求。它调用ngx_http_handler(),后者又调用ngx_http_core_run_phases()。

nginx将数据处理过程划分为几个阶段(phase),每个阶段可以挂接若干处理函数,ngx_http_core_run_phase() 按阶段调用这些函数。Nginx自身的nginx_module,用户自己的ngx_module,都可以挂接自己的处理函数。

相关链接

nginx 1.18 源代码分析 (一)
nginx 1.18 源代码分析 (二)
nginx 1.18 源代码分析 (三)
nginx 1.18 源代码分析 (四)
nginx 1.18 源代码分析 (五)
nginx 1.18 源代码分析 (六)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容