Mongoose是C语言网络库,为TCP、UDP、HTTP、WebSocket、CoAP、MQTT实现了事件驱动型的非阻塞api。
Mongoose库
Mongoose是出名的嵌入式网络编程库(https://github.com/cesanta/mongoose);只需微小的静态和运行时占用空间,实现了:
- 普通TCP、普通UDP、SSL/TLS(单向或双向)、客户端和服务器。
- http客户端和服务器。
- WebSocket客户端和服务器。
- MQTT客户机和服务器。
- CoAP客户端和服务器。
- DNS客户端和服务器。
- 异步DNS解析程序。
设计理念
Mongoose的三个基本数据结构:
struct mg_mgr;
struct mg_connection;
struct mbuf;
每个链接都使用mg_connection进行描述,一个连接可以是:
- outbound(出站)链接:通过调用mg_connect()产生;
- listening(监听)链接:通过调用mg_bind()产生;
- inbound(入站)链接:listening链接所收到的链接;
Mongoose应用应遵循事件驱动模式,通过mg_mgr_poll()遍历所有的套接字,接受新链接、发送、接收数据、关闭链接;并为各自的事件调用事件处理函数。
struct mg_mgr mgr;
mg_mgr_init(&mgr, NULL);
struct mg_connection *c = mg_bind(&mgr, "80", ev_handler_function);
mg_set_protocol_http_websocket(c);
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
缓冲区
每个连接都有一个发送和接收缓冲区:
mg_connection::send_mbuf :将接收到的数据加到recv_mbuf 后面,并触发一个MG_EV_RECV 事件;mg_connection::recv_mbuf :用户发送数据(mg_send() 或 mg_printf() )时,输出函数将数据追加到send_mbuf ;成功地将数据写到socket后,丢弃缓冲区中的数据并触发一个MG_EV_SEND 事件;
连接关闭后,触发一个MG_EV_CLOSE 事件。
事件处理函数
每个链接都有其与之相关的事件处理函数,是数据处理的关键元素。
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
switch (ev) {
...
}
}
参数说明:
mg_connection *nc :触发事件的连接;内部有void *user_data 字段,可保存用户自定义信息;int ev :事件编号;void *ev_data : 事件数据指针(不同的事件有不同的意义)
事件
Mongoose接受传入连接、读取和写入数据,并在适当时机触发对应指定事件:
- 出站连接:
MG_EV_CONNECT -> (MG_EV_RECV , MG_EV_SEND , MG_EV_POLL …) -> MG_EV_CLOSE - 入站连接:
MG_EV_ACCEPT -> (MG_EV_RECV , MG_EV_SEND , MG_EV_POLL …) -> MG_EV_CLOSE
核心事件说明:
MG_EV_ACCEPT : 接收到新连接,void *ev_data 是远程端的union socket_address 。MG_EV_CONNECT : 当mg_connect() 创建了一个新出站链接时(无论连接是否成功),void *ev_data 是int *success ;当success 是0,则连接已经建立,否则包含一个错误码(mg_connect_opt() 函数来查看错误码)。MG_EV_RECV :接收数据并追加到recv_mbuf 时触发。void *ev_data 是int *num_received_bytes (接收到的数据长度)。通常,事件处理函数应通过nc->recv_mbuf 检查接收数据,并通过mbuf_remove() 丢弃已处理的数据(用户有责任从接收缓冲区丢弃已处理的数据)。MG_EV_SEND : 已将数据写入到socket中,并且已经丢弃写入到mg_connection::send_mbuf 的数据;void *ev_data 是int *num_sent_bytes 。MG_EV_POLL :在每次调用mg_mgr_poll() 时触发。该事件被用于做任何事情,例如,检查某个超时是否已过期并关闭连接或发送心跳消息等。MG_EV_TIMER : 当mg_set_timer() 调用后触发。
连接flags
每个链接都有flags位域。
由事件处理程序设置的链接flags列表:
- MG_F_FINISHED_SENDING_DATA:告诉mongoose所有数据已经追加到send_mbuf,只要mongoose将数据写入socket,此链接就会关闭。
- MG_F_BUFFER_BUT_DONT_SEND:告诉mongoose追加数据到send_mbuf,但数据不要马上发送,因为此数据稍后会被修改。然后通过清除MG_F_BUFFER_BUT_DONT_SEND标志将数据发送出去。
- MG_F_CLOSE_IMMEDIATELY:告诉mongoose立即关闭链接,通常在产生错误后发送此事件。
- MG_USER_1,MG_USER_2,MG_USER_3,MG_USER_4:开发者可用它来存储特定应用程序的状态
由mongoose设置的flags:
- MG_F_SSL_HANDSHAKE_DONE:ssl握手完成时设置;
- MG_F_CONNECTING:在mg_connect()调用后链接处于链接状态,但未完成链接时设置;
- MG_F_LISTENING:在监听中;
- MG_F_UDP:链接是udp时设置;
- MG_F_WEBSOCKET:链接是websocket时设置;
- MG_F_WEBSOCKET_NO_DEFRAG:如果用户想关闭websocket的自动帧碎片整理功能,则由用户设置此标记。
Http示例
HTTP请求中主要消息格式为:
struct http_message {
struct mg_str message;
struct mg_str body;
struct mg_str method;
struct mg_str uri;
struct mg_str proto;
int resp_code;
struct mg_str resp_status_msg;
struct mg_str query_string;
struct mg_str header_names[MG_MAX_HTTP_HEADERS];
struct mg_str header_values[MG_MAX_HTTP_HEADERS];
};
RESTful Server
对于Http请求,MG_EV_HTTP_MSG为接收到消息时触发的事件。
mg_http_message中存放的是Http请求的消息,通过mg_http_reply来应答Http请求。
std::string getMgStr(struct mg_str str) {
return std::string(str.ptr, str.len);
}
// We use the same event handler function for HTTP and HTTPS connections
// fn_data is NULL for plain HTTP, and non-NULL for HTTPS
static void funCallback(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
std::cout << "Connect from: " << c->peer.ip << ":" << c->peer.port << ", type: " << ev << std::endl;
if (ev == MG_EV_ACCEPT && fn_data != NULL) {
struct mg_tls_opts opts = {
//.ca = "ca.pem", // Uncomment to enable two-way SSL
.cert = "server.pem", // Certificate PEM file
.certkey = "server.pem", // This pem contains both cert and key
};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
std::cout << "Http request: Method=" << getMgStr(hm->method) << ", URI=" << getMgStr(hm->uri)
<< ", query=" << getMgStr(hm->query) << ", proto=" << getMgStr(hm->proto) << std::endl;
std::cout << "Request body: " << getMgStr(hm->body) << std::endl;
if (mg_vcmp(&hm->uri, "/api/f1") == 0) {// Serve REST
json jData = json::parse(hm->body.ptr, hm->body.ptr + hm->body.len);
std::cout << "##iterate data\n";
for (auto it:jData) {
std::cout << "value: " << it << '\n';
}
std::cout << "##iterate items\n";
for (auto &el:jData.items()) {
std::cout << "Key: " << el.key() << ", value: " << el.value() << '\n';
}
mg_http_reply(c, 200, "", "{\"result\": %d}\n", 123);
} else if (mg_http_match_uri(hm, "/api/f2/*")) {
mg_http_reply(c, 200, "", "{\"result\": \"%.*s\"}\n", (int) hm->uri.len, hm->uri.ptr);
} else {
// struct mg_http_serve_opts opts = {.root_dir = s_root_dir};
// mg_http_serve_dir(c, ev_data, &opts);
mg_http_reply(c, 200, "", "Unknown request\n"); // Serve REST
}
}
(void) fn_data;
}
void startHttpServer() {
struct mg_mgr mgr; // Event manager
mg_log_set("2"); // Set to 3 to enable debug
mg_mgr_init(&mgr); // Initialise event manager
mg_http_listen(&mgr, s_http_addr, funCallback, NULL); // Create HTTP listener
// mg_http_listen(&mgr, s_https_addr, funCallback, (void *) 1); // HTTPS listener
for (;;)
mg_mgr_poll(&mgr, 1000); // Infinite event loop
mg_mgr_free(&mgr);
}
|