因为须要把大量连接从用户空间复制到内核空间,所以开销巨大,因此,使用poll、select事件驱动方式,最大并发数量仅仅能达到几千。Linux 2.6版本号之后加入了epoll函数接口,
使得最大并发数量能够达到百万级。epoll的使用方法例如以下:- 调用epoll_create建立一个epoll对象。
- 调用epoll_ctl向epoll对象中加入连接套接字。
- 调用epoll_wait收集发生事件的连接。
- struct rb_root rbr; // 一棵红黑树。保存全部通过epoll_ctl加入进来的须要监控的事件
- struct list_head rdllist; // 一个双向链表,保存将要通过epoll_wait返回的、满足条件的事件
首先是决定解析哪些配置项的ngx_command_t结构体数组:
typedef struct { ngx_uint_t events; /* epoll_wait的參数3:一次最多能够返回的事件数 */ ngx_uint_t aio_requests; } ngx_epoll_conf_t; static ngx_command_t ngx_epoll_commands[] = { /* epoll_wait系统调用一次最多能够返回的事件数 */ { ngx_string("epoll_events"), NGX_EVENT_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, /* 提前定义方法解析配置项 */ 0, offsetof(ngx_epoll_conf_t, events), NULL }, /* 异步I/O相关 */ { ngx_string("worker_aio_requests"), NGX_EVENT_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, /* 提前定义方法解析配置项 */ 0, offsetof(ngx_epoll_conf_t, aio_requests), NULL }, ngx_null_command };
static ngx_str_t epoll_name = ngx_string("epoll"); ngx_event_module_t ngx_epoll_module_ctx = { &epoll_name, /* "epoll" */ ngx_epoll_create_conf, /* 创建存储配置项的结构体 */ ngx_epoll_init_conf, /* 解析完配置项后调用的函数 */ /* ngx_event_actions_t */ { 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 */ NULL, /* process the changes */ ngx_epoll_process_events, /* process the events */ ngx_epoll_init, /* init the events */ ngx_epoll_done, /* done the events */ } };
- 调用epoll_create创建epoll对象。
- 创建event_list数组接收从内核传过来的事件。
static int ep = -1; // epoll对象描写叙述符 static struct epoll_event *event_list; // 作为epoll_wait的參数,接收从内核传过来的事件 static ngx_uint_t nevents; // 可以返回的事件最大数目,同一时候也是event_list数组大小 /* 在ngx_event_core_module中调用,主要完毕两件事情: * 1、调用epoll_create方法创建epoll对象 * 2、创建event_list数组用于从内核接收发生的事件 */ static ngx_int_t ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer) { ngx_epoll_conf_t *epcf; /* 获取存储配置项的结构体 */ epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module); if (ep == -1) { /* 系统调用创建epoll对象,參数表示须要处理的事件的大致数目 * Linux内核中不处理这个參数 */ ep = epoll_create(cycle->connection_n / 2); #if (NGX_HAVE_FILE_AIO) /* 异步I/O相关 */ ngx_epoll_aio_init(cycle, epcf); #endif } if (nevents < epcf->events) { if (event_list) { ngx_free(event_list); } /* 初始化event_list数组,数组大小是配置项epoll_events的參数 */ event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events, cycle->log); } /* nevents相同是epoll_events配置项的參数 */ nevents = epcf->events; /* 指明读写I/O的方法 */ ngx_io = ngx_os_io; /* ngx_event_actions是个全局的ngx_event_actions_t结构体 * 用于存储事件模块的10个函数接口 */ ngx_event_actions = ngx_epoll_module_ctx.actions; #if (NGX_HAVE_CLEAR_EVENT) ngx_event_flags = NGX_USE_CLEAR_EVENT // 使用epoll的边缘触发模式 #else ngx_event_flags = NGX_USE_LEVEL_EVENT // 使用epoll的水平触发模式 #endif |NGX_USE_GREEDY_EVENT |NGX_USE_EPOLL_EVENT; return NGX_OK; }
/* 把一个感兴趣的事件加入到epoll中 */ static ngx_int_t ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags) { int op; uint32_t events, prev; ngx_event_t *e; ngx_connection_t *c; struct epoll_event ee; /* 每一个事件的data成员都存放着其相应的ngx_connection_t连接 */ c = ev->data; /* events代表事件类型。在以下设置 */ events = (uint32_t) event; if (event == NGX_READ_EVENT) { /* 写事件 */ e = c->write; prev = EPOLLOUT; #if (NGX_READ_EVENT != EPOLLIN|EPOLLRDHUP) events = EPOLLIN|EPOLLRDHUP; #endif } else { /* 读事件 */ e = c->read; prev = EPOLLIN|EPOLLRDHUP; #if (NGX_WRITE_EVENT != EPOLLOUT) events = EPOLLOUT; #endif } /* 依据是否为活跃事件确定是改动还是加入事件 */ if (e->active) { op = EPOLL_CTL_MOD; /* 改动epoll中的事件 */ events |= prev; } else { op = EPOLL_CTL_ADD; /* 加入新事件到epoll中 */ } /* 设置事件类型 */ ee.events = events | (uint32_t) flags; /* data的ptr成员指向一个连接,同一时候把最低位设置为instance标志,事件分发程序将这个标志提取出来 */ ee.data.ptr = (void *) ((uintptr_t) c | ev->instance); /* 调用epoll_ctl方法加入或改动事件 * 參数1:epoll对象描写叙述符 * 參数2:表示要运行的操作 * EPOLL_CTL_ADD:加入新事件到epoll中 * EPOLL_CTL_MOD:改动epoll中的事件 * EPOLL_CTL_DEL:删除epoll中的事件 * 參数3:待监听的连接套接字 * 參数4:描写叙述事件的结构体epoll_event */ if (epoll_ctl(ep, op, c->fd, &ee) == -1) { ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, "epoll_ctl(%d, %d) failed", op, c->fd); return NGX_ERROR; } /* 改动active标志,表示当前事件是活跃的 */ ev->active = 1; return NGX_OK; }
/* 收集、分发事件 */ static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags) { int events; uint32_t revents; ngx_int_t instance, i; ngx_uint_t level; ngx_err_t err; ngx_event_t *rev, *wev, **queue; ngx_connection_t *c; /* NGX_TIMER_INFINITE == INFTIM */ /* 等待获取事件,最长等待时间为timer以保证时间可以得到更新 * 參数1:epoll对象描写叙述符 * 參数2:保存返回的就绪事件数组 * 參数3:可以返回的最大事件数目 * 參数4:最长等待时间 * 返回值:就绪事件个数 */ events = epoll_wait(ep, event_list, (int) nevents, timer); err = (events == -1) ?ngx_errno : 0; if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) { ngx_time_update(); /* 更新时间 */ } .... if (events == 0) { if (timer != NGX_TIMER_INFINITE) { return NGX_OK; } return NGX_ERROR; } ngx_mutex_lock(ngx_posted_events_mutex); /* 遍历本次返回的全部事件 */ for (i = 0; i < events; i++) { c = event_list[i].data.ptr; /* ptr指向事件相应的连接 */ /* 提取出instance标志 */ instance = (uintptr_t) c & 1; /* 屏蔽最后一位计算出真正的连接对象的地址 */ c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1); /* 取出读事件 */ rev = c->read; /* 推断这个读事件是否过期 */ if (c->fd == -1 || rev->instance != instance) continue; /* 以过期。不处理 */ /* 获得事件类型 */ revents = event_list[i].events; .... /* 假设是读事件且该事件是活跃的 */ if ((revents & EPOLLIN) && rev->active) { .... /* 延后处理这批事件 */ if (flags & NGX_POST_EVENTS) { /* 依据是新连接事件还是普通事件选择不同的队列 */ queue = (ngx_event_t **) (rev->accept ? &ngx_posted_accept_events : &ngx_posted_events); /* 将事件加入到延后运行队列中 */ ngx_locked_post_event(rev, queue); } else { rev->handler(rev); /* 不须要延后,则马上处理事件 */ } } /* 取出写事件 */ wev = c->write; if ((revents & EPOLLOUT) && wev->active) { /* 推断是否过期 */ if (c->fd == -1 || wev->instance != instance) continue; .... if (flags & NGX_POST_EVENTS) { /* 将写事件加入到延后处理队列 */ ngx_locked_post_event(wev, &ngx_posted_events); } else { wev->handler(wev); /* 马上处理这个事件 */ } } } ngx_mutex_unlock(ngx_posted_events_mutex); return NGX_OK; }