1. 概述

ngx_event_core_module 模块是一个事件类型的模块,它在所有事件模块中的顺序是第一位。它主要完成以下两点任务:

  • 创建连接池(包括读/写事件);
  • 决定究竟使用哪些事件驱动机制,并初始化将要使用的事件模块

2. 配置项

static ngx_command_t  ngx_event_core_commands[] = {

    /* 连接池的大小,也就是每个 worker 进程中支持的 TCP 最大连接数 */
{ ngx_string("worker_connections"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_connections,
0,
0,
NULL }, /* 确定选择哪一个事件模块作为事件驱动机制 */
{ ngx_string("use"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_use,
0,
0,
NULL }, /* 对应事件定义 ngx_event_s 结构体的成员 available 字段。对于 epoll 事件驱动模式来说,
* 意味着在接收到一个新连接事件时,调用 accept 以尽可能多地接收连接 */
{ ngx_string("multi_accept"),
NGX_EVENT_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
0,
offsetof(ngx_event_conf_t, multi_accept),
NULL }, /* 确定是否使用 accept_mutex 负载均衡锁,默认为开启 */
{ ngx_string("accept_mutex"),
NGX_EVENT_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
0,
offsetof(ngx_event_conf_t, accept_mutex),
NULL }, /* 启用 accept_mutex 负载均衡锁后,延迟 accept_mutex_delay 毫秒后再试图处理新连接事件 */
{ ngx_string("accept_mutex_delay"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
0,
offsetof(ngx_event_conf_t, accept_mutex_delay),
NULL }, /* 需要对来自指定 IP 的 TCP 连接打印 debug 级别的调试日志 */
{ ngx_string("debug_connection"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_debug_connection,
0,
0,
NULL }, ngx_null_command
};

2.1 ngx_event_conf_t 结构体

typedef struct {
/* 连接池的大小 */
ngx_uint_t connections;
/* 选用的事件模块在所有事件模块中的序号,即 ctx_index 成员 */
ngx_uint_t use; /* 标志位,为 1 时表示在接收到一个新连接事件时,一次性建立尽可能多的连接 */
ngx_flag_t multi_accept;
/* 标志位,为 1 时表示启用负载均衡锁 */
ngx_flag_t accept_mutex; /* 负载均衡锁会使有些 worker 进程在拿不到锁时延迟建立新连接,accept_mutex_delay
* 就是这段延迟时间的长度 */
ngx_msec_t accept_mutex_delay; /* 所选用事件模块的名字,它与 use 成员是匹配的 */
u_char *name; #if (NGX_DEBUG)
/* 在 --with-debug 编译模式下,可以仅针对某些客户端建立的连接输出调试级别的日志,
* 而 debug-connection 数组用于保存这些客户端的地址信息 */
ngx_array_t debug_connection;
#endif
} ngx_event_conf_t;

3. 事件模块公共接口:ngx_event_module_t

static ngx_event_module_t  ngx_event_core_module_ctx = {
&event_core_name,
ngx_event_core_create_conf, /* create configuration */
ngx_event_core_init_conf, /* init configuration */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
};

从这可以看出,ngx_event_core_module 模块仅实现了 create_conf 方法和 init_conf 方法,这是因为它并不真正负责

TCP 网络事件的驱动,所以不会实现 ngx_event_actions_t 中的方法。

ngx_event_core_create_conf 方法仅是为 ngx_event_conf_t 结构体分配内存空间,并初始化其成员值。

ngx_event_core_init_conf 方法则会根据系统平台选择一个合适的事件处理模块,此外,还对一些重要的但是没有在

nginx.conf 中配置的值 进行初始化。

static char *ngx_event_core_init_conf(ngx_cycle_t *cycle, void *conf)
{
TRACE_DEBUG(NGX_LOG_INFO, "ngx_event_core_module: init conf"); ngx_event_conf_t *ecf = conf; #if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)
int fd;
#endif
ngx_int_t i;
ngx_module_t *module;
ngx_event_module_t *event_module; module = NULL; /* Nginx根据当前系统平台选择一个合适的事件处理模块 */ #if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL) fd = epoll_create(100); if (fd != -1)
{
(void)close(fd);
module = &ngx_epoll_module;
}
else if (ngx_errno != NGX_ENOSYS)
{
module = &ngx_epoll_module;
} #endif #if (NGX_HAVE_DEVPOLL) && !(NGX_TEST_BUILD_DEVPOLL) module = &ngx_devpoll_module; #endif #if (NGX_HAVE_KQUEUE) module = &ngx_kqueue_module; #endif #if (NGX_HAVE_SELECT) if (module == NULL)
{
module = &ngx_select_module;
} #endif if (module == NULL)
{
for (i = 0; cycle->modules[i]; i++)
{
if (cycle->modules[i]->type != NGX_EVENT_MODULE)
{
continue;
} event_module = cycle->modules[i]->ctx; if (ngx_strcmp(event_module->name->data, event_core_name.data) == 0)
{
continue;
} module = cycle->modules[i];
break;
}
} if (module == NULL)
{
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "no events module found");
return NGX_CONF_ERROR;
} ngx_conf_init_uint_value(ecf->connections, DEFAULT_CONNEXTIONS);
cycle->connection_n = ecf->connections; /* 把该事件处理模块序号记录在配置变量ecf->use中 */
ngx_conf_init_uint_value(ecf->use, module->ctx_index); event_module = module->ctx;
ngx_conf_init_ptr_value(ecf->name, event_module->name->data); ngx_conf_init_value(ecf->multi_accept, 0);
ngx_conf_init_value(ecf->accept_mutex, 0);
ngx_conf_init_msec_value(ecf->accept_mutex_delay, 500); return NGX_CONF_OK;
}

4. ngx_event_core_module 模块的定义

/* 该模块会决定使用哪种事件驱动机制,以及如何管理事件 */
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 */
/* 没有 fork 出 worker 子进程时,会调用该函数 */
ngx_event_module_init, /* init module */
/* fork出子进程后,每一个worker进程会在用 ngx_event_core_module 模块的
* ngx_event_process_init 方法后才会进入正式的工作循环 */
ngx_event_process_init, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};

4.1 ngx_event_module_init()

static ngx_int_t ngx_event_module_init(ngx_cycle_t *cycle)
{
void ***cf;
u_char *shared;
size_t size, cl;
ngx_shm_t shm;
ngx_time_t *tp;
ngx_core_conf_t *ccf;
ngx_event_conf_t *ecf; /* 获取 ngx_events_module 模块持有的关于事件模块的总配置项结构体指针数组 */
cf = ngx_get_conf(cycle->conf_ctx, ngx_events_module);
/* 在总配置项结构体指针数组中获取 ngx_event_core_module 模块的配置项结构体 */
ecf = (*cf)[ngx_event_core_module.ctx_index]; if (!ngx_test_config && ngx_process <= NGX_PROCESS_MASTER) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
"using the \"%s\" event method", ecf->name);
} ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); ngx_timer_resolution = ccf->timer_resolution; #if !(NGX_WIN32)
{
ngx_int_t limit;
struct rlimit rlmt; if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"getrlimit(RLIMIT_NOFILE) failed, ignored"); } else {
if (ecf->connections > (ngx_uint_t) rlmt.rlim_cur
&& (ccf->rlimit_nofile == NGX_CONF_UNSET
|| ecf->connections > (ngx_uint_t) ccf->rlimit_nofile))
{
limit = (ccf->rlimit_nofile == NGX_CONF_UNSET) ?
(ngx_int_t) rlmt.rlim_cur : ccf->rlimit_nofile; ngx_log_error(NGX_LOG_WARN, cycle->log, 0,
"%ui worker_connections exceed "
"open file resource limit: %i",
ecf->connections, limit);
}
}
}
#endif /* !(NGX_WIN32) */ if (ccf->master == 0) {
return NGX_OK;
} if (ngx_accept_mutex_ptr) {
return NGX_OK;
} /* cl should be equal to or greater than cache line size */ cl = 128; size = cl /* ngx_accept_mutex */
+ cl /* ngx_connection_counter */
+ cl; /* ngx_temp_number */ #if (NGX_STAT_STUB) size += cl /* ngx_stat_accepted */
+ cl /* ngx_stat_handled */
+ cl /* ngx_stat_requests */
+ cl /* ngx_stat_active */
+ cl /* ngx_stat_reading */
+ cl /* ngx_stat_writing */
+ cl; /* ngx_stat_waiting */ #endif shm.size = size;
ngx_str_set(&shm.name, "nginx_shared_zone");
shm.log = cycle->log; if (ngx_shm_alloc(&shm) != NGX_OK) {
return NGX_ERROR;
} shared = shm.addr; ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;
ngx_accept_mutex.spin = (ngx_uint_t) -1; if (ngx_shmtx_create(&ngx_accept_mutex, (ngx_shmtx_sh_t *) shared,
cycle->lock_file.data)
!= NGX_OK)
{
return NGX_ERROR;
} ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl); (void) ngx_atomic_cmp_set(ngx_connection_counter, 0, 1); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"counter: %p, %uA",
ngx_connection_counter, *ngx_connection_counter); ngx_temp_number = (ngx_atomic_t *) (shared + 2 * cl); tp = ngx_timeofday(); ngx_random_number = (tp->msec << 16) + ngx_pid; #if (NGX_STAT_STUB) ngx_stat_accepted = (ngx_atomic_t *) (shared + 3 * cl);
ngx_stat_handled = (ngx_atomic_t *) (shared + 4 * cl);
ngx_stat_requests = (ngx_atomic_t *) (shared + 5 * cl);
ngx_stat_active = (ngx_atomic_t *) (shared + 6 * cl);
ngx_stat_reading = (ngx_atomic_t *) (shared + 7 * cl);
ngx_stat_writing = (ngx_atomic_t *) (shared + 8 * cl);
ngx_stat_waiting = (ngx_atomic_t *) (shared + 9 * cl); #endif return NGX_OK;
}

