mongoose 是一个支持多协议的开源代码,这里主要结合 mongoose 源码梳理一下应用程序开发的基本步骤。
一般的 main 函数或是启动 mongoose 监听功能的代码,如下:
- int main(int argc, char *argv[])
- {
- if(argc < 2)
- {
- errorf("Usage: %s port\n", argv[0]);
- return 1;
- }
-
- int port = atoi(argv[1]);
-
- // webStart();
-
- struct userData uData{100};
- struct mg_mgr mgr;
-
- //mgr里的user_data指针将会指向第二个参数,当有连接过来时
- //mg_connection中的mgr中的user_data指向的就是第二个参数
- mg_mgr_init(&mgr, &uData);
-
- char buf[32] = {0};
- snprintf(buf, sizeof(buf), "%d", port);
- struct mg_connection *con = mg_bind(&mgr, buf, eventHandler);
-
- if(con == NULL) {
- errorf("mg_bind fail\n");
- return -1;
- }
-
- mg_set_protocol_http_websocket(con);
-
- uData.index = 200;
- infof("listen ip[%s], port[%d]....\n", inet_ntoa(con->sa.sin.sin_addr), port);
-
- //uri是/fileUpload 时调用函数fileUpload
- // mg_register_http_endpoint(con, "/fileUpload", fileUpload);
-
- while (1)
- {
- mg_mgr_poll(&mgr, 100);
- sleep(1);
- }
-
- mg_mgr_free(&mgr);
- return 0;
- }
其中涉及到 mongoose 接口的就有 5 个,下面我们一起看一下这 5 个接口主要做了哪些工作。
1,mg_mgr_init()
函数原型是:void mg_mgr_init(struct mg_mgr *mgr, void *user_data); 这个接口就是对 mgr 参数进行赋值操作,其实第二个参数也会赋值到 mgr 中,struct mg_mgr 结构体中就一个指针参数
- struct mg_mgr {
- struct mg_connection *active_connections;
- #if MG_ENABLE_HEXDUMP
- const char *hexdump_file; /* Debug hexdump file path */
- #endif
- #if MG_ENABLE_BROADCAST
- sock_t ctl[2]; /* Socketpair for mg_broadcast() */
- #endif
- void *user_data; /* User data */
- int num_ifaces;
- int num_calls;
- struct mg_iface **ifaces; /* network interfaces */
- const char *nameserver; /* DNS server to use */
- };
中的 void *user_data; 第二个参数就是赋值给 user_data。
那这个 user_data 指针有什么用呢?实际使用时这个指针会保存某个类(单例)的地址,这样在事件触发时,回调函数里的参数 struct mg_connection *nc 就带着这个指针,这样的话就可以把这个指针转换成你实际的类,然后进行类操作,比如事件回调函数是全局的,但实际处理事件的是一个单例类,就可以这样:
- static void onHttpEvents(mg_connection *nc, int event, void *eventData) {
- WebSvrImpl* server = (WebSvrImpl*)(nc->mgr->user_data);
- server->onEvent(nc,event,eventData,true);
- }
这个初始化函数最主要的工作是指定了与 socket 相关的函数接口,如 listen、poll、connect、send、recv 等函数,这些函数才是真正操作 socket 的函数,而我们代码里调用的一些发送接口,如:mg_send() 接口只是把数据发送到 mg_connection 的 send_mbuf 缓冲区,下面的 mg_socket_if_tcp_send 接口才是真正发送数据的。这些默认接口为:
- /* clang-format off */
- #define MG_SOCKET_IFACE_VTABLE \
- { \
- mg_socket_if_init, \
- mg_socket_if_free, \
- mg_socket_if_add_conn, \
- mg_socket_if_remove_conn, \
- mg_socket_if_poll, \
- mg_socket_if_listen_tcp, \
- mg_socket_if_listen_udp, \
- mg_socket_if_connect_tcp, \
- mg_socket_if_connect_udp, \
- mg_socket_if_tcp_send, \
- mg_socket_if_udp_send, \
- mg_socket_if_tcp_recv, \
- mg_socket_if_udp_recv, \
- mg_socket_if_create_conn, \
- mg_socket_if_destroy_conn, \
- mg_socket_if_sock_set, \
- mg_socket_if_get_conn_addr, \
- }
- /* clang-format on */
这些默认接口会被初始化到一个vtable变量里:m->ifaces[i]->vtable->init(m->ifaces[i]); 而这个init函数指针就是指向了上面的 MG_SOCKET_IFACE_VTABLE
- struct mg_iface_vtable {
- void (*init)(struct mg_iface *iface);
- void (*free)(struct mg_iface *iface);
- void (*add_conn)(struct mg_connection *nc);
- void (*remove_conn)(struct mg_connection *nc);
- time_t (*poll)(struct mg_iface *iface, int timeout_ms);
-
- /* Set up a listening TCP socket on a given address. rv = 0 -> ok. */
- int (*listen_tcp)(struct mg_connection *nc, union socket_address *sa);
- /* Request that a "listening" UDP socket be created. */
- int (*listen_udp)(struct mg_connection *nc, union socket_address *sa);
-
- /* Request that a TCP connection is made to the specified address. */
- void (*connect_tcp)(struct mg_connection *nc, const union socket_address *sa);
- /* Open a UDP socket. Doesn't actually connect anything. */
- void (*connect_udp)(struct mg_connection *nc);
-
- /* Send functions for TCP and UDP. Sent data is copied before return. */
- int (*tcp_send)(struct mg_connection *nc, const void *buf, size_t len);
- int (*udp_send)(struct mg_connection *nc, const void *buf, size_t len);
-
- int (*tcp_recv)(struct mg_connection *nc, void *buf, size_t len);
- int (*udp_recv)(struct mg_connection *nc, void *buf, size_t len,
- union socket_address *sa, size_t *sa_len);
-
- /* Perform interface-related connection initialization. Return 1 on ok. */
- int (*create_conn)(struct mg_connection *nc);
- /* Perform interface-related cleanup on connection before destruction. */
- void (*destroy_conn)(struct mg_connection *nc);
-
- /* Associate a socket to a connection. */
- void (*sock_set)(struct mg_connection *nc, sock_t sock);
-
- /* Put connection's address into *sa, local (remote = 0) or remote. */
- void (*get_conn_addr)(struct mg_connection *nc, int remote,
- union socket_address *sa);
- };
这些函数指针的调用就是调用上面定义的那些函数了。
2,mg_bind()
struct mg_connection *con = mg_bind(&mgr, buf, eventHandler);
mg_bind 实际调用的是函数
return mg_bind_opt(srv, address, MG_CB(event_handler, user_data), opts);
mg_bind_opt()函数主要做地址解析,就是第二个参数 address,可以指定 tcp 或 udp,指定监听的 IP地址和端口,也可以只写端口号,如:
- int port = atoi(argv[1]);
- char buf[32] = {0};
- snprintf(buf, sizeof(buf), "%d", port);
- struct mg_connection *con = mg_bind(&mgr, buf, eventHandler);
这里从命令行参数里获取要绑定的端口号,传递到 mg_bind() 的第二个参数里。
上面说到的 user_data 就是在 mg_create_connection() 里赋值给创建的 struct mg_connection 变量进行返回,即 nc。往下就是进行监听和链接添加入管理
- if (nc->flags & MG_F_UDP) {
- rc = nc->iface->vtable->listen_udp(nc, &nc->sa);
- } else {
- rc = nc->iface->vtable->listen_tcp(nc, &nc->sa);
- }
- if (rc != 0) {
- fprintf(stderr, "Failed to open listener: %d", rc);
- MG_SET_PTRPTR(opts.error_string, "failed to open listener");
- mg_destroy_conn(nc, 1 /* destroy_if */);
- return NULL;
- }
- mg_add_conn(nc->mgr, nc);
执行完 mg_bind() 监听socket 已经创建。
3,mg_set_protocol_http_websocket()
- void mg_set_protocol_http_websocket(struct mg_connection *nc) {
- nc->proto_handler = mg_http_handler;
- }
就指定了一个回调函数。函数主要进行消息解析,然后进行事件的回调,即用户注册进来的回调函数。
4,mg_mgr_poll()
- int mg_mgr_poll(struct mg_mgr *m, int timeout_ms) {
- int i, num_calls_before = m->num_calls;
- infof("num_ifaces = %d\n", m->num_ifaces);
- for (i = 0; i < m->num_ifaces; i++) {
- m->ifaces[i]->vtable->poll(m->ifaces[i], timeout_ms);
- }
-
- return (m->num_calls - num_calls_before);
- }
这个函数是进行 poll 操作,但实际用的是 select() 系统调用,而不是 poll 和 epoll 函数。poll 函数指针指向的是这个函数 mg_socket_if_poll(),函数里调用 select() 系统调用。所以应用程序里 mg_mgr_poll() 一般是循环调用,即不停地判断是否有可读、可写或出错的 fd。
- while (1)
- {
- mg_mgr_poll(&mgr, 100);
- sleep(1);
- }
一秒执行一次 select () 的打印信息:
5,mg_mgr_free()
释放操作函数,也许程序要一直运行,但还是要在异常情况下或手动停止情况下进行资源的释放操作。
通过这几个函数基本框架就可以搭建起来了,最后剩下的就是业务处理模块了。