Mongoose-基于C的Web服務(wù)器 介紹和使用

前言

由于最近想要使用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)模式:

  1. 定義和初始化事件管理器:
struct mg_mgr mgr;
 mg_mgr_init(&mgr, NULL);
  1. 創(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);
  1. 在一個(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_mbufstruct 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_connectionvoid *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_dataint *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_dataint *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_dataint *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;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 名稱 libev - 一個(gè) C 編寫(xiě)的功能全面的高性能事件循環(huán)。 概要 示例程序 關(guān)于 libev Libev 是...
    hanpfei閱讀 15,551評(píng)論 0 5
  • 大綱 一.Socket簡(jiǎn)介 二.BSD Socket編程準(zhǔn)備 1.地址 2.端口 3.網(wǎng)絡(luò)字節(jié)序 4.半相關(guān)與全相...
    VD2012閱讀 2,706評(píng)論 0 5
  • ?追根究底?Libevent內(nèi)部實(shí)現(xiàn)原理初探 Libevent確實(shí)方便了開(kāi)發(fā)人員,對(duì)于定時(shí)器、信號(hào)處理、關(guān)心的文件...
    meng_philip123閱讀 4,859評(píng)論 0 4
  • 說(shuō)明 本文 翻譯自 realpython 網(wǎng)站上的文章教程 Socket Programming in Pytho...
    keelii閱讀 2,432評(píng)論 0 16
  • 2018.4.15.祈禱,讀經(jīng),今天我給兒子說(shuō)到母親節(jié)讓愛(ài)人給我買(mǎi)個(gè)高檔的炒鍋,兒子說(shuō):不用等過(guò)節(jié),你給爸爸說(shuō)一聲...
    玉鳳_0e9c閱讀 315評(píng)論 0 1

友情鏈接更多精彩內(nèi)容