注1:选择5.1版本进行分析是因为我参与的项目中使用的就是5.1版本,而且个人理解这一版本的代码结构比较有序,功能也不是太复杂。当然后续会继续分析5.2,5.3以及最新的代码。
注2:在分析的过程中,为了简化代码和逻辑,我打开了下述编译宏以关闭对应的功能:
[plain] view plain copy print ?
- NO_CGI,NO_WEBSOCKET,NO_DIRECTORY_LISTING,NO_DAV,NO_AUTH,NO_LOGGING
NO_CGI,NO_WEBSOCKET,NO_DIRECTORY_LISTING,NO_DAV,NO_AUTH,NO_LOGGING
注3:有一个gcc自带的工具cpp,非常便于对原始代码进行预处理。对于我这样不喜欢#if和#ifdef之类的编译宏,在阅读代码时有很大的帮助。我使用的cpp预处理命令如下:
[plain] view plain copy print ?
- cpp -DNO_CGI -DNO_WEBSOCKET -DNO_DIRECTORY_LISTING -DNO_DAV -DNO_AUTH -DNO_LOGGING -fdirectives-only -nostdinc -undef ../mongoose.c > ../mongoose.i
cpp -DNO_CGI -DNO_WEBSOCKET -DNO_DIRECTORY_LISTING -DNO_DAV -DNO_AUTH -DNO_LOGGING -fdirectives-only -nostdinc -undef ../mongoose.c > ../mongoose.i
一个简单的mg_server的使用过程如下
1. 创建一个mg_server,参数可以为空,也可以传递一个自定义的数据
server = mg_create_server(NULL);
2. 配置mg_server
mg_set_option(server, "listening_port", "8080");
mg_add_uri_handler(server, "/", index_html);
3. 进入for循环,轮询mg_sever,超时时间为1000ms,即1s
for (;;) {
mg_poll_server(server, 1000);
}
4. 等到Ctrl+C中断后,退出for循环,然后销毁mg_server
mg_destroy_server(&server);
mg_create_server函数实现
1. 注册信号处理函数,忽略SIGPIPE信号
signal(SIGPIPE, SIG_IGN);
原因分析:Socket连接建立,若某一端关闭连接,而另一端仍然向它写数据,第一次写数据后会收到RST响应,此后再写数据,内核将向进程发出SIGPIPE信号,通知进程此连接已经断开。而SIGPIPE信号的默认处理是终止程序,此处我们不需要终止程序,只需要等到socket存活时间超时后销毁socket即可。
2. 初始化uri_handlers和active_connections链表
#define LINKED_LIST_INIT(N) ((N)->next = (N)->prev = (N))
LINKED_LIST_INIT(&server->active_connections);
LINKED_LIST_INIT(&server->uri_handlers);
3. 创建control socket pair
do {
mg_socketpair(server->ctl);
} while (server->ctl[0] == INVALID_SOCKET);
4. 赋值自定义server_data
server->server_data = server_data;
这个参数的使用完全是由调用者决定的,比如multi_threaded.c例子中就是传递了一个"1"和"2"来表示创建的server标识。
5. 设置监听socket为非法值
server->listening_sock = INVALID_SOCKET;
具体的socket是在mg_set_option(server, "listening_port", "8080")时调用open_listening_socket(&server->lsa)创建的。
6. 设置默认参数
set_default_option_values(server->config_options);
默认参数值定义在static_config_options静态全局变量中。
mg_socketpair函数
1. 该函数的返回值完全没有使用到,对应的ret变量完全无用。
sock_t ret = -1;
2. sa变量
sa的赋值:
sa.sin_port = htons(0);
sa.sin_addr.s_addr = htonl(0x7f000001);
端口为0,即不指定发送端口;地址使用0x7f000001,表示Loop地址127.0.0.1,具体见<netinet/in.h>头文件:
# define INADDR_LOOPBACK ((in_addr_t) 0x7f000001) /* Inet 127.0.0.1. */
3. if部分
该函数主要是一个if else处理,然后关闭打开的sock套接字。关键部分就在if处理,else部分就是一个异常情况下的释放资源处理。
整个if判断可以分解为以下几个部分:
- sock = socket(AF_INET, SOCK_STREAM, 0) 创建一个Socket,地址是LoopBack
- bind(sock, (struct sockaddr *) &sa, len) 绑定到这个socket上
- listen(sock, 1) 监听这个socket
- getsockname(sock, (struct sockaddr *) &sa, &len) 获取该套接字的名字
- sp[0] = socket(AF_INET, SOCK_STREAM, 6) 创建socket sp[0] [<netinet/tcp.h>#define SOL_TCP 6]
- connect(sp[0], (struct sockaddr *) &sa, len) 使用sp[0] connect 刚才创建的LoopBack套接字
- sp[1] = accept(sock,(struct sockaddr *) &sa, &len) LoopBack套接字的accept函数返回一个新的socket,赋值给sp[1]
如果全部通过,则进行以下处理:
- set_close_on_exec(sp[0]);
- set_close_on_exec(sp[1]);
set_close_on_exec函数的实现只有一行:
fcntl(fd, F_SETFD, FD_CLOEXEC);
close on exec, not on-fork, 意为如果对描述符设置了FD_CLOEXEC,使用execl执行的程序里,此描述符被关闭,不能再使用它,但是在使用fork调用的子进程中,此描述符并不关闭,仍可使用。
4. return之前处理
closesocket(sock);
销毁掉最开始创建的LoopBack套接字sock
注:这个函数最重要的作用就是创建了两个socket,也即control socket pair。sp[0]是client端,sp[1]是accept出来的server端。
因此,mg_iterate_over_connections函数中就有send操作:
send(server->ctl[0], (void *) msg, sizeof(msg), 0);
execute_iteration函数中就有recv操作:
recv(server->ctl[1], (void *) msg, sizeof(msg), 0);
同时,在mg_poll_server轮询函数中有把sp[1]添加到select的FD_SET中和select之后的FD_ISSET判断:
add_to_set(server->ctl[1], &read_set, &max_fd);
if (FD_ISSET(server->ctl[1], &read_set))
从上面分析来看,这两个socket实际上是在api调用层,通过mg_iterate_over_connections接口,对每个connection执行自定义的操作的接口实现。只不过是要用异步接口,所以采用了内部socket中转了一下