该方法主要初始化了一些变量,尤其是 ngx_http_stub_status_module 统计模块使用的一些原子性的统计变量。

4.2 ngx_event_process_init()

static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)
{
ngx_uint_t m, i;
ngx_event_t *rev, *wev;
ngx_listening_t *ls;
ngx_connection_t *c, *next, *old;
ngx_core_conf_t *ccf;
ngx_event_conf_t *ecf;
ngx_event_module_t *module; ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module); /* 当打开 accept_mutex 负载均衡锁,同时使用了 master 模式且 worker 进行数量大于 1 时,
* 才正式确定了进程将使用 accept_mutex 负载均衡锁。因此,即使我们在配置文件中指定打开
* accept_mutex 锁,如果没有使用 master 模式或者 worker 进程数量等于 1,进程在运行时
* 还是不会使用负载均衡锁(既然不存在多个进程去抢一个监听端口上的连接的情况,自然就不
* 需要均衡多个 worker 进程的负载)*/
if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
/* 这里才置位了才明确表示使用负载均衡锁 */
ngx_use_accept_mutex = 1;
ngx_accept_mutex_held = 0;
ngx_accept_mutex_delay = ecf->accept_mutex_delay; } else {
/* 关闭负载均衡锁 */
ngx_use_accept_mutex = 0;
} #if (NGX_WIN32) /*
* disable accept mutex on win32 as it may cause deadlock if
* grabbed by a process which can't accept connections
*/ ngx_use_accept_mutex = 0; #endif /* 初始化红黑树实现的定时器 */
ngx_queue_init(&ngx_posted_accept_events);
ngx_queue_init(&ngx_posted_events); if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
return NGX_ERROR;
} /* 在调用 use 配置项指定的事件模块中,在 ngx_event_module_t 接口下,ngx_event_actions_t
* 中的 init 方法进行这个事件模块的初始化工作 */
for (m = 0; cycle->modules[m]; m++) {
if (cycle->modules[m]->type != NGX_EVENT_MODULE) {
continue;
} if (cycle->modules[m]->ctx_index != ecf->use) {
continue;
} module = cycle->modules[m]->ctx; if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
/* fatal */
exit(2);
} break;
} #if !(NGX_WIN32) /* 如果 nginx.conf 配置文件中设置了 timer_resolution 配置项,即表明需要控制时间
* 精度,这时会调用 setitimer 方法,设置时间间隔为 timer_resolution 毫秒来回调
* ngx_timer_signal_handler 方法 */
if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
struct sigaction sa;
struct itimerval itv; ngx_memzero(&sa, sizeof(struct sigaction));
/* 在 ngx_timer_signal_handler 方法中仅是对全局变量 ngx_event_timer_alarm
* 置 1,表示需要更新时间,在 ngx_event_actions_t 的 process_events 方法中,
* 每一个事件驱动模块都需要在 ngx_event_timer_alarm 为 1 时调用
* ngx_time_update 方法更新系统时间,在更新系统时间结束后需要将
* ngx_event_timer_alarm 置为 0 */
sa.sa_handler = ngx_timer_signal_handler;
sigemptyset(&sa.sa_mask); if (sigaction(SIGALRM, &sa, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"sigaction(SIGALRM) failed");
return NGX_ERROR;
} itv.it_interval.tv_sec = ngx_timer_resolution / 1000;
itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000;
itv.it_value.tv_sec = ngx_timer_resolution / 1000;
itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000; if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setitimer() failed");
}
} if (ngx_event_flags & NGX_USE_FD_EVENT) {
struct rlimit rlmt; if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"getrlimit(RLIMIT_NOFILE) failed");
return NGX_ERROR;
} cycle->files_n = (ngx_uint_t) rlmt.rlim_cur; cycle->files = ngx_calloc(sizeof(ngx_connection_t *) * cycle->files_n,
cycle->log);
if (cycle->files == NULL) {
return NGX_ERROR;
}
} #else if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
ngx_log_error(NGX_LOG_WARN, cycle->log, 0,
"the \"timer_resolution\" directive is not supported "
"with the configured event method, ignored");
ngx_timer_resolution = 0;
} #endif /* 预分配 ngx_connection_t 数组作为连接池,同时将 ngx_cycle_t 结构体中的
* connections 成员指向该数组。数组的个数为 nginx.conf 配置文件中
* worker_connections 中配置的连接数 */
cycle->connections =
ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
if (cycle->connections == NULL) {
return NGX_ERROR;
} c = cycle->connections; /* 预分配 ngx_event_t 事件数组作为读事件池,同时将 ngx_cycle_t 结构体中的
* read_events 成员指向该数组。数组的个数为 nginx.conf 配置文件中
* worker_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;
} /* 预分配 ngx_event_t 事件数组作为写事件池,同时将 ngx_cycle_t 结构体中的
* write_events 成员指向该数组。数组的个数为 nginx.conf 配置文件中
* worker_connections 里配置的连接数 */
cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
cycle->log);
if (cycle->write_events == NULL) {
return NGX_ERROR;
} wev = cycle->write_events;
/* 将所有写事件池的写事件置为关闭状态,即未使用 */
for (i = 0; i < cycle->connection_n; i++) {
wev[i].closed = 1;
} i = cycle->connection_n;
next = NULL; /* 按照序号,将上述 3 个数组相应的读/写事件设置到每一个 ngx_connection_t 连接
* 对象中,同时把这些连接以 ngx_connection_t 中的 data 成员作为 next 指针串联
* 成链表,为下一步设置空闲连接链表做好准备 */
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); /* 将 ngx_cycle_t 结构体中的空闲连接链表 free_connections 指向 connections 数组
* 的第 1 个元素,也就是上一步所有 ngx_connection_t 连接通过 data 成员组成的
* 单链表的首部 */
cycle->free_connections = next;
cycle->free_connection_n = cycle->connection_n; /* for each listening socket */ /* 在刚刚建立好的连接池中,为所有 ngx_listening_t 监听对象中 connections 成员
* 分配连接,同时对监听端口的读事件设置处理方法为 ngx_event_accept,也就是说,
* 有新连接事件时将调用 ngx_event_accept 方法建立新连接 */
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) { #if (NGX_HAVE_REUSEPORT)
if (ls[i].reuseport && ls[i].worker != ngx_worker) {
continue;
}
#endif c = ngx_get_connection(ls[i].fd, cycle->log); if (c == NULL) {
return NGX_ERROR;
} c->type = ls[i].type;
c->log = &ls[i].log; c->listening = &ls[i];
ls[i].connection = c; rev = c->read; rev->log = c->log;
rev->accept = 1; #if (NGX_HAVE_DEFERRED_ACCEPT)
rev->deferred_accept = ls[i].deferred_accept;
#endif if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
if (ls[i].previous) { /*
* delete the old accept events that were bound to
* the old cycle read events array
*/ old = ls[i].previous->connection; if (ngx_del_event(old->read, NGX_READ_EVENT, NGX_CLOSE_EVENT)
== NGX_ERROR)
{
return NGX_ERROR;
} old->fd = (ngx_socket_t) -1;
}
} #if (NGX_WIN32) if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
ngx_iocp_conf_t *iocpcf; rev->handler = ngx_event_acceptex; if (ngx_use_accept_mutex) {
continue;
} if (ngx_add_event(rev, 0, NGX_IOCP_ACCEPT) == NGX_ERROR) {
return NGX_ERROR;
} ls[i].log.handler = ngx_acceptex_log_error; iocpcf = ngx_event_get_conf(cycle->conf_ctx, ngx_iocp_module);
if (ngx_event_post_acceptex(&ls[i], iocpcf->post_acceptex)
== NGX_ERROR)
{
return NGX_ERROR;
} } else {
rev->handler = ngx_event_accept; if (ngx_use_accept_mutex) {
continue;
} if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return NGX_ERROR;
}
} #else
/* 设置读事件的回调方法 */
rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
: ngx_event_recvmsg; #if (NGX_HAVE_REUSEPORT) if (ls[i].reuseport) {
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return NGX_ERROR;
} continue;
} #endif if (ngx_use_accept_mutex) {
continue;
} #if (NGX_HAVE_EPOLLEXCLUSIVE) if ((ngx_event_flags & NGX_USE_EPOLL_EVENT)
&& ccf->worker_processes > 1)
{
if (ngx_add_event(rev, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT)
== NGX_ERROR)
{
return NGX_ERROR;
} continue;
} #endif /* 将监听对象连接的读事件添加到事件驱动模块中,这样,epoll 等事件模块
* 就开始检测监听服务,并开始向用户提供服务了 */
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return NGX_ERROR;
} #endif } return NGX_OK;
}

