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()
释放操作函数,也许程序要一直运行,但还是要在异常情况下或手动停止情况下进行资源的释放操作。
通过这几个函数基本框架就可以搭建起来了,最后剩下的就是业务处理模块了。