前言
由于最近想要使用scheme需要做一個(gè)web服務(wù)器,從socket開(kāi)始花費(fèi)的時(shí)間很多,所以就選用一款基于C的web類(lèi)庫(kù),然后用scheme包裝它。
我打算選擇Mongoose,這篇文章就記錄Mongoose的要點(diǎn)。(注意,此Mongoose并非Nodejs中的)
簡(jiǎn)單說(shuō)明
Mongoose是一個(gè)用C語(yǔ)言編寫(xiě)的網(wǎng)絡(luò)庫(kù),它是一把用于嵌入式網(wǎng)絡(luò)編程的瑞士軍刀。它為T(mén)CP、UDP、HTTP、WebSocket、CoAP、MQTT實(shí)現(xiàn)了事件驅(qū)動(dòng)的非阻塞API,用于客戶機(jī)和服務(wù)器模式功能包括:
跨平臺(tái):適用于linux/unix、macos、qnx、ecos、windows、android、iphone、freertos。
自然支持PicoTCP嵌入式TCP/IP堆棧,LWIP嵌入式TCP/IP堆棧。
適用于各種嵌入式板:ti cc3200、ti msp430、stm32、esp8266;適用于所有基于linux的板,如Raspberry PI, BeagleBone等。
單線程、異步、無(wú)阻塞核心,具有簡(jiǎn)單的基于事件的api。
內(nèi)置協(xié)議:
普通TCP、普通UDP、SSL/TLS(單向或雙向)、客戶端和服務(wù)器。
http客戶端和服務(wù)器。
WebSocket客戶端和服務(wù)器。
MQTT客戶機(jī)和服務(wù)器。
CoAP客戶端和服務(wù)器。
DNS客戶端和服務(wù)器。
異步DNS解析程序。
Mongoose只需微小的靜態(tài)和運(yùn)行時(shí)占用空間,源代碼既兼容ISOC又兼容ISO C++,而且很容易集成。
設(shè)計(jì)理念
Mongoose有三種基本數(shù)據(jù)結(jié)構(gòu):
struct mg_mgr是保存所有活動(dòng)連接的事件管理器;
struct mg_connection描述連接;
struct mbuf描述數(shù)據(jù)緩沖區(qū)(接收或發(fā)送的數(shù)據(jù));
connetions可以是listening, outbound 和 inbound。outbound連接由mg_connect()調(diào)用創(chuàng)建的。listening連接由mg_bind()調(diào)用創(chuàng)建的。inbound連接是偵聽(tīng)連接接受的連接。每個(gè)connetion都由struct mg_connection結(jié)構(gòu)描述,該結(jié)構(gòu)有許多字段,如socket、事件處理函數(shù)、發(fā)送/接收緩沖區(qū)、標(biāo)志等。
使用Mongoose的應(yīng)用程序應(yīng)遵循事件驅(qū)動(dòng)應(yīng)用程序的標(biāo)準(zhǔn)模式:
- 定義和初始化事件管理器:
struct mg_mgr mgr;
mg_mgr_init(&mgr, NULL);
- 創(chuàng)建連接。例如,一個(gè)服務(wù)程序需要?jiǎng)?chuàng)建監(jiān)聽(tīng)連接。
struct mg_connection *c = mg_bind(&mgr, "80", ev_handler_function);
mg_set_protocol_http_websocket(c);
- 在一個(gè)循環(huán)里使用calling mg_mgr_poll()創(chuàng)建一個(gè)事件循環(huán)。
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_poll() 遍歷所有socket,接受新連接,發(fā)送和接受數(shù)據(jù),關(guān)閉連接并調(diào)用事件處理函數(shù)。
內(nèi)存緩存
每個(gè)連接都有一個(gè)發(fā)送和接收緩存。分別是struct mg_connection::send_mbuf 和 struct mg_connection::recv_mbuf 。當(dāng)數(shù)據(jù)接收后,Mongoose將接收到的數(shù)據(jù)加到recv_mbuf后面,并觸發(fā)一個(gè)MG_EV_RECV事件。用戶可以使用其中一個(gè)輸出函數(shù)將數(shù)據(jù)發(fā)送回去,如mg_send() 或 mg_printf()。輸出函數(shù)將數(shù)據(jù)追加到send_mbuf。當(dāng)Mongoose成功地將數(shù)據(jù)寫(xiě)到socket后,它將丟棄struct mg_connection::send_mbuf里的數(shù)據(jù),并發(fā)送一個(gè)MG_EV_SEND事件。當(dāng)連接關(guān)閉后,發(fā)送一個(gè)MG_EV_CLOSE事件。
事件處理函數(shù)
每個(gè)連接都有一個(gè)與之關(guān)聯(lián)的事件處理函數(shù)。這些函數(shù)必須由用戶實(shí)現(xiàn)。事件處理器是Mongoose程序的核心元素,因?yàn)樗x程序的行為。以下是一個(gè)處理函數(shù)的樣子:
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
switch (ev) {
/* Event handler code that defines behavior of the connection */
...
}
}
-
struct mg_connection *nc: 接收事件的連接。 -
int ev: 時(shí)間編號(hào),定義在mongoose.h。比如說(shuō),當(dāng)數(shù)據(jù)來(lái)自于一個(gè)inbound連接,ev就是MG_EV_RECV。 -
void *ev_data: 這個(gè)指針指向event-specific事件,并且對(duì)不同的事件有不同的意義。舉例說(shuō),對(duì)于一個(gè)MG_EV_RECV事件,ev_data是一個(gè)int *指針,指向從遠(yuǎn)程另一端接收并保存到接收IO緩沖區(qū)中的字節(jié)數(shù)。ev_data確切描述每個(gè)事件的意義。Protocol-specific事件通常有ev_data指向保存protocol-specific信息的結(jié)構(gòu)體。
注意:struct mg_connection有void *user_data,他是application-specific的占位符。Mongoose并沒(méi)有使用這個(gè)指針。事件處理器可以保存任意類(lèi)型的信息。
事件
Mongoose接受傳入連接、讀取和寫(xiě)入數(shù)據(jù),并在適當(dāng)時(shí)為每個(gè)連接調(diào)用指定的事件處理程序。典型的事件順序是:
對(duì)于出站連接:MG_EV_CONNECT -> (MG_EV_RECV, MG_EV_SEND, MG_EV_POLL ...) -> MG_EV_CLOSE
對(duì)于入站連接:MG_EV_ACCEPT -> (MG_EV_RECV, MG_EV_SEND, MG_EV_POLL ...) -> MG_EV_CLOSE
以下是Mongoose觸發(fā)的核心事件列表(請(qǐng)注意,除了核心事件之外,每個(gè)協(xié)議還觸發(fā)特定于協(xié)議的事件):
MG_EV_ACCEPT: 當(dāng)監(jiān)聽(tīng)連接接受到一個(gè)新的服務(wù)器連接時(shí)觸發(fā)。void *ev_data是遠(yuǎn)程端的union socket_address。
MG_EV_CONNECT: 當(dāng)mg_connect()創(chuàng)建了一個(gè)新出站鏈接時(shí)觸發(fā),不管成功還是失敗。void *ev_data是int *success。當(dāng)success是0,則連接已經(jīng)建立,否則包含一個(gè)錯(cuò)誤碼。查看mg_connect_opt()函數(shù)來(lái)查看錯(cuò)誤碼示例。
MG_EV_RECV:心數(shù)據(jù)接收并追加到recv_mbuf結(jié)尾時(shí)觸發(fā)。void *ev_data是int *num_received_bytes。通常,時(shí)間處理器應(yīng)該在nc->recv_mbuf檢查接收數(shù)據(jù),通過(guò)調(diào)用mbuf_remove()丟棄已處理的數(shù)據(jù)。如果必要,請(qǐng)查看連接標(biāo)識(shí)nc->flags(see struct mg_connection),并通過(guò)輸出函數(shù)(如mg_send())寫(xiě)數(shù)據(jù)到遠(yuǎn)程端。
警告:Mongoose使用realloc()展開(kāi)接收緩沖區(qū),用戶有責(zé)任從接收緩沖區(qū)的開(kāi)頭丟棄已處理的數(shù)據(jù),請(qǐng)注意上面示例中的mbuf_remove()調(diào)用。
MG_EV_SEND: Mongoose已經(jīng)寫(xiě)數(shù)據(jù)到遠(yuǎn)程,并且已經(jīng)丟棄寫(xiě)入到mg_connection::send_mbuf的數(shù)據(jù)。void *ev_data是int *num_sent_bytes。
注意:Mongoose輸出函數(shù)僅追加數(shù)據(jù)到mg_connection::send_mbuf。它們不做任何socket的寫(xiě)入操作。一個(gè)真實(shí)的IO是通過(guò)mg_mgr_poll()完成的。一個(gè)MG_EV_SEND事件僅僅是一個(gè)關(guān)于IO完成的通知。
MG_EV_POLL:在每次調(diào)用mg_mgr_poll()時(shí)發(fā)送到所有連接。該事件被用于做任何事情,例如,檢查某個(gè)超時(shí)是否已過(guò)期并關(guān)閉連接或發(fā)送心跳消息等。
MG_EV_TIMER: 當(dāng)mg_set_timer()調(diào)用后,發(fā)送到連接。
TCP服務(wù)器示例
#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;
}