Mongoose-基于C的Web服务器 介绍和使用

前言

由于最近想要使用scheme需要做一个web服务器,从socket开始花费的时间很多,所以就选用一款基于C的web类库,然后用scheme包装它。
我打算选择Mongoose,这篇文章就记录Mongoose的要点。(注意,此Mongoose并非Nodejs中的)

简单说明

Mongoose是一个用C语言编写的网络库,它是一把用于嵌入式网络编程的瑞士军刀。它为TCP、UDP、HTTP、WebSocket、CoAP、MQTT实现了事件驱动的非阻塞API,用于客户机和服务器模式功能包括:
跨平台:适用于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等。
单线程、异步、无阻塞核心,具有简单的基于事件的api。

内置协议:
普通TCP、普通UDP、SSL/TLS(单向或双向)、客户端和服务器。
http客户端和服务器。
WebSocket客户端和服务器。
MQTT客户机和服务器。
CoAP客户端和服务器。
DNS客户端和服务器。
异步DNS解析程序。

Mongoose只需微小的静态和运行时占用空间,源代码既兼容ISOC又兼容ISO C++,而且很容易集成。

设计理念

Mongoose有三种基本数据结构:
struct mg_mgr是保存所有活动连接的事件管理器;
struct mg_connection描述连接;
struct mbuf描述数据缓冲区(接收或发送的数据);

connetions可以是listening, outbound 和 inbound。outbound连接由mg_connect()调用创建的。listening连接由mg_bind()调用创建的。inbound连接是侦听连接接受的连接。每个connetion都由struct mg_connection结构描述,该结构有许多字段,如socket、事件处理函数、发送/接收缓冲区、标志等。
使用Mongoose的应用程序应遵循事件驱动应用程序的标准模式:

  1. 定义和初始化事件管理器:
struct mg_mgr mgr;
 mg_mgr_init(&mgr, NULL);
  1. 创建连接。例如,一个服务程序需要创建监听连接。
struct mg_connection *c = mg_bind(&mgr, "80", ev_handler_function);
mg_set_protocol_http_websocket(c);
  1. 在一个循环里使用calling mg_mgr_poll()创建一个事件循环。
 for (;;) {
   mg_mgr_poll(&mgr, 1000);
 }

mg_mgr_poll() 遍历所有socket,接受新连接,发送和接受数据,关闭连接并调用事件处理函数。

内存缓存

每个连接都有一个发送和接收缓存。分别是struct mg_connection::send_mbufstruct mg_connection::recv_mbuf 。当数据接收后,Mongoose将接收到的数据加到recv_mbuf后面,并触发一个MG_EV_RECV事件。用户可以使用其中一个输出函数将数据发送回去,如mg_send()mg_printf()。输出函数将数据追加到send_mbuf。当Mongoose成功地将数据写到socket后,它将丢弃struct mg_connection::send_mbuf里的数据,并发送一个MG_EV_SEND事件。当连接关闭后,发送一个MG_EV_CLOSE事件。

事件处理函数

每个连接都有一个与之关联的事件处理函数。这些函数必须由用户实现。事件处理器是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 */
    ...
  }
}
  • struct mg_connection *nc : 接收事件的连接。
  • int ev : 时间编号,定义在mongoose.h。比如说,当数据来自于一个inbound连接,ev就是MG_EV_RECV
  • void *ev_data : 这个指针指向event-specific事件,并且对不同的事件有不同的意义。举例说,对于一个MG_EV_RECV事件,ev_data是一个int *指针,指向从远程另一端接收并保存到接收IO缓冲区中的字节数。ev_data确切描述每个事件的意义。Protocol-specific事件通常有ev_data指向保存protocol-specific信息的结构体。

注意:struct mg_connectionvoid *user_data,他是application-specific的占位符。Mongoose并没有使用这个指针。事件处理器可以保存任意类型的信息。

事件

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

以下是Mongoose触发的核心事件列表(请注意,除了核心事件之外,每个协议还触发特定于协议的事件):
MG_EV_ACCEPT: 当监听连接接受到一个新的服务器连接时触发。void *ev_data是远程端的union socket_address
MG_EV_CONNECT: 当mg_connect()创建了一个新出站链接时触发,不管成功还是失败。void *ev_dataint *success。当success是0,则连接已经建立,否则包含一个错误码。查看mg_connect_opt()函数来查看错误码示例。
MG_EV_RECV:心数据接收并追加到recv_mbuf结尾时触发。void *ev_dataint *num_received_bytes。通常,时间处理器应该在nc->recv_mbuf检查接收数据,通过调用mbuf_remove()丢弃已处理的数据。如果必要,请查看连接标识nc->flags(see struct mg_connection),并通过输出函数(如mg_send())写数据到远程端。

警告:Mongoose使用realloc()展开接收缓冲区,用户有责任从接收缓冲区的开头丢弃已处理的数据,请注意上面示例中的mbuf_remove()调用。

MG_EV_SEND: Mongoose已经写数据到远程,并且已经丢弃写入到mg_connection::send_mbuf的数据。void *ev_dataint *num_sent_bytes

注意:Mongoose输出函数仅追加数据到mg_connection::send_mbuf。它们不做任何socket的写入操作。一个真实的IO是通过mg_mgr_poll()完成的。一个MG_EV_SEND事件仅仅是一个关于IO完成的通知。

MG_EV_POLL:在每次调用mg_mgr_poll()时发送到所有连接。该事件被用于做任何事情,例如,检查某个超时是否已过期并关闭连接或发送心跳消息等。

MG_EV_TIMER: 当mg_set_timer()调用后,发送到连接。

TCP服务器示例

#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;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容

  • 名称 libev - 一个 C 编写的功能全面的高性能事件循环。 概要 示例程序 关于 libev Libev 是...
    hanpfei阅读 14,855评论 0 5
  • 大纲 一.Socket简介 二.BSD Socket编程准备 1.地址 2.端口 3.网络字节序 4.半相关与全相...
    VD2012阅读 2,109评论 0 5
  • ❲追根究底❳Libevent内部实现原理初探 Libevent确实方便了开发人员,对于定时器、信号处理、关心的文件...
    meng_philip123阅读 4,437评论 0 4
  • 说明 本文 翻译自 realpython 网站上的文章教程 Socket Programming in Pytho...
    keelii阅读 2,057评论 0 16
  • 2018.4.15.祈祷,读经,今天我给儿子说到母亲节让爱人给我买个高档的炒锅,儿子说:不用等过节,你给爸爸说一声...
    玉凤_0e9c阅读 213评论 0 1