accpet 的初始化和调用可以从work进程的启动开始讲起
在unix系平台上,work进程的主函数是ngx_worker_process_cycle
ngx_worker_process_cycle首先会执行进程初始化动作,调用的是ngx_worker_process_init,这个函数会执行一些初始化进程环境变量,信号的任务,以及各个I/O模型的初始化
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
continue;
}
if (ngx_modules[m]->ctx_index != ecf->use) {
continue;
}
module = ngx_modules[m]->ctx;
if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
/* fatal */
exit(2);
}
break;
}在前面章节已经介绍过,随后会ngx_event_process_init,这个函数的将会执行accept相关的初始化
首先会初始化cycle的connecntion:
cycle->connections =
ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
if (cycle->connections == NULL) {
return NGX_ERROR;
}
c = cycle->connections;
cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
cycle->log);
if (cycle->read_events == NULL) {
return NGX_ERROR;
}
rev = cycle->read_events;
for (i = 0; i < cycle->connection_n; i++) {
rev[i].closed = 1;
rev[i].instance = 1;
}1个connection就是一个socket,c->fd即是socket句柄,connection_n表示一个cycle包含多少已listen的socket
而cycle->listening里面包含了所有的socket,现在需要把cycle里面的socke复制到cycle->connections这个数组里面,首先是给这个数组填充一些默认值,fd设置成-1,
然后吧第一个成员的指针赋予cycle->free_connections,代码如下:
do {
i--;
c[i].data = next;
c[i].read = &cycle->read_events[i];
c[i].write = &cycle->write_events[i];
c[i].fd = (ngx_socket_t) -1;
next = &c[i];
} while (i);
cycle->free_connections = next;
cycle->free_connection_n = cycle->connection_n;cycle->listening包含了已经进入被动监听状态的socket数组,这是在nginx启动过程中完成的,
插一段socket的初始化过程,callstack如下
>>>>>>>>>>>>>>>>>>>
ngx_http_add_listening
ngx_http_init_listening
ngx_http_optimize_servers
ngx_http_block
ngx_conf_handlerr
ngx_conf_parse
ngx_init_cycle
>>>>>>>>>>>>>>>>>>>再看ngx_init_cycle的代码,可以看出,实际上是在ngx_conf_parse里面根据地址端口分配了相应准备listen的socket的数据结构,然后执行调用ngx_open_listening_sockets来真正执行listen,所有进入listen状态的socket会通过*cycle传递给所有的work_process,就是前面提到的cycle->listening。
随后,调用ngx_get_connection来把cycle->listening里面的fd真正赋给connect里面的每个成员。ngx_get_connection有一个重要的操作是把connection和他的成员read互相索引:
rev = c->read;
rev->data = c
接着注册accept的回调函数,win32的iocp会有一个单独的处理函数 rev->handler = ngx_event_acceptex;
其他的则是rev->handler = ngx_event_accept;
再调用ngx_add_event,向I/O模型注册异步通知,具体细节在前面章节有描述。由于socket在listen会直接进入可读状态,那么之前注册的ngx_event_accept会被触发,我们看看它的具体实现,细节暂不谈,最重要的是这一行:ls->handler(c);
这里调用的handler正是在ngx_http_add_listening里面注册的ls->handler = ngx_http_init_connection;这样,就跟前面章节介绍的HTTP流程的起点对接上了。
最后说总结一下nginx的这个accept模型,首先他在master process里面绑定套接口并进入listen,随后启动的work process共享这些socket,但并没有直接调用accept,而是让socket在listen阶段等待客户端连接,当有客户端发起了连接并自动完成三次握手之后,再发出异步完成消息,并调用注册的回调函数ngx_event_accept或者ngx_event_acceptex,从而给这个客户端生成一个新socket。
其实在win32平台,由于accept有异步版本的acceptex,因此可以不用在listen之后就进入异步通知状态,而是直接向listen状态的socket直接投递1个或者多个异步acceptex调用,并在event_process直接处acceptex的异步返回即可,这样接受并发的连接的数量应该更高。但是在nginx中对win,unix的所有I/O模型都采用了在listen进入异步通知的方式,可能是在unix没有acceptex这样真正的异步I/O,为了一个框架兼容这2个平台而采取的折中办法。
