Nginx事件管理之ngx_event_core_module模块的更多相关文章

  1. Nginx事件管理之核心模块ngx_events_module

    1. ngx_events_module核心模块的功能介绍 ngx_events_module 模式是一个核心模块,它的功能如下: 定义新的事件类型 定义每个事件模块都需要实现的ngx_event_m ...

  2. Nginx事件管理之epoll模块

    1. epoll 原理 假设有 100 万用户同时与一个进程保持着 TCP 连接,而每一时刻只有几十个或几百个 TCP 连接时活跃的(接收到 TCP 包),也就是说,在每一时刻,进程只需要处理这 10 ...

  3. Nginx事件管理之概念描述

    1. Nginx事件管理概述 首先,Nginx定义了一个核心模块ngx_events_module,这样在Nginx启动时会调用ngx_init_cycle方法解析配置项,一旦在 nginx.conf ...

  4. Nginx事件管理之定时器事件

    1. 缓存时间 1.1 管理 Nginx 中的每个进程都会单独地管理当前时间.ngx_time_t 结构体是缓存时间变量的类型: typedef struct { /* 格林威治时间1970年1月1日 ...

  5. Backbone事件管理——Backbone.Events模块API结构

    模块Backbone.Events的事件管理是通过Backbone提供的Events API来实现的,该API在1.0版本之前仅仅提供了几个基本的方法,如on.off.trigger.once分别执行 ...

  6. Nginx事件管理之事件处理流程

    1. 概述 事件处理要解决的两个问题: "惊群" 问题,即多个 worker 子进程监听相同端口时,在 accept 建立新连接时会有争抢,引发不必要的上下文切换, 增加系统开销. ...

  7. Nginx事件管理机制-epoll

    epoll的最大好处在于他不会随着被监控描述符的数目的增长而导致效率极致下降. select是遍历扫描来判断每个描述符是否有事件发生,当监控的描述付越多时,时间消耗就越多,并且由于系统的限制selec ...

  8. 【Nginx】ngx_event_core_module模块

    ngx_event_core_module模块属于事件模块,它是其他事件类模块的基础.它主要完毕下面任务: 创建连接池 决定使用哪些事件驱动机制 初始化将要使用的事件模块 以下分析该模块的代码. ng ...

  9. 【Nginx】Nginx事件模块

    一.事件处理框架概述 事件处理框架所要解决的问题是如何收集.管理.分发事件.事件以网络事件和定时器事件为主,而网络事件中以TCP网络事件为主.事件处理框架需要在不同的操作系统内核中选择一种事件驱动机制 ...

随机推荐

  1. 转:git常用操作

    转自:https://www.cnblogs.com/donghaojun/p/10906981.html 开发常用git指令:git init  # 初始化本地git环境 git clone xxx ...

  2. 帝国cms 修改分页样式

    帝国cms 修改分页样式(路径) /e/class/t_functions.php

  3. javascript修改html <b>标签里面的内容

    简单实现仅供参考: javascript修改html <b>标签里面的化妆步骤内容<体><b style=“width:100px:height:100px:border ...

  4. springBoot2.x 支持跨域请求配置

    提供三种配置方式: 1.配置过滤器,实现 WebMvcConfigurer接口(springboot2.x的方式) @Configuration public class GlobalCorsConf ...

  5. Java web 项目 web.xml 配置文件加载过程

    转载自:http://blog.csdn.net/luoliehe/article/details/46884757#comments WEB加载web.xml初始化过程: 在启动Web项目时,容器( ...

  6. CAN总线简介:如何以编程方式控制汽车

    最近,我正与Voyage公司的朋友合作研究,以实现福特Fusion空调系统(A/C)的编程控制.目前,Voyage公司正努力打造自动驾驶的终极目标:能够以低廉的价格成本和广泛的投放范围,把世界任何地方 ...

  7. 《python解释器源码剖析》第6章--python中的dict对象

    6.0 序 元素和元素之间可能存在着某种关系,比如学生姓名和成绩.我希望能够通过学生的姓名找到这个学生的成绩,那么只需要将两者关联起来即可.字典正是这么做的,字典中的每个元素就是一个key:value ...

  8. QQ空间添加背景音乐

    QQ空间背景音乐方式 1.QQ音乐添加背景音乐一种是开通绿砖[有矿的忽略此条]2.QQ空间添加网络音乐的方法步骤:1.首先,需要先下载好想要音乐作为QQ空间背景音乐的歌曲文件(建议为MP3格式)[我这 ...

  9. python常用模块:pickle、shelve、json、xml、configparser

    今日内容主要有: 一.pickle模块二.shelve模块三.json模块四.json练习五.xml模块 六.xml练习七.configparser模块 一.pickle模块 #pickle是一个用来 ...

  10. Activity的跳转及返回值,activity的生命周期

    Activity生命周期 从创建到销毁的生命周期: onCreate()→onStart()→onResume()→onPouse()→onStop()→onDestroy() 从起动到后台再到前台: ...