參考資料:https://docs.cesanta.com/mongoose/master/
源碼下載地址:https://github.com/cesanta/mongoose
當前最新版本號:Mongoose 6.7
聲明:本文章參照Mongoose網站說明,可視爲其簡略翻譯,只做學習記錄使用,由於能力有限,不保證一定準確無誤。如需轉載請註明出處,謝謝~
簡述:
Mongoose前身爲shttpd,使用標準C/C++編寫而成,轉爲嵌入式設備設計的,支持跨平臺的網絡服務器庫。Mongoose實現了非阻塞式事件驅動API,支持TCP, UDP, HTTP, WebSocket, CoAP, MQTT。
Mongoose的三個基本結構體:
struct mg_mgr; // 持有所有活動的連接的事件管理器
struct mg_connection ; // 用於連接的套接字狀態的描述
struct mbuf ; // 用於接收和發送數據緩存的描述
聲明與初始化事件管理器
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);
}
收發緩衝區
每個連接都有自己的收發緩衝區struct mbuf,當Mongoose 接收到數據時數據被追加到接收緩衝區並觸發一個MG_EV_RECV 事件。當需要發送數據時,只需要使用 mg_send()或者mg_printf()將數據追加到發送緩衝區,當數據發送成功,Mongoose 將數據從發送緩衝區刪除併發送一個MG_EV_SEND事件。當連接關閉時,觸發MG_EV_CLOSE事件。以下爲mbuf的定義:
/* Memory buffer descriptor */
struct mbuf {
char *buf; /* Buffer pointer */
size_t len; /* Data length. Data is located between offset 0 and len. */
size_t size; /* Buffer size allocated by realloc(1). Must be >= len */
};
事件及事件處理函數
Mongoose 爲連接、讀寫、關閉等都定義了事件,每個連接都有與其關聯的事件處理函數——該函數由用戶自身實現,該函數的原型如下:
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
switch (ev) {
/* Event handler code that defines behavior of the connection */
...
}
}
典型的事件序列如下:
- 對於客戶端: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
以下爲Mongoose的核心事件:
- MG_EV_ACCEPT:當accept到一個新的連接請求時觸發該事件,ev_data 爲socket_address 聯合體
- MG_EV_CONNECT:當使用mg_connect() 創建連接套接字時觸發該事件,ev_data 爲int *success,success爲0表示成功,否則爲失敗的errno
- MG_EV_RECV:接收到新的數據,void *ev_data 是接收到的字節數,接收到應該使用recv_mbuf來獲取數據,使用mg_send()發送數據。Mongoose 使用realloc來擴展接收緩衝區,但是需要由用戶刪除已接收的數據——比如mbuf_remove()。
- MG_EV_SEND:Mongoose 已經將(int *)ev_data 的數據寫到了遠端並將數據從send_mbuf中刪除。mg_send()並不發送數據,只是將數據追加到緩衝區,正真的IO操作由mg_mgr_poll()完成。
- MG_EV_POLL:該事件被髮送給所有的已連接套接字,它可以被用來作任何持續性的事務,比如檢查某個連接是否已經超時或者關閉、過期,或者用來發送心跳消息。
- MG_EV_TIMER:當mg_set_timer() 被調用時被用來發送給指定的connection
連接標記位
每個連接都有自己的標記位,比如當創建一個UDP連接時,Mongoose 將爲這個連接的標記爲設置爲MG_F_UDP。
以下標記爲用戶設定:
- MG_F_FINISHED_SENDING_DATA:告訴Mongoose 數據已經全部存放到了發送緩衝區,當Mongoose 將數據發送完畢時,主動關閉連接。
- MG_F_BUFFER_BUT_DONT_SEND :告訴Mongoose 只將數據追加到緩衝區但是不要發送數據,因爲數據之後可能會被修改,當MG_F_BUFFER_BUT_DONT_SEND標記位被清除時數據再被髮送
- MG_F_CLOSE_IMMEDIATELY :告訴Mongoose 立馬關閉連接,一般在出錯的時候設置
- MG_F_USER_1, MG_F_USER_2, MG_F_USER_3, MG_F_USER_4:用戶自定義,用來存放應用的指定狀態
以下標記由Mongoose 設定:
- MG_F_SSL_HANDSHAKE_DONE SSL:只有在SSL連接中才會設置,當SSL的握手完成時設定
- MG_F_CONNECTING:在調用mg_connect() 後但是連接還沒有完成時設置
- MG_F_LISTENING:爲所有監聽套接字設置
- MG_F_UDP:如果是UDP協議則設置
- MG_F_IS_WEBSOCKET:如果是網絡套接字則設置
- MG_F_WEBSOCKET_NO_DEFRAG:由用戶希望關閉自動的websocket框架碎片整理時設置
編譯選項:
Mongoose 源代碼由單一的c文件構成,Mongoose 所支持的協議都由它提供。在編譯時Mongoose 可以去除不需要的功能以減小執行文件的大小。比如可以使用-D MG_DISABLE_MQTT -D MG_DISABLE_COAP去除代碼中的MQTT和CoAP的支持代碼。
linux下的編譯樣例:
cc my_app.c mongoose.c -D MG_DISABLE_MQTT -D MG_DISABLE_COAP
Mongoose 的使用樣例:
1.將mongoose.c 和mongoose.h拷貝到你的工程目錄下
2.使用mongoose提供的API編寫工程,例如my_app.c
3.編譯工程:cc my_app.c mongoose.c
#include "mongoose.h" // Include Mongoose API definitions
// Define an event handler function
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
struct mbuf *io = &nc->recv_mbuf;
switch (ev) {
case MG_EV_RECV:
// This event handler implements simple TCP echo server
mg_send(nc, io->buf, io->len); // Echo received data back
mbuf_remove(io, io->len); // Discard data from recv buffer
break;
default:
break;
}
}
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr, NULL); // Initialize event manager object
// Note that many connections can be added to a single event manager
// Connections can be created at any point, e.g. in event handler function
mg_bind(&mgr, "1234", ev_handler); // Create listening connection and add it to the event manager
for (;;) { // Start infinite event loop
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
return 0;
}