对于c语言来说,如果需要支持多个操作系统,就需要封装一下文件的读写。封装文件的读写还有一个益处就是能够把读写异常,读写的内存控制,日志的记录封装起来,以便于其他的模块更好的应用。文件的读写一般会封装成打开文件,关闭打开的文件,读写文件。
在nginx的源码中,文件读写主要放在core/ngx_file.c,core/ngx_file.h,src/os/unix/ngx_files.h和src/os/unix/ngx_files.c中。由于nginx的文件读写函数较多,我们只是详细介绍比较重要,经常使用,实现很有技巧的函数。
typedef struct {
ngx_str_t name; //路径数据字符串
size_t len; //目录长度
size_t level[3]; //临时目录的三级目录大小
ngx_path_manager_pt manager;
ngx_path_loader_pt loader;
void *data;
u_char *conf_file; //该路径的来源的配置文件
ngx_uint_t line; //该路径在来源的配置文件中的行数,主要用于记录日志,排查错误
} ngx_path_t;
我们首先介绍简单封装的函数,再介绍复杂封装的函数,简单封装的函数有如下函数:
ngx_open_file/ngx_close_file
src/os/unix/ngx_files.h
#define ngx_open_file(name, mode, create, access) \
open((const char *) name, mode|create|O_BINARY, access)
#else
#define ngx_open_file(name, mode, create, access) \
open((const char *) name, mode|create, access)
#endif
#define ngx_close_file close
这里可以看到单纯的对c语言的open和close进行了封装,是不是非常简单呢?
src/os/unix/ngx_files.h
#define ngx_delete_file(name) unlink((const char *) name)
ngx_delete_file删除文件函数,调用系统的unlink函数,unlink()会删除pathname指定的文件。如果该文件名最后连接点,但有其他进程打开了此文件,则在所有关于此文件的文件描述词皆关闭后才会删除。如果参数pathname为一符号连接(symboliclink),则此连接会被删除。
在ngx_files.c文件中存在大量的文件读写操作,这里就不一一讲解;
配置文件一般有三个作用:
根据配置文件启用某段程序,
用配置文件传入参数,
设定启动程序程序之间的相互关系。
Nginx的配置文件也实现了这三部分的功能,nginx的配置文件主要有以下几个主要的结构体和函数配置文件的结构体有:
结构体说明
ngx_command_s 定义了配置文件中的 tag的配置,以及遇到该tag该怎么处理的函数
ngx_open_file_s 定义了打开文件的参数的结构体
ngx_conf_file_t 定义了缓存配置文件的数据的结构体
ngx_conf_s 定义了配置文件解析过程中需要缓存的数据
ngx_command_s这个结构体定义了配置文件中的tag,以及遇到该tag,该怎么处理,其结构如下表:
结构体说明
name 配置文件中的tag
Type 配置类型,这个参数中会定义这个配置是什么范围内的配置(核心配置或是普通配置),以及有多少参数, 是块配置,还是行配置
Set 解析该配置的函数
ConfOffset 指定配置存储的位置
Post 指向模块在读配置的时候需要的一些零碎变量。一般它是NULL
在每一个ngx_command_s数组的结尾必须有一个ngx_null_command,以用于判断是否结束。
配置文件解析的入口为ngx_conf_parse,如果了解了ngx_conf_parse,对nginx的配置文件的原理就有了基本的了解,下面我们详细的解释一下
src/ngx_conf_file.h
char *
ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
//cf是输入参数也是输出参数,在文件名为空的情况下,第二个
//参数是文件名,可能为空,一般在解析配置块或是一行的时候。
{
char *rv;
ngx_fd_t fd;
ngx_int_t rc;
ngx_buf_t buf;
ngx_conf_file_t *prev, conf_file;
enum {
parse_file = 0,
parse_block,
parse_param
} type;
#if (NGX_SUPPRESS_WARN)
fd = NGX_INVALID_FILE;
prev = NULL;
#endif
if (filename) { //如果文件名不为空 则打开配置文件
/* open configuration file */
fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);//打开配置文件
if (fd == NGX_INVALID_FILE) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
ngx_open_file_n " \"%s\" failed",
filename->data);
return NGX_CONF_ERROR;
}
prev = cf->conf_file;
cf->conf_file = &conf_file;
if (ngx_fd_info(fd, &cf->conf_file->file.info) == -1) {
ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
ngx_fd_info_n " \"%s\" failed", filename->data);
}
cf->conf_file->buffer = &buf;
buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log);
if (buf.start == NULL) {
goto failed;
}
buf.pos = buf.start;
buf.last = buf.start;
buf.end = buf.last + NGX_CONF_BUFFER;
buf.temporary = 1;
cf->conf_file->file.fd = fd;
cf->conf_file->file.name.len = filename->len;
cf->conf_file->file.name.data = filename->data;
cf->conf_file->file.offset = 0;
cf->conf_file->file.log = cf->log;
cf->conf_file->line = 1;
type = parse_file;
} else if (cf->conf_file->file.fd != NGX_INVALID_FILE) {
type = parse_block;
} else {
type = parse_param;
}
for ( ;; ) {
rc = ngx_conf_read_token(cf);
//ngx_conf_read_token函数的主要功能是读取并解析配置文件,
//解析的参数存到cf->args中,读取到";"标点则返回
//读取到"{",则接着读取,读取到"}",这直接完成。
/*
* ngx_conf_read_token() may return
*
* NGX_ERROR there is error
* NGX_OK the token terminated by ";" was found
* NGX_CONF_BLOCK_START the token terminated by "{" was found
* NGX_CONF_BLOCK_DONE the "}" was found
* NGX_CONF_FILE_DONE the configuration file is done
*/
if (rc == NGX_ERROR) {
goto done; //如果错误 结束
}
if (rc == NGX_CONF_BLOCK_DONE) { //如果发现“}”
if (type != parse_block) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"}\"");
goto failed;
}
goto done;
}
if (rc == NGX_CONF_FILE_DONE) { //如果发现文件结束
if (type == parse_block) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"unexpected end of file, expecting \"}\"");
goto failed;
}
goto done;
}
if (rc == NGX_CONF_BLOCK_START) { //如果发现“{”
if (type == parse_param) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"block directives are not supported "
"in -g option");
goto failed;
}
}
/* rc == NGX_OK || rc == NGX_CONF_BLOCK_START */
if (cf->handler) { //这个函数只在模块的时候使用type
/*
* the custom handler, i.e., that is used in the http's
* "types { ... }" directive
*/
rv = (*cf->handler)(cf, NULL, cf->handler_conf);
if (rv == NGX_CONF_OK) {
continue;
}
if (rv == NGX_CONF_ERROR) {
goto failed;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, rv);
goto failed;
}
rc = ngx_conf_handler(cf, rc);
//ngx_conf_handler函数主要功能是先验证配置有没有问题,然后调用cmd->set函数
if (rc == NGX_ERROR) {
goto failed;
}
}
failed:
rc = NGX_ERROR;
done:
if (filename) {
if (cf->conf_file->buffer->start) {
ngx_free(cf->conf_file->buffer->start);
}
if (ngx_close_file(fd) == NGX_FILE_ERROR) { //关闭文件
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
ngx_close_file_n " %s failed",
filename->data);
return NGX_CONF_ERROR;
}
cf->conf_file = prev;
}
if (rc == NGX_ERROR) {
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
ngx_conf_parse函数除了在ngx_init_cycle函数中调用外,在配置块的解析中,即cmd->set函数(例如解析http配置块的ngx_http_block函数)中也会调用该函数,从某种程度上理解该函数其实是一个递归函数,只不过中间调用了其他函数。在讲解http的配置文件那一章我们会根据http的配置例子详细的讲解。