mini_ngx_实现二-core模块

Catalogue
  1. 1. main启动函数
  2. 2. cycle 相关函数
    1. 2.1. @init_cycle 主循环函数
    2. 2.2. 全局变量
    3. 2.3. @init 初始化cycle
    4. 2.4. @init_module 初始化模块
    5. 2.5. @start_module 启动模块
  3. 3. connection 相关
  4. 4. mem_pool 相关
  5. 5. log 日志记录相关
    1. 5.1. @log_open 日志打开
    2. 5.2. @log_request 记录日志请求
    3. 5.3. @log_error 错误日志打印
    4. 5.4. @log_info 普通日志记录信息
  6. 6. cmake 编译配置相关

main启动函数

主要调用init_cycle() 进入框架事件循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main()
{
cycle_t * cycle = init_cycle();
//程序结束 回收内存池内存
if(cycle->pool){
destroy_pool(cycle->pool);
}
if(cycle->connections){
free(cycle->connections);
}
if(cycle->read_events){
free(cycle->read_events);
}
if(cycle->write_events){
free(cycle->write_events);
}
free(cycle);
}

当返回时,说明程序已经结束,剩下的只需要销毁内存池,连接池,读写事件内存即可

cycle 相关函数

@init_cycle 主循环函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cycle_t* init_cycle(){
//初始化全局结构体
cycle_t* cycle = init();

//初始化模块
init_module(cycle);

//启动模块
if(start_module(cycle) != OK){
return cycle;
}

//epoll wait 分发事件
while(1){
if(process_events(cycle,event_flags) == ERROR){
goto end;
}
}
end:
close_listening_sockets(cycle);

return cycle;
}
  1. 初始化cycle结构体: 如nginx那样做一些初始工作,分配内存池,初始化监听socket列表、打开日志文件,判断是否需要守护进程,注意:nginx都是基于配置来做的,但是我们省去了配置相关流程,直接手动在代码里面配置
  2. 初始化模块: 如nginx一样,变量全局模块数组,执行所有的模块初始化工作
  3. 启动模块: 调用每个模块init_process方法,例如http则启动监听socket初始化,event则初始化连接池,创建epoll等等
  4. 事件循环:接下来则是事件循环,调用event等待事件就绪,注意nginx的实现是有定时器的,我们这里为了跑通主流程,将定时器省去

全局变量

1
2
3
4
5
cycle_t *GCYCLE;
module_t* modules[] = {
&http_core_module,
&event_core_module,
};
  1. 如nginx一样 有一个全局cycle
  2. modules 保存了所有模块的地址,也就掌握了程序的核心启动入口,nginx所有的功能都是以单独的模块module_t来进行扩充的,而且模块之前还有启动顺序

@init 初始化cycle

  1. 申请内存池,默认1024字节,不够会单独扩充,注意需要先调用getpagesize()获取内核内存分页大小初始化全局pagesize变量
  2. 有了内存池后,其他所有内存申请都走内存池管理
  3. 打开日志文件
  4. 判断是否需要守护进程,这里在nginx中依然是通过解析配置来对应设置相关参数的,我们这里直接设置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    //创建初始化cycle 全局结构体
    static cycle_t * init()
    {
    pagesize = getpagesize();
    cycle_t * cycle = (cycle_t*) malloc(sizeof(cycle_t));
    memzero(cycle,sizeof(cycle_t));
    cycle->pool = create_pool(1024);
    cycle->pool->max = MAX_ALLOC_FROM_POOL;
    GCYCLE = cycle;//global cycle

    //初始化监听端口链表
    if (array_init(&cycle->listening, cycle->pool, 10,sizeof(listening_t)) != OK){
    return NULL;
    }

    // 打开日志文件
    cycle->log = palloc(cycle->pool,sizeof(log_t));
    cycle->log->use_logfile = 1;
    log_open(cycle->log, "./run.log");

    //开启守护进程
    cycle->is_daemon = 0;
    daemonize(cycle);

    return cycle;

    }

@init_module 初始化模块

这里在nginx中其实是非常复杂的,因为在编译之前环境检查的时候就已经构造好了模块数组,所以做了大量的工作,但是本质其实就是将所有模块都加入到全局模块数组中,在后面cycle->init()的时候统一初始化

