当前位置: 首页 > news >正文

ngxin系列--(二)--stream模块的加载、accept、read/write

本文主要介绍了stream流程的:模块启动、accept、read/write

20241021 22:59 遗留问题:在哪里写回响应,是不是写回响应也是一个handler,而不是单独的函数?今天下班了。已解决,就是在handler中处理

1:stream处理流程

upstream和downstream:downstream代表客户端,upstream代表后端服务。客户端发来一个请求到nginx,最先到到downstream流程代码,然后downstream流程把请求转发给backendserver,当backendserver处理完后会发消息给nginx,此时与nginx通信的就是upstream,nginx收到后通过upstrema流程发给client

1-1:模块启动和listenfd注册到epoll流程

总结下来流程就是:1:解析配置文件,边读边解析,递归处理嵌套配置块,不管是最顶层的还是嵌套的配置块,一个配置块都对应一个命令,每个命令都包含一个set函数,每读取到一个配置块就调用命令的set函数来解析该配置块

nginx.mainngx_module.ngx_preinit_modules                    #给模块编号,ngxin_modules是一个全局数组,保存了所有模块,每个元素是一个ngx_module_t结构体#每个ngx_module_t结构体内含有一个ngx_command_t数组和ngx_core_module_t变量,#这两个变量都是是全局变量,ngx_modules标识一个模块,ngx_command_t标识一个模块支持哪些模块命令#ngx_core_module_t变量是模块的上下文变量,包含了一系列函数指针#ngx_module_names是一个module名字数组#!!!ngx_modules保存的是模块对应的信息,ngx_command_t是一个数组,可以包含多个command,#!!!每个command对应一个配置块,command的名字就是配置块的名字,#!!!解析一个配置块就是调用对应command的set函数#!!!一个配置块与他内部嵌套的配置块对应的配置命令可能会放在不同的模块内。#就是说一个http模块,对应配置为http{ server{},servername=xx,},#http配置块对应的命令放在http_modules模块中#但是server配置块的信息却放在http_core_modules块中,最顶层的块一般只有一个命令,#主要是用来标识模块,但是块内的嵌套块却可以有多个,因为一个模块内可以有多个配置块#每个配置块对应一个模块,如上所示,http是最顶层的块,标识检测到了一个http模块#而server和servername则是http模块内的两个嵌套块(server嵌套块还可以有嵌套块)#一旦解析到http块就会调用http模块的http命令的set函数(http模块只有一个http命令)#一旦解析到server块或者servername块,就会调用ngx_http_core_module中对应命令的set函数for (i = 0; ngx_modules[i]; i++) {  ngx_modules[i]->index = i;ngx_modules[i]->name = ngx_module_names[i];}ngx_cycle.ngx_init_cyclengx_conf_file.ngx_conf_parse           #解析conf文件,根据conf文件里的配置块来调用对应模块命令的setfor ; ;                              #for conf文件的每一个配置块,调用对应模块的对应命令的set函数ngx_conf_file.ngx_conf_read_token  #读取一个tokenngx_conf_file.ngx_conf_handler     #就是处理块比如workProcessor块、http/stream模块下面举例if name== modules[i]->commands->name:      #name就是配置块的名字,modules[i]->commands就是模块对应的命令,#commands->name就是该命令对应的名字#也就是说nginx根据conf文件中块的名字来匹配对应的模块以及调用对应的模块命令的set函数#备注:一个模块可以有多个命令cmd=modules[i]->commandscmd->set                                   #cmd->set就是各个模块对应的set函数,不同的模块对应不同的函数#比如stream模块就调用ngx_stream_block,http就用ngx_http_block#!!!也就是说nginx用函数指针来实现了一种多态的效果#!!!也就是说可以用同一套代码流程来加载http、stream模块1: worker_processes 块nginx.ngx_set_worker_processes   #对应配置文件里的 worker_processes 配置块2:event块ngx_event.ngx_events_block  3: http块,这是最顶层的http 块   ngx_http.ngx_http_block                         #http模块set函数,包括很多操作,比如初始化好几张表结构(如loaction)等#block是块的意思,不是阻塞的意思ngx_conf_file.ngx_conf_parse                  #递归处理http嵌套块,ngxin是递归嵌套解析的,比如http里的server块ngx_conf_file.ngx_conf_handler  ngx_http_core_module.ngx_http_core_server #这里以server块为例...略...ngx_http.ngx_http_init_phases              #回到这里表明嵌套块都处理完了(递归特点决定的),可以进行一些操作了#http的处理阶段分为11个阶段,这里就列举其phases的初始化和server对象的创建,其他过程略  #这里给每个阶段的handler数组分配空间 ,就是说有很多个阶段,并且每个阶段的handler可以不止一个ngx_http.ngx_http_init_phase_handlers      #!!!给每个阶段都设置对应的checker和handler, 并平铺到一个数组phase_handler中#举例:pi,ci,hj分别表示:#第i个阶段,第i个阶段对应的checker,第i个阶段对应的第j个handler#最终数组为:phase_handler={c0-h0,c0-h1,c1-h0,c1-h1,c1-h2,c2-h0,.....}#这样处理请求的时候就只要一个for循环就可以调用所有阶段的所有handler#checker与handler:处理请求时,外部代码是调用checker,然后checker内部会调用handler,#checker主要用于控制 HTTP 请求在不同阶段(phase)的处理逻辑 #通常是对 handler 的返回值进行检查或进一步处理。#笔记:处理的时候默认是从handler数组的第0个元素开始调用handler,#但是对于内部请求等一些场景,并不需要从0开始,可以直接从某个pos直接开始,比如rewrite_index#一共有11个阶段,默认是一共有14个handler,即数组长度为14,可以百度,这里就不记录了#可以直接搜索NGX_HTTP_POST_READ_PHASE这个枚举值就可以找到整个枚举,一共11个阶段#笔记:elts表示数组基地址,nlts表示数组元素ngx_http.ngx_http_optimize_servers         #主要初始化listen socket相关结构ngx_http.ngx_http_init_listening         #!!!注意:只是初始化相关listen socket结构,并不会bind,#因为此时还是初始化阶段,不能打开端口,所以bind操作不在这里#等到conf处理完以及其他初始化工作都处理后才开启ngx_http.ngx_http_add_listeningls=ngx_create_listening  ls.handler=ngx_http_init_connection   #!!!设置connection handler。#!!!就是accept连接的时候accept_handler会初始化一个connection结构体#!!!然后填充部分参数,然后调用connection_handler来处理新建立的连接#!!!这里listenfd对新连接的处理函数设置为ngx_http_init_connection#!!!stream模块中则设置为ngx_stream_init_connection#!!!再次强调,ngx通过函数指针来模拟了多态,#!!!也就是同一套代码可以同时处理httpfd和streamfd4:stream块。流程和http模块一模一样,只是函数名字不同,比如ls.handler的名字分别为ngx_stream_init_connection和ngx_http_init_connectionngx_stream.ngx_stream_block                  #cmd->set函数,即配置块对应的解析函数,这里处理conf中的stream块即stream 模块ngx_conf_file.ngx_conf_parse               #递归处理stream的嵌套块ngx_conf_file.ngx_conf_handler  ...略...ngx_stream.ngx_stream_init_phases          #同http模块,不过stream只有7个阶段ngx_stream.ngx_stream_init_phase_handlers  #同http模块  ngx_stream.ngx_stream_optimize_servers     #同http模块......ngx_http.ngx_stream_add_listeningls=ngx_create_listening  ls.handler=ngx_stream_init_connection #!!!设置connection handler。#!!!就是accept连接的时候accept_handler会初始化一个connection结构体#!!!然后填充部分参数,然后调用connection_handler来处理新建立的连接#!!!下面的代码不管是http还是stream,都是同一个处理流程:注册到epollfd#!!!因为nginx中http和strema模块的不同在于处理函数的不同,而nginx把不同的handler封装在event_t结构体中#!!!而event_t结构体又包含在connection结构体中,clientfd存在epollfd中的就是connection结构,#!!!也就是说不管是http还是stream,用的都是connection结构,而他们的区别只有字段值的不同#!!!正因为如此,所以可以用同一个epollfd来同时处理httpserver-listenfd、streamserver-listenfd#!!!、httpclientfd、streamclientfd、streamupstreamfd上的读写事件ngx_connection.ngx_open_listening_sockets            #ngx_init_cycle函数中处理完配置文件后就打开所有的listenfdlistenfd=ngx_socket                                #创建sokcet。前面只是初始化listenfd所需的各种结构,但是listenfd是没有初始化的#这里创建fd后,直接复制给前面初始化的结构:xx.fd=listenfdsocket.bind                                        #绑定端口socket.listen                                      #开启监听,也就是这时候可以接受连接。注意:是可以接受连接#但是listenfd还没有注册到epoll中所以nginx不会accept连接,连接都会放到back队列ngx_connection.ngx_configure_listening_sockets       #对listenfd进行一些设置ngx_model.ngx_init_modules                           #!!!前面只是根据配置文件中的信息来调用对应模块的set函数,set函数不是init函数#因为是根据conf文件对模块进行设置,所以该函数指针才叫做set,这里就是init_modules模块#ngx规定了每个许多模块都必须实现的函数,init_modules是其中一个for modules->init_modulesngx_process_cycle.ngx_master_process_cycle             #main函数里调用这个,开始进入多进程#!!!master主要开启子进程并监控子进程,master不负责接受连接和处理连接ngx_process_cycle.ngx_start_worker_processes  for (i = 0; i < n; i++):                           #启动n个子进程ngx_process.ngx_spawn_processunistd.fork                                                          #fork子进程ngx_channel = ngx_processes[s].channel[1];                           #父子进程之间的通信通道ngx_proces_cycle.ngx_worker_process_cycle                            #这里是子进程里面的流程1:!!!初始化eventloop(初始化+注册相关fd到eventloop)ngx_process_cycle.ngx_worker_process_init                        #初始化处理进程cycle->modules[i]->init_process(cycle)                         #调用对应的模块的init_process(main函数里调用的是init_modules,别搞混了)ngx_event.ngx_event_process_init                             #初始化epoll并注册listenfd到epollfdngx_epoll_modules.ngx_epoll_initepoll.epoll_create                                       #调用linux epoll_create创建epollfdnotify_fd = eventfd(0, 0);                               #调用linux系统调用创建进程之间的事件通知notifyfdngx_epoll_modules.ngx_epoll_notify_init                  #注册notifyfd到epollfd中  rev->handler = ngx_event_accept;                           #!!!设置accept事件的处理函数为ngx_event_accept#!!!nginx把fd、handler都封装在ngx_event_t结构体中#!!!并作为epoll_events的data字段一同丢到epollfd中#!!!封装使得epoll_eventloop流程代码可以通用#!!!即httpfd事件还是streamfd事件还是listenfd事件ngx_add_event(rev, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT)    #!!!注册listenfd的read事件到epollfd,并且是exclusive模式#!!!accept_handler为ngx_event_acceptee.data.ptr = (void *) ((uintptr_t) c | ev->instance)    #data字段为c,c是一个connection对象指针epoll_ctl(ep, op, c->fd, &ee)                            #这样我们直接从data取出connection对象#也就是说connection对象保存了一个连接的所有状态#redis也是一样的操作,所以epoll类型的应该都是一样的操作ngx_channel.ngx_add_channel_event                              #把父子进程之间的通信fd注册到epoll中ngx_process_cycle.ngx_start_cache_manager_processes  #缓存管理器对应的进程2:执行eventloop,略,详情见eventloop源码流程

