天天看点

mongoose源码分析系列一一个简单的mg_server的使用过程如下mg_create_server函数实现mg_socketpair函数

注1:选择5.1版本进行分析是因为我参与的项目中使用的就是5.1版本,而且个人理解这一版本的代码结构比较有序,功能也不是太复杂。当然后续会继续分析5.2,5.3以及最新的代码。 注2:在分析的过程中,为了简化代码和逻辑,我打开了下述编译宏以关闭对应的功能:

NO_CGI,NO_WEBSOCKET,NO_DIRECTORY_LISTING,NO_DAV,NO_AUTH,NO_LOGGING
           

注3:有一个gcc自带的工具cpp,非常便于对原始代码进行预处理。对于我这样不喜欢#if和#ifdef之类的编译宏,在阅读代码时有很大的帮助。我使用的cpp预处理命令如下:

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)

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中转了一下。

继续阅读