mongoose执行文件的入口肯定在main.c中(如果将Mongoose嵌入到你的应用程序中,就由你来决定入口了!)。
在典型的main函数入口中,我们可以看到下面的流程:
上面即是main函数中的主流程。需要注意的是调用mg_start()之后返回一个mg_context结构体的实例,这个实例将会在整个连接请求中用到,而且如果你在启动mongoose中设置了参数选项,在下面的process_command_line_arguments()函数中还会对ctx进行修改。从这里我们也知道了,mongoose程序的核心入口时mg_start(),最后终结于mg_stop()。
init_server_name()函数:
具体实现:
snprintf(server_name, sizeof(server_name), "Mongoose web server v. %s",mg_version());
分析:
涉及函数和变量:
mg_version()
#define MONGOOSE_VERSION "3.6"
static char server_name[40]; // Set by init_server_name()
mg_version函数的具体实现如下:
const char *mg_version(void)
{
return MONGOOSE_VERSION;
}
start_mongoose()函数:
具体实现:
static void start_mongoose(int argc, char *argv[]) {
char *options[MAX_OPTIONS];
int i;
// Edit passwords file if -A option is specified
if (argc > 1 && !strcmp(argv[1], "-A")) { //指定-A选项 编辑密码文件
if (argc != 6) {
show_usage_and_exit();
}
exit(mg_modify_passwords_file(argv[2], argv[3], argv[4], argv[5]) ?
EXIT_SUCCESS : EXIT_FAILURE);
}
// Show usage if -h or --help options are specified
if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) { // -h 或者-help 指定用法
show_usage_and_exit();
}
/* Update config based on command line arguments */
process_command_line_arguments(argv, options); //更新配置文件 基于命令行
/*设置信号: Ctrl-C 退出*/
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
/* Start Mongoose */
ctx = mg_start(&mongoose_callback, NULL, (const char **) options);
for (i = 0; options[i] != NULL; i++) {
free(options[i]);
}
if (ctx == NULL) {
die("%s", "Failed to start Mongoose."); //打印错误字符串
}
}
分析:
其中 void show_usage_and_exit 函数:打印退出字符串
static void show_usage_and_exit(void) {
const char **names;
int i;
fprintf(stderr, "Mongoose version %s (c) Sergey Lyubka, built %s\n",
mg_version(), __DATE__);
fprintf(stderr, "Usage:\n");
fprintf(stderr, " mongoose -A <htpasswd_file> <realm> <user> <passwd>\n");
fprintf(stderr, " mongoose <config_file>\n");
fprintf(stderr, " mongoose [-option value ...]\n");
fprintf(stderr, "\nOPTIONS:\n");
names = mg_get_valid_option_names();
for (i = 0; names[i] != NULL; i += 3) {
fprintf(stderr, " -%s %s (default: \"%s\")\n",
names[i], names[i + 1], names[i + 2] == NULL ? "" : names[i + 2]);
}
fprintf(stderr, "\nSee http://code.google.com/p/mongoose/wiki/MongooseManual"
" for more details.\n");
fprintf(stderr, "Example:\n mongoose -s cert.pem -p 80,443s -d no\n");
exit(EXIT_FAILURE);
其中 process_command_line_arguments函数:对ctx进行修改
static void process_command_line_arguments(char *argv[], char **options) {
char line[MAX_CONF_FILE_LINE_SIZE], opt[sizeof(line)], val[sizeof(line)], *p;
FILE *fp = NULL;
size_t i, cmd_line_opts_start = 1, line_no = 0;
options[0] = NULL;
// Should we use a config file ?
if (argv[1] != NULL && argv[1][0] != '-') {
snprintf(config_file, sizeof(config_file), "%s", argv[1]);
cmd_line_opts_start = 2;
} else if ((p = strrchr(argv[0], DIRSEP)) == NULL) {
// No command line flags specified. Look where binary lives
snprintf(config_file, sizeof(config_file), "%s", CONFIG_FILE);
} else {
snprintf(config_file, sizeof(config_file), "%.*s%c%s",
(int) (p - argv[0]), argv[0], DIRSEP, CONFIG_FILE);
}
fp = fopen(config_file, "r");
// If config file was set in command line and open failed, die
if (cmd_line_opts_start == 2 && fp == NULL) {
die("Cannot open config file %s: %s", config_file, strerror(errno));
}
// Load config file settings first
if (fp != NULL) {
fprintf(stderr, "Loading config file %s\n", config_file);
// Loop over the lines in config file
while (fgets(line, sizeof(line), fp) != NULL) {
line_no++;
// Ignore empty lines and comments
for (i = 0; isspace(* (unsigned char *) &line[i]); ) i++;
if (line[i] == '#' || line[i] == '\0') {
continue;
}
if (sscanf(line, "%s %[^\r\n#]", opt, val) != 2) {
printf("%s: line %d is invalid, ignoring it:\n %s",
config_file, (int) line_no, line);
} else {
set_option(options, opt, val);
}
}
(void) fclose(fp);
}
// Handle command line flags. They override config file and default settings.
for (i = cmd_line_opts_start; argv[i] != NULL; i += 2) {
if (argv[i][0] != '-' || argv[i + 1] == NULL) {
show_usage_and_exit();
}
set_option(options, &argv[i][1], argv[i + 1]);
}
}
可以指定配置文件 ./mongoose test.txt 如果不指定 ,默认在bin下面寻找 mongoose.conf
可以进行如下配置:
error_log_file error.log
# cgi_extensions php
# cgi_environment <value>
# put_delete_passwords_file <value>
# cgi_interpreter c:\root\php-cgi.exe
# protect_uri <value>
# authentication_domain mydomain.com
# ssi_extensions .shtml,.shtm
# access_log_file <value>
# ssl_chain_file <value>
# enable_directory_listing yes
# global_passwords_file <value>
#index_files index.html,index.htm,index.cgi
# enable_keep_alive no
# access_control_list <value>
# max_request_size 16384
# extra_mime_types <value>
#端口
#listening_ports 8088
#指定网站根目录,指到本人的Apache服务器里(根据自己的需要配置)
# document_root D:\http
# ssl_certificate <value>
# num_threads 10
# run_as_user <value>
mg_start()函数:在一个单独的线程里开始Mongoose服务器实例. 返回实例句柄(Mongoose上下文). 这个上下文
在后面同服务器的交互中需要,比如设置选项,停止服务等等
在mg_start()主要是做一些初始化的工作,最后才会正式进入工作服务于client。这里的初始化工作就好比一个人的出生需要十月怀胎,为诞生积蓄能量,要从受精卵长成一个完整的人。在准备工作完成之后,mg_start()会启动一个主线程master_thread,它用于监听所有的client连接请求。
具体实现:
struct mg_context *mg_start(mg_callback_t user_callback, void *user_data,
const char **options) {
struct mg_context *ctx;
const char *name, *value, *default_value;
int i;
// Allocate context and initialize reasonable general case defaults.
// TODO(lsm): do proper error handling here.
if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) {
return NULL;
}
ctx->user_callback = user_callback;
ctx->user_data = user_data;
while (options && (name = *options++) != NULL) {
if ((i = get_option_index(name)) == -1) {
cry(fc(ctx), "Invalid option: %s", name);
free_context(ctx);
return NULL;
} else if ((value = *options++) == NULL) {
cry(fc(ctx), "%s: option value cannot be NULL", name);
free_context(ctx);
return NULL;
}
if (ctx->config[i] != NULL) {
cry(fc(ctx), "warning: %s: duplicate option", name);
free(ctx->config[i]);
}
ctx->config[i] = mg_strdup(value);
DEBUG_TRACE(("[%s] -> [%s]", name, value));
}
// Set default value if needed
for (i = 0; config_options[i * ENTRIES_PER_CONFIG_OPTION] != NULL; i++) {
default_value = config_options[i * ENTRIES_PER_CONFIG_OPTION + 2];
if (ctx->config[i] == NULL && default_value != NULL) {
ctx->config[i] = mg_strdup(default_value);
DEBUG_TRACE(("Setting default: [%s] -> [%s]",
config_options[i * ENTRIES_PER_CONFIG_OPTION + 1],
default_value));
}
}
// NOTE(lsm): order is important here. SSL certificates must
// be initialized before listening ports. UID must be set last.
if (!set_gpass_option(ctx) ||
#if !defined(NO_SSL)
!set_ssl_option(ctx) ||
#endif
!set_ports_option(ctx) ||
#if !defined(_WIN32)
!set_uid_option(ctx) ||
#endif
!set_acl_option(ctx)) {
free_context(ctx);
return NULL;
}
// Ignore SIGPIPE signal, so if browser cancels the request, it
// won't kill the whole process.
(void) signal(SIGPIPE, SIG_IGN);
// Also ignoring SIGCHLD to let the OS to reap zombies properly.
(void) signal(SIGCHLD, SIG_IGN);
(void) pthread_mutex_init(&ctx->mutex, NULL);
(void) pthread_cond_init(&ctx->cond, NULL);
(void) pthread_cond_init(&ctx->sq_empty, NULL);
(void) pthread_cond_init(&ctx->sq_full, NULL);
// Start master (listening) thread
mg_start_thread(master_thread, ctx);
// Start worker threads
for (i = 0; i < atoi(ctx->config[NUM_THREADS]); i++) {
if (mg_start_thread(worker_thread, ctx) != 0) {
cry(fc(ctx), "Cannot start worker thread: %d", ERRNO);
} else {
ctx->num_threads++;
}
}
return ctx;
mg_stop()函数:在关闭服务的最后 调用,释放所有相关资源. 这个将会阻塞,直到所有的Mongoose线程停止.
stop_flag表示是否应该停止的标记,它有三个可能的值0、1、2。 stop_flag=0表示 不应该停止,这是初始值;stop_flag=1表示停止,在mg_stop()函数中的一开始设置stop_flag=1,这会触发mg_fini(),且在mg_stop()中会一直等待mg_fini执行完成;stop_flag=2用于通知mg_stop(),mg_fini已经执行完成,stop_flag=2在mg_fini函数中的末尾设置。
具体实现:
void mg_stop(struct mg_context *ctx) {
ctx->stop_flag = 1;
// Wait until mg_fini() stops
while (ctx->stop_flag != 2) {
(void) mg_sleep(10);
}
free_context(ctx);
}
其他函数:
const char *mg_get_option(struct mg_context *context, const char *option_name)
返回特定选项的当前值
int mg_set_option(struct mg_context *context, const char *option_name, const char *option_value)
设置一个特定的选项. 选项值被复制. 选项的值 必须是一个有效的 以nul字节结束的ASCII或者UTF-8字符串. 可以在任意时候改变选项值. 如果选项是未知的,返回-1. 0表示设置失败哦, 1表示成功.
void mg_set_uri_callback(struct mg_context *context, const char *uri_regx, mg_callback_t func, void *user_data)
注册一个URI处理函数. 在uri_regx使用”*”(星号,匹配0到多个字符), 可以处理多个URI
user_data 指针 被作为第 3个参数 传递给 处理函数 .如果为NULL, 那么就删除这个handler
void mg_set_error_callback(struct mg_context *context, int error_code, mg_callback_t func, void *user_data)
注册一个错误处理函数. 比如 “404 Not Found”错误处理. 传递0作为 错误代码,将绑定到所有错误. 错误代码值 在 请求信息作为状态代码 被传递
void mg_set_log_callback(struct mg_context *context, mg_callback_t func)
指定一个函数来接收 所有的服务器的日志信息
void mg_set_ssl_password_callback(struct mg_context *ctx, mg_spcb_t func)
只在SSL许可请求密码时才需要.
void mg_modify_passwords_file(struct mg_context *ctx, const char *file_name, const char *user_name, const char *password)
添加,编辑或者删除 密码文件中的条目. 如果密码不为NULL, 条目将添加(已经存在就是修改)
如果password是NULL, 条目将删除. 返回1表示成功, 0表示错误
处理用户请求
int mg_write(struct mg_connection *, const void *buf, int len)
发送数据给客户. 返回值为 发送的字节个数. 如果发送的字节数< 请求的,或者为-1,表明发生了网络错误, 通常是 远程的客户端关闭了链接.
int mg_printf(struct mg_connection *, const char *fmt, …)
发送数据给客户端, 用printf()的semantics. 注意mg_printf 使用内部的缓冲区大小为
MAX_REQUEST_SIZE(16kb默认), 所以不要往里面打印大于 这个数目的数据. 否则,多出的数据会被截断. 函数的返回值是 发送的字节个数. Error sematics同mg_write()函数
const char *mg_get_header(const struct mg_connection *, const char *header_name)
给出特定的HTTP头部的值
char *mg_get_var(const struct mg_connection *, const char*var_name)
得到特定的表单变量的值. 注意: 这个函数(不像其他函数)会申请内存. 返回的值,必须自己主动用mg_free()释放. 如果这个变量既不再URI查询串,也不在POST数据中出现, 就返回NULL
char *mg_free(void *ptr)
释放 由 mg_get_var()申请的内存
void mg_authorize(struct mg_connection *)
设置authorize标志. 应该只在用mg_protect_uri()注册的回调函数中调用
通用辅助函数
const char *mg_version(void)
返回当前Mongoose version的版本
void mg_show_usage_string(FILE *)
显示 Mongoose 使用方法
命令行版本的使用
启动
mongoose option config_file
备注:参考吴秦的日志,感谢吴秦同学~~