模块原理就是:有一个模块数组,里面存放了所有模块的配置信息,一个模块的配置信息包括:模块名、模块解析函数、其他,模块加载原理就是:读取conf文件,然后对于每一个顶层配置块,因为nginx要求配置块的名字必须模块的名字一模一样,所以nginx读取conf中的一个配置块的时候就能根据配置块的名字,获取模块配置信息,然后从中取出对应的模块解析函数,然后调用这个配置函数就能解析这个配置块了。

模块相关的全局变量和代码:

0:设置模块的模块名和索引:设置ngx_modules模块中每个模块的名字和索引for (i = 0; ngx_modules[i]; i++) {  ngx_modules[i]->index = i;ngx_modules[i]->name = ngx_module_names[i];}1:ngx_modules数组,保存了所有ngx_module_t结构,即保存了所有模块对应的变量
ngx_module_t *ngx_modules[] = {...,&ngx_stream_module,&ngx_stream_core_module,...,
}2:ngx_module_names数组,保存了所有模块的名字。注意:ngx_modules和ngx_module_names中的元素是一一对应的,顺序不能错
char *ngx_module_names[] = {        #这些都是nginx内建的模块...,"ngx_stream_module","ngx_stream_core_module",...,
}3:ngx_stream_module模块。ngx_modules数组存放的便是这个。主要关注ctx和commands这两个元素
ngx_module_t  ngx_stream_module = {NGX_MODULE_V1,&ngx_stream_module_ctx,                /* module context */ngx_stream_commands,                   /* module directives */NGX_CORE_MODULE,                       /* module type */NULL,                                  /* init master */NULL,                                  /* init module */NULL,                                  /* init process */NULL,                                  /* init thread */NULL,                                  /* exit thread */NULL,                                  /* exit process */NULL,                                  /* exit master */NGX_MODULE_V1_PADDING
};ngx_module_t  ngx_stream_core_module = {NGX_MODULE_V1,&ngx_stream_core_module_ctx,           /* module context */ngx_stream_core_commands,              /* module directives */NGX_STREAM_MODULE,                     /* module type */NULL,                                  /* init master */NULL,                                  /* init module */NULL,                                  /* init process */NULL,                                  /* init thread */NULL,                                  /* exit thread */NULL,                                  /* exit process */NULL,                                  /* exit master */NGX_MODULE_V1_PADDING
};4:ngx_stream_module_ctx。表示模块上下文。包含了一系列函数,就是说模块内定义了一些函数指针,ctx里就是这些指针指向的函数!!!redis也是这样设置的,比如CT_Socket变量,代码中用的都是函数指针, !!!然后专门用CT_Socket变量来保存一系列函数指针,用的时候直接从CT_Socket中取值
static ngx_core_module_t  ngx_stream_module_ctx = {ngx_string("stream"),NULL,NULL
};
static ngx_stream_module_t  ngx_stream_core_module_ctx = {ngx_stream_core_preconfiguration,      /* preconfiguration */NULL,                                  /* postconfiguration */ngx_stream_core_create_main_conf,      /* create main configuration */ngx_stream_core_init_main_conf,        /* init main configuration */ngx_stream_core_create_srv_conf,       /* create server configuration */ngx_stream_core_merge_srv_conf         /* merge server configuration */
};
static ngx_event_module_t  ngx_epoll_module_ctx = {          &epoll_name,ngx_epoll_create_conf,               /* create configuration */ngx_epoll_init_conf,                 /* init configuration */{                          ngx_epoll_add_event,             /* add an event */ngx_epoll_del_event,             /* delete an event */ngx_epoll_add_event,             /* enable an event */ngx_epoll_del_event,             /* disable an event */ngx_epoll_add_connection,        /* add an connection */ngx_epoll_del_connection,        /* delete an connection */
#if (NGX_HAVE_EVENTFD)ngx_epoll_notify,                /* trigger a notify */
#elseNULL,                            /* trigger a notify */
#endifngx_epoll_process_events,        /* process the events */ngx_epoll_init,                  /* init the events */ngx_epoll_done,                  /* done the events */}
};5:!!!ngx_stream_commands 
static ngx_command_t  ngx_stream_commands[] = {            #注意:这是一个数组,即一个模块可以有多个命令,但是这里只有一个命令,数组以null_command结束,#nginx源码通过null_command来判断命令是否结束#nginx很多数组都是判断最后一个元素是不是该类型的空元素来判断是不是已经到到末尾了#比如ph[parse_handler].check是否为null来判断是否已经遍历所有pharse了{ ngx_string("stream"),                                #指令名字,也是conf文件中对应的配置块名字。即nginx读取到stream配置块#ngx_stream_commands是顶层配置块对应的名字,一般只含有一个命令,#个人笔记:如果要给指令别名,直接添加一个对应的命令,然后设置相同的set函数#就会知道这是ngx_stream_commands模块的stream命令NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,ngx_stream_block,                                    #!!!模块的set函数,即cmd->set对应的函数,即模块解析函数,#比如读取到stream配置块就会调用ngx_stream_block函数解析stream配置块#nginx的配置文件中,写了,就解析,没有就不调用,就用默认的#nginx还加载和初始化所有模块,只不过有些模块可能不会起作用#比如这里stream模块,如果conf没写,nginx就不会创建对应的server,#写了,就解析,解析函数里会创建server0,0,NULL },ngx_null_command                                     #标记数组已经结束了
};static ngx_command_t  ngx_stream_core_commands[] = {       #ngx_stream_core模块包含很多stream块内部块对应的命令{ ngx_string("variables_hash_max_size"),NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1,ngx_conf_set_num_slot,NGX_STREAM_MAIN_CONF_OFFSET,offsetof(ngx_stream_core_main_conf_t, variables_hash_max_size),NULL },......ngx_null_command
};6:event modules
ngx_module_t  ngx_event_core_module = {NGX_MODULE_V1,&ngx_event_core_module_ctx,            /* module context */ngx_event_core_commands,               /* module directives */NGX_EVENT_MODULE,                      /* module type */NULL,                                  /* init master */ngx_event_module_init,                 /* init module 模块初始化操作*/  ngx_event_process_init,                /* init process ngx_init_process 是在工作进程(worker process)中执行的初始化函数,负责进程相关的初始化。*/NULL,                                  /* init thread */NULL,                                  /* exit thread */NULL,                                  /* exit process */NULL,                                  /* exit master */NGX_MODULE_V1_PADDING
};

1-2:stream accept源码流程

流程:如果是write事件就调用wev->handler,如果是read事件就调用rev->handler,listenfd只有读事件没有write事件,读事件的handler就是ngx_event_accept,然后ngx_event_accept处理连接的时候为该clientfd创建对应的connection结构,然后在connection结构体中填充rev->handler和wev->handler,然后连接对应的upstream,然后把upstream fd 写事件注册到epollfd,然后在第二轮eventloop upstream的写事件处理中重置upstreamfd的read/write handler,并把数据从src写到dst,然后注册src clientfd到epollfd中,支持完成,也就是原本正常的流程应该是accept,accept之后立马注册到epollfd,但是stream模块中

ngx_proces_cycle.ngx_worker_process_cycle                          #这里是子进程里面的流程1:!!!初始化eventloop(初始化+注册相关fd到eventloop)......ngx_add_event(rev, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT)        #!!!注册listenfd的read事件到epollfd,并且是exclusive模式......2:!!!执行eventLoopfor ; ;                                                          #死循环,即eventloop,事件循环ngx_event.ngx_process_events_and_timers                        #处理事件和定时器  ngx_epoll_module.ngx_epoll_process_events                    #到此处时已经是epoll_wait返回的fd了,所以直接处理fd就行了#!!!如果触发了读事件就调用rev->handler(rev=connection->read_events)#!!!如果触发了写事件就调用wev->handler(wev=connection->write_events)wev = c->write;if ((revents & EPOLLOUT) && wev->active) wev->ready = 1;wev->handler                                             #accept只注册了read事件,没有注册write事件,所以revents&EPOLLOUT=0#!!!accept事件对应读事件,所以调用rev->handlerif ((revents & EPOLLIN) && rev->active) :  rev->ready = 1;ngx_event_accept.ngx_event_accept         #rev->handler对应的函数就是ngx_event_acceptlc = ev->data;                          #ev->data就是我们分配给listenfd的connection结构,我们从中取出相关信息,比如handler  s=socket.accept4/socket.accept          #调用linux接受连接。一个for循环一次接受所有连接,并且clientfd设置为nonblockngx_accept_disabled = ngx_cycle->connection_n / 8       #!!!nginx通过这个来判断worker是否接受了过多连接- ngx_cycle->free_connection_n;     #算法是:如果空闲连接数少于已有连接数的八分之一,就认为连接过多,要禁止继续accept c=ngx_connection.ngx_get_connection(s)  #分配一个connection结构给clientfd。nginx缓存了一些空闲的connection结构,可减少内存分配次数c->recv = ngx_recv;                     #这里就是设置clientfd的相关handler到connection结构中。#ngx_recv与ngx_send都是处理tcp读取的,udp用的是ngx_udp_recv和ngx_udp_send#但是这里用的是指针,所以可以用同一个c->recv/c->send来同时处理tcp/udp,相当于c里面的多态#ngx_recv和ngx_recv_chain相比,只是调用场景不同,目的都是读取数据c->send = ngx_send;c->recv_chain = ngx_recv_chain;c->send_chain = ngx_send_chain;ngx_stream_handler.ngx_stream_init_connection                   #listenfd->handler,是在ngx_http.ngx_http_add_listening时设置的s = ngx_pcalloc(c->pool, sizeof(ngx_stream_session_t));       #创建sessions->connection = c;                                            #session保存connection的指针c->data = s;                                                  #conneciton保存session指针rev->handler = ngx_stream_session_handler;                    #rev即connection上读事件对应的event结构体#这里就是设置connection的read事件的handler为ngx_stream_session_handler#笔记:epollfd注册的clientfd对应的data字段是一个connection#connection有一个两个字段分别指向read events、write events#read/write events结构体中保存了处理read/write所需的各种handler#!!!再次强调:nginx中不管什么fd(listenfd/clientfd),都用同一套epoll流程,#!!!具体的事件怎么处理,则取决于封装在的events结构中的handler#!!!流程为:1:epoll_wait获取发生了事件的fd;#!!!2:从fd对应的data字段取出connection字段#!!!3:从connection中取出read/write events#!!!4:调用对应的events结构中的handler对事件进行处理ngx_stream_handler.ngx_stream_session_handlerngx_stream_core_module.ngx_stream_core_run_phases          while (ph[s->phase_handler].checker) {rc = ph[s->phase_handler].checker(s, &ph[s->phase_handler]);ngx_stream_core_module.ngx_stream_core_generic_phase  ngx_stream_set_module.ngx_stream_set_handlerngx_stream_core_module.ngx_stream_core_generic_phasengx_stream_limit_conn_module.ngx_stream_limit_conn_handlerngx_stream_core_module.ngx_stream_core_generic_phasengx_stream_access_module.ngx_stream_access_handlerngx_stream_core_module.ngx_stream_core_content_phasengx_stream_proxy_module.ngx_stream_proxy_handler    #与upstream建立连接,也就是说在clientfd 第一次连接stream服务器时#ngxin就建立与upstream的连接,因为stream就是一个转发操作,#所以在clientfd第一次到来时#在listenfd_accept_handler中建立与upstrema的连接c->write->handler = ngx_stream_proxy_downstream_handler;   #设置clientfd读事件对应的rev->handlerc->read->handler = ngx_stream_proxy_downstream_handler;    #设置clientfd写事件对应的wev->handler#!!!第一次循环会设置clientfd对应的handler,#!!!但是不会把clientfd注册到epollfdngx_stream_proxy_module.ngx_stream_proxy_connect  #!!!与peer updstream建立连接,并把upstream对应的fd注册到epollfd中c->log->action = "connecting to upstream";      #????是不是ngxin与upstream也是短连接所以导致#每次client发来消息时都要进行一次连接upstream?ngx_event_connect.ngx_event_connect_peer                                   #连接upstream并注册到epollfdngx_stream_upstream_round_robin.ngx_stream_upstream_get_round_robin_peer #用round_robin算法从n个upstream中选择一个,返回对应的配置信息fd=ngx_socket                                             #创建套接字ngx_epoll_module.ngx_epoll_add_event(fd)                  #ngx_add_conn对应的是ngx_epoll_add_event#创建套接字后把该连接注册到epollfd中#此时还没有连接,但是linux支持把未连接的fd注册到epollfd中ee.events = EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP;        #!!!add connection会注册读写事件epoll_ctl                                               #注册描述符c->read->active = 1;                                    #开放读c->write->active = 1;                                   #开放写#!!!只要可写就会触发write事件#也就是说client连接nginx stream server#然后nginx连接upstream,然后注册读写事件到epollfd中#下一轮eventloop中会立马触发upstream fd的写事件#在写事件中会把刚才收到的来自clientfd的数据写到upstreamfd中#即转发给upstreampc->read->handler = ngx_stream_proxy_connect_handler;       pc->write->handler = ngx_stream_proxy_connect_handler;  #设置upstream fd的write_handler#!!!stream流中accept的时候不会把clientfd注册到epoll#!!!而是会在ngx_stream_proxy_connect_handler中注册到epoll#即第二次事件循环的时候才把clientfd注册到epollsocket.connect(upstream_addr,fd)  #连接upstreamngx_event_connection.ngx_reorder_accept_events(ls)    #epoll启用EPOLLEXCLUSIVE后发生accept事件时知会通知第一个进程,#所以为了公平,需要进行reorder处理,ls表示监听描述符,每个子进程都拥有一个相同的listenfd#这里的逻辑就是:accept一个连接,就在epoll中删除并重新添加一次listenfd,#从而让其他子进程有机会accept新连接(linux EPOLLEXCLUSIVE怎么支持这个,还需要自己看一下)#默认只有一个worker,无需reorder,所以需要配置一下if workers<=1:returnngx_del_event(c->read, NGX_READ_EVENT, NGX_DISABLE_EVENT)     #从epoll中删除该listenfd读事件ngx_add_event(c->read, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT)   #向epoll中添加该listenfd的读事件----杂记----
nginx是一个connection对象,然后connection对象里包含了两个event对象,分别表示read event和write event,nginx对于event结构体的内存管理:预先分配一个足够大的数据,然后分配给某个connection,然后epoll_wait唤醒后,直接取出这个event,然后再填充信息到这个event,就不用用时分配

1.3 upstream 第一次write事件

只是进行相关设置,并不会真的从src读和写dst

ngx_proces_cycle.ngx_worker_process_cycle                          #这里是子进程里面的流程1:!!!初始化eventloop(初始化+注册相关fd到eventloop)......ngx_add_event(rev, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT)        #!!!注册listenfd的read事件到epollfd,并且是exclusive模式......2:!!!执行eventLoopfor ; ;                                                          #死循环,即eventloop,事件循环ngx_event.ngx_process_events_and_timers                        #处理事件和定时器  ngx_epoll_module.ngx_epoll_process_events                    #到此处时已经是epoll_wait返回的fd了,所以直接处理fd就行了#!!!如果触发了读事件就调用rev->handler(rev=connection->read_events)#!!!如果触发了写事件就调用wev->handler(wev=connection->write_events)wev = c->write;if ((revents & EPOLLOUT) && wev->active) wev->ready = 1;ngx_stream_proxy_module.ngx_stream_proxy_connect_handler ngx_stream_proxy_module.ngx_stream_proxy_init_upstream    #进行一些初始化操作pc->read->handler = ngx_stream_proxy_upstream_handler;  #!!!accept中连接upstreamd的时候handler=ngx_stream_proxy_connect_handler pc->write->handler = ngx_stream_proxy_upstream_handler; #!!!handler=ngx_stream_proxy_connect_handler #为第一次连接upstream,做些初始化操作#!!!只在第一次的时候调用,第一次调用完成初始化之后就可以正常处理upstream 事件了#!!!即ngx_stream_proxy_upstream_handler负责处理upstream事件ngx_stream_proxy_module.ngx_stream_proxy_process(from_upstream=0,dowrite=false) #!!!第二次write的时候就直接进到这里,就会跳过init阶段if (from_upstream) {                                  #!!!这里就是根据from_upstream来决定:#是从client读然后发送给upstream还是从upstream读然后转发给clientsrc = pc;dst = c;recv_action = "proxying and reading from upstream";send_action = "proxying and sending to client";} else {src = c;                                           #c表示client和nginx之间的连接dst = pc;                                          #pc表示nginx和upstream之间的连接recv_action = "proxying and reading from client";send_action = "proxying and sending to upstream";}{if (*out || *busy || dst->buffered)                    #!!!处理upstream第一次写事件时,这个if不满足,所以第一次不会从src读和写dst#!!!也就是说upstream第一次读只是进行相关设置#!!!待注册clientfd到epoll后处理clientfd读事件时这个if才为true#笔记:clientfd对应的read_evevnt_handler#为ngx_stream_proxy_downstream_handlern = src->recv(src, b->last, size)                    #从src读,这里是从client读取,src->recv是一个函数指针,ngx_recv.ngx_unix_recv                             #其值为ngx_unix_recv函数,ngx_unix_recv函数内部调用linux recv读取网络数据socket2.recv  dowrite=true                                         #读完以后就可以写了ngx_stream.ngx_stream_top_filter                     #写数据到dst,此处即写数据到upstream,ngx_stream_top_filter也是一个函数指针ngx_stream_write_filter_module.ngx_stream_write_filter #其值为ngx_stream_write_filter,就是一个过滤链,和http写流程差不多ngx_linux_sendfile_chain.ngx_linux_sendfile_chain    #调用linux sendfile或者writev来发送数据给upstreamif xxx:ngx_linux_sendfile_chain.ngx_linux_sendfile      #如果满足某种条件就走sendfileelse:ngx_writev_chain.ngx_writev                      #不过一般是走writevuio.writev                                     #调用linux writev#!!!执行完这条语句,数据就交给os了,和nginx无关了#!!!也就是说在这条语句之后backend就会收到数据即使nginx卡住了#到此处已执行完stream的主要操作,后面就是一些清理工作了}flags = src->read->eof ? NGX_CLOSE_EVENT : 0;                #读src并写dst后,如果client已经关闭连接,那么就清除这个连接上的读事件ngx_event.ngx_handle_read_event(src->read, flags)            #!!!把clientfd注册到epollfd,此处src=clientfd,flags=0ngx_add_event(NGX_READ_EVENT,NGX_CLEAR_EVENT)              #后续eventloop就会处理来自client的stream读事件#clientfd的read/write handler均为ngx_stream_proxy_downstream_handler#这是在accept clientfd是处理的#!!!#define NGX_CLEAR_EVENT EPOLLET 就是说nginx默认是边缘触发  ngx_event.ngx_handle_write_event(dst->write, flags)          #注册upstream写事件。注意:此处不会注册写事件到epoll                                                   if !wev->active && !wev->ready:                            #此时这个if为false,因为只有client发,upstream才能写,#但是刚才已经写完了,所以wev->active==1#即在此处ngx_handle_read_event相当于是一个空操作,可忽略ngx_add_event(epollfd,upstreamfd,NGX_WRITE_EVENT)  ngx_stream_proxy_module.ngx_stream_proxy_test_finalize       #另一些清理工作ngx_stream_proxy_module.ngx_stream_proxy_finalize          #尝试结束一个stream请求。和ngx_http_finalize_request类似

1.4:clientfd read

从src读,然后写dst,即从client读,写upstream。这也正好说明了nginx stream功能就是一个转发的功能

ngx_proces_cycle.ngx_worker_process_cycle                          #这里是子进程里面的流程1:!!!初始化eventloop(初始化+注册相关fd到eventloop)......ngx_add_event(rev, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT)        #!!!注册listenfd的read事件到epollfd,并且是exclusive模式......2:!!!执行eventLoopfor ; ;                                                          #死循环,即eventloop,事件循环ngx_event.ngx_process_events_and_timers                        #处理事件和定时器  ngx_epoll_module.ngx_epoll_process_events                    #到此处时已经是epoll_wait返回的fd了,所以直接处理fd就行了#!!!如果触发了读事件就调用rev->handler(rev=connection->read_events)#!!!如果触发了写事件就调用wev->handler(wev=connection->write_events)rev = c->read;if ((revents & EPOLLIN) && rev->active) :rev->read=1ngx_stream_proxy_module.ngx_stream_proxy_downstream_handler  #accept的时候就设置了clientfd对应的read handler为这个函数#downstream_handler是对process_connection函数的一个包装#upstream_handler内部也是调用的process_connection函数#downstream和upstream的区别在于src和dst不同,#process_connection通过from_upstream参数来判断src是clientfd还是upstreamfd#downstream中clientfd 没有设置write所以ev->write=null即from_upstream=0#即表示这个请求来自clientfdngx_stream_proxy_module.ngx_stream_proxy_process_connection(from_upstream=ev->write) ngx_stream_proxy_module.ngx_stream_proxy_process(from_upstream=0,dowrite=false)  if (from_upstream) {                                    #这里就是根据from_upstream来决定:#是从client读然后发送给upstream还是从upstream读然后转发给clientsrc = pc;dst = c;recv_action = "proxying and reading from upstream";send_action = "proxying and sending to client";} else {src = c;                                           #c表示client和nginx之间的连接dst = pc;                                          #pc表示nginx和upstream之间的连接recv_action = "proxying and reading from client";send_action = "proxying and sending to upstream";}n = src->recv(src, b->last, size)                    #从src读,这里是从client读取,src->recv是一个函数指针,ngx_recv.ngx_unix_recv                             #其值为ngx_unix_recv函数,ngx_unix_recv函数内部调用linux recv读取网络数据socket2.recv  dowrite=true                                              #读完以后就可以写了ngx_stream.ngx_stream_top_filter                          #写数据到dst,此处即写数据到upstream,ngx_stream_top_filter也是一个函数指针ngx_stream_write_filter_module.ngx_stream_write_filter  #其值为ngx_stream_write_filter,就是一个过滤链,和http写流程差不多ngx_linux_sendfile_chain.ngx_linux_sendfile_chain     #调用linux sendfile或者writev来发送数据给upstreamif xxx:ngx_linux_sendfile_chain.ngx_linux_sendfile       #如果满足某种条件就走sendfileelse:ngx_writev_chain.ngx_writev                       #不过一般是走writevuio.writev                                      #调用linux writev#!!!执行完这条语句,数据就交给os了,和nginx无关了#!!!也就是说在这条语句之后backend就会收到数据即使nginx卡住了#到此处已执行完stream的主要操作,后面就是一些清理工作了flags = src->read->eof ? NGX_CLOSE_EVENT : 0;                #读src并写dst后,如果client已经关闭连接,那么就清除这个连接上的读事件ngx_event.ngx_handle_read_event(src->read, flags)            #!!!把clientfd注册到epollfd,此处src=clientfd,flags=0ngx_add_event(epollfd,clientfd,NGX_READ_EVENT)             #后续eventloop就会处理来自client的stream读事件#clientfd的read/write handler均为ngx_stream_proxy_downstream_handler#这是在accept clientfd是处理的   ngx_event.ngx_handle_write_event(dst->write, flags)          #注册upstream写事件。注意:此处不会注册写事件到epoll                                                     if !wev->active && !wev->ready:                            #此时这个if为false,因为只有client发,upstream才能写,#但是刚才已经写完了,所以wev->active==1 wev->ready=1#即在此处ngx_handle_read_event相当于是一个空操作,可忽略#也即是说写upstreamd的操作是直接在clientfd read事件的处理过程中就写完了#而不是通过epoll write时间来写ngx_add_event(epollfd,upstreamfd,NGX_WRITE_EVENT)  ngx_stream_proxy_module.ngx_stream_proxy_test_finalize       #另一些清理工作ngx_stream_proxy_module.ngx_stream_proxy_finalize          #尝试结束一个stream请求。和ngx_http_finalize_request类似

2.6:upstream read

nginx stream功能是一个转发的功能,client请求发给nginx,然后nginx会转发给upstream,而upstream处理后也会把结果返回给nginx,就是会触发upstreamfd的读事件,nginx在upstreamfd的读事件处理过程中就把数据转发给clientfd,也就是说nginx stream就是一个转发的功能,还有nginx是通过直接发送而不是通过clientfd eventloop write事件来发送的。

!!!!upstream read功能和downstream功能流程一模一样,再次强调,nginx stream就是一个转发的过程

downstream read事件数据流程: client --> nginx --> upstream

upstream read事件数据流程: client <-- nginx <-- upstream

可以看到nginx在这两个read事件的过程中起的作用都是一样的,把数据从src转发到dst,所以代码流程也是一样的

ngx_proces_cycle.ngx_worker_process_cycle                          #这里是子进程里面的流程1:!!!初始化eventloop(初始化+注册相关fd到eventloop)......ngx_add_event(rev, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT)        #!!!注册listenfd的read事件到epollfd,并且是exclusive模式......2:!!!执行eventLoopfor ; ;                                                          #死循环,即eventloop,事件循环ngx_event.ngx_process_events_and_timers                        #处理事件和定时器  ngx_epoll_module.ngx_epoll_process_events                    #到此处时已经是epoll_wait返回的fd了,所以直接处理fd就行了#!!!如果触发了读事件就调用rev->handler(rev=connection->read_events)#!!!如果触发了写事件就调用wev->handler(wev=connection->write_events)rev = c->read;if ((revents & EPOLLIN) && rev->active) :rev->read=1ngx_stream_proxy_module.ngx_stream_proxy_upstream_handler    #第一次处理upstream write事件的时候就设置了write handler为这个#upstream_handler是对process_connection函数的一个包装#downstream和upstream的区别在于src和dst不同,#process_connection通过from_upstream参数来判断src是clientfd还是upstreamfd#upstream中upstreamfd设置了write即!ev->write=!null即from_upstream=1#即表示这个请求来自clientfdngx_stream_proxy_module.ngx_stream_proxy_process_connection(from_upstream=!ev->write) #!!!在这之后的流程同downstream流程一模一样ngx_stream_proxy_module.ngx_stream_proxy_process(from_upstream=0,dowrite=false)  if (from_upstream) {                                    #这里就是根据from_upstream来决定:#是从client读然后发送给upstream还是从upstream读然后转发给clientsrc = pc;                                             #pc表示nginx和upstream之间的连接dst = c;                                              #c表示client和nginx之间的连接recv_action = "proxying and reading from upstream";send_action = "proxying and sending to client";} else {src = c;                                    dst = pc;                                   recv_action = "proxying and reading from client";send_action = "proxying and sending to upstream";}n = src->recv(src, b->last, size)                    #从src读,这里是从upstreamfd读取,src->recv是一个函数指针,ngx_recv.ngx_unix_recv                             #其值为ngx_unix_recv函数,ngx_unix_recv函数内部调用linux recv读取网络数据socket2.recv  dowrite=true                                              #读完以后就可以写了ngx_stream.ngx_stream_top_filter                          #写数据到dst,此处即写数据到clientfd,ngx_stream_top_filter也是一个函数指针ngx_stream_write_filter_module.ngx_stream_write_filter  #其值为ngx_stream_write_filter,就是一个过滤链,和http写流程差不多ngx_linux_sendfile_chain.ngx_linux_sendfile_chain     #调用linux sendfile或者writev来发送数据给upstreamif xxx:ngx_linux_sendfile_chain.ngx_linux_sendfile       #如果满足某种条件就走sendfileelse:ngx_writev_chain.ngx_writev                       #不过一般是走writevuio.writev                                      #调用linux writev#!!!执行完这条语句,数据就交给os了,和nginx无关了#!!!也就是说在这条语句之后backend就会收到数据即使nginx卡住了#到此处已执行完stream的主要操作,后面就是一些清理工作了flags = src->read->eof ? NGX_CLOSE_EVENT : 0;                #读src并写dst后,如果upstream已经关闭连接,那么就清除这个连接上的读事件ngx_event.ngx_handle_read_event(src->read, flags)            #把upstream注册到epollfd,但是以前就注册了,所以这里相当于空操作ngx_add_event(epollfd,clientfd,NGX_READ_EVENT)             #后续eventloop就会处理来自client的stream读事件#upstream的read/write handler均为ngx_stream_proxy_upstream_handlerngx_event.ngx_handle_write_event(dst->write, flags)          #注册client写事件。注意:此处不会注册写事件到epoll                                                     if !wev->active && !wev->ready:                            #此时这个if为false #即在此处ngx_handle_read_event相当于是一个空操作,可忽略#也即是说写upstreamd的操作是直接在clientfd read事件的处理过程中就写完了#而不是通过epoll write事件来写ngx_add_event(epollfd,upstreamfd,NGX_WRITE_EVENT)  ngx_stream_proxy_module.ngx_stream_proxy_test_finalize       #另一些清理工作ngx_stream_proxy_module.ngx_stream_proxy_finalize          #尝试结束一个stream请求。和ngx_http_finalize_request类似

– – - - - - – - 杂记-- - - – - – - -

keepalive和wait:最开始的handler是wait,但是在finalize里面会设置成keepalive

– – — – – --杂记-- - - - - - - - –

https://www.nginx.org.cn/article/detail/98

杂记:if ((ngx_event_flags & NGX_USE_EPOLL_EVENT)&& ccf->worker_processes > 1) 前者为真,但是worker_process==1

nginx是多进程,子进程通过读取conf来确定请求对应的处理模块

,nginx配置servername为x,也必须在client的主机上修改host,让ipx指向域名x,这样才能访问nginx。如果clinet上不修改host,那么就会因为无法解析域名而导致访问不到ip地址ipx,即访问不到nginx

反向代理:可以向用户隐藏服务端的真实url,更安全

nginx是一种代理,client的请求通过nginx发往对应的backend,backend处理完后返回给nginx

动静分离:静态页面直接放到nginx,这样访问静态页面就直接由nginx返回,动态请求则转发给backend

keepalived是一个第三方软件,可以监视nginx并转移流量

证书:直接配一个证书路径就行

如果是多台nginx服务器,用户先在a服务器上登陆,然后后续请求到了b服务器,为了解决登陆问题,可以用一个共享的redis缓存来存放session

?nginx如何执行lua,是不是和redis一样

nginx 基于ip的虚拟主机:server同时配ip和端口,基于端口的虚拟机主机:server只配端口,基于域名的虚拟主机:server同时配域名和端口。虚拟主机的意思是,这些虚拟主机可以在同一个物理机上,nginx可以在同一个端口上运行多个虚拟主机。

可以试着从如下角度查看:多进程reactor的封装(处理io、处理连接断开,包括accept用户以及主动connect backend)、11个流程封装

也可以试着从:内存池、线程池、原子操作、共享内存、红黑树

笔记:redis没做的:内存管理、删除

笔记:同步里的条件变量:虚假唤醒时需要重新检测条件是否满足,即循环判断,满足后再去尝试获得锁

共享内存:worker可以共用的内存

Nginx 的 worker 进程是用来处理客户端请求的,它们是在 master 进程 启动时创建的,每个 worker 进程是独立的,并且通常会根据 Nginx 的配置文件,处理来自所有虚拟主机的所有请求。

Nginx 的虚拟主机配置可以通过反向代理将请求分配到不同的物理机上,就是说worker和虚拟主机无关,worker处理的是nginx收到的请求,这个nginx收到的请求肯定是nginxworker来处理,只不过woker处理的时候会根据conf中虚拟主机部分的配置信息来处理


http://www.mrgr.cn/news/64541.html

相关文章:

  • VSCode 1.82之后的vscode server离线安装
  • Aurora 64b/66bIP核学习
  • 头歌——数据库系统原理(数据高级查询实验1)
  • Prometheus套装部署到K8S+Dashboard部署详解
  • PD取电快充协议芯片,XSP08Q在灯具中的应用
  • 油饼,舌尖上的思乡情
  • 利士策分享,青年心向新潮,未来可期
  • 大模型入门(二)—— PEFT
  • 【JavaEE初阶 — 多线程】Thread的常见构造方法&属性
  • 【Python】全面解析Python中的GIL(全局解释器锁):多线程与多进程的实战与抉择
  • asrpro 库制作
  • 样本不均衡与异常点检测处理|SMOTE|LOF|IForest
  • SVN 提交操作
  • 【语义分割|代码解析】CMTFNet-4: CNN and Multiscale Transformer Fusion Network 用于遥感图像分割!
  • 非线性数据结构之图
  • Python编程风格:保持逻辑完整性
  • Linux运行Java程序,并按天输出日志
  • 【Orange Pi 5 Linux 5.x 内核编程】-设备驱动中的sysfs
  • 【单片机C51两个按键K1、K2控制8个LED灯,初始值0xFE。摁下一次K1,LED灯左移;摁下一次K2,LED灯右移;】2022-1-5
  • 再学FreeRTOS---(中断管理)
  • 智能指针、移动语义、完美转发、lambda
  • 数字信号处理Python示例(3)生成三相正弦信号
  • 鸿蒙开发案例:分贝仪
  • Android中的Handle底层原理
  • 如何设置和使用低代码平台中的点击事件?
  • redis源码系列--(二)--eventlooop+set流程