我们这里省去了前面初始化工作,直接加入我们仅有的两个模块http,event

1
2
3
4
5
6
7
//模拟nginx模块注册流程
static void init_module(cycle_t *cycle)
{
log_info(cycle->log,"cycle: init module");
cycle->modules_n = 2;
cycle->modules = modules;
}

@start_module 启动模块

如nginx一样,在cycle_init中初始化所有核心模块,非核心模块应该在核心模块启动后自己管理的,我们只有两个核心模块,像nginx那样变量数组,直接调用init_process即可

我们这里也是由顺序的,必须先启动http模块将所有socket资源先初始化,然后在启动event模块,因为在event模块中需要收集相关网络事件,如监听的端口等

1
2
3
4
5
6
7
8
9
10
11
12
13
//启动模块
int_t start_module(cycle_t *cycle){
log_info(cycle->log,"cycle: start module");
for(int i = 0;i < cycle->modules_n ; i++){
//http 注册tcp监听端口
//event 模块创建epoll epoll_events
if(cycle->modules[i]->init_process(cycle) != OK){
log_error(cycle->log,"cycle: start module init process error");
return ERROR;
}
}
return OK;
}

connection 相关

封装了连接池相关代码,可以看 http://wiki.brewlin.com/wiki/blog/nginx/1.ngx_%E8%BF%9E%E6%8E%A5%E6%B1%A0%E4%B8%8E%E4%BA%8B%E4%BB%B6%E5%B0%81%E8%A3%85(%E4%B8%80)/

http://wiki.brewlin.com/wiki/blog/nginx/2.ngx_%E8%BF%9E%E6%8E%A5%E6%B1%A0%E4%B8%8E%E4%BA%8B%E4%BB%B6%E5%B0%81%E8%A3%85(%E4%BA%8C)/

mem_pool 相关

封装了内存池相关代码,可以看 http://wiki.brewlin.com/wiki/blog/nginx/nginx_%E5%86%85%E5%AD%98%E6%B1%A0%E5%B0%81%E8%A3%85/

log 日志记录相关

@log_open 日志打开

这里可以看到,日志文件资源充分的利用了内存池机制,将资源类型自定义handler扔到内存池中,无需关心释放问题,在程序结束后销毁内存池时自动释放该文件资源

相关特性请看文章: http://wiki.brewlin.com/wiki/blog/nginx/nginx_%E5%86%85%E5%AD%98%E6%B1%A0%E5%B0%81%E8%A3%85/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 以append模式打开日志文件
void log_open(log_t *log, const char *logfile) {
if (log->use_logfile) {
log->logfp = fopen(logfile, "a");

if (!log->logfp) {
perror(logfile);
exit(1);
}
//auto clean the file
pool_cleanup_t *cl = pool_cleanup_add(GCYCLE->pool,sizeof(log_t));
cl->handler = clean;
cl->data = log;

return;
}

openlog("weblog_t", LOG_NDELAY | LOG_PID, LOG_DAEMON);
}

@log_request 记录日志请求

1
2
// 记录HTTP请求
void log_request(http_connection *con) {}

@log_error 错误日志打印

1
2
3
4
5
6
7
8
// 记录出错信息
void log_error(log_t *log, const char *format, ...) {
va_list ap;

va_start(ap, format);
log_write(log, "error", format, ap);
va_end(ap);
}

@log_info 普通日志记录信息

1
2
3
4
5
6
7
8
9

// 记录日志信息
void log_info(log_t *log, const char *format, ...) {
va_list ap;

va_start(ap, format);
log_write(log, "info", format, ap);
va_end(ap);
}

cmake 编译配置相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
project(demo)

add_definitions("-Wall -g")

include_directories(./include)

add_subdirectory(./core)
add_subdirectory(./event)
add_subdirectory(./http)

add_executable(demo main.c)

target_link_libraries(demo
core http
event core
http)

在最后链接的时候,会发现有相互依赖的问题,原因是我们的程序相关隔离性还是没有划分的太好

比如在demo - core 中demo链接core中的函数,但是core中的函数又依赖http,所以只有在后面再多加一个连接即可