mongoose使用详细 -- 如何通过mongoose搭建服务器

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: mongoose使用详细 -- 如何通过mongoose搭建服务器

前言

       授人以鱼不如授人以渔,这篇文章详细介绍了,对于一个从来没有听说过mongoose的小菜鸟如何快速了解和上手mongoose

       其他一些开源库可以借助类似的方法进行学习

提前需要准备的工具

1.官网文档  Mongoose :: Documentation

  官网提供了很多例子讲解,本文主要针对HTTP server/client和webSocket Server/client进行讲解。

2.下载源码 GitHub - cesanta/mongoose: Embedded Web Server

   下载最新版源码,本文主要通过源码中例子进行快速了解mongoose如何使用,以官网提供的文档进行辅助工具

3.阅读源码的工具(这里推荐Source Insight)

4.开发工具(IDE,本文使用VS2017,请根据自己的开发环境进行选择)

通过source insight查看源码

对于如何使用source insight,安装下面截图的步骤进行基本不会有太大的问题,这里把工具也分享给大家:source insight4.0破解版

打开的整体页码:

mongoose简介

根据官网文档给出的描述大致总结下:

1.mongoose是一个用于C/C++的网络库,它为TCP、UDP、HTTP、WebSocket、MQTT实现了事件驱动的非阻塞API。Mongoose使嵌入式编程快速、健壮、简单。

2.mongoose可在windows、Linux、Mac和许多嵌入式架构上运行。它可以在现有的操作系统和TCP/IP堆栈(如FreeRTOS和lwIP)上运行,也可以在裸机上运行,利用Mongoose内置的TCP/IP堆栈和网络驱动程序。

如何使用mongoose

这部分官网提供了说明,根据2-minute integration guide这部分的说明,我们可以猜测下:

为了将Mongoose集成到现有的C/C++应用程序或固件中,请使用以下步骤:

1.将mongoose.c和mongoose.h复制到源代码中

使用起来很简单,只需要把mongoose.c和mongoose.h加入到源代码中就可以顺畅的使用了,不需要使用cmake和vs编译,神仙体验。

分析http-server

再来说下,为什么建议去看源码中的例子,而不是别人博客中的案例。版本问题,不同的版本可能使用的方法会有一些不同,不同的版本支持功能源有会有一些差异。

如何看源码:主--宾,动词最重要,也就是说函数最重要(函数就是一个动作)

重要的函数先摘出来:

signal(SIGINT, signal_handler);
mg_log_set(s_debug_level);
mg_mgr_init(&mgr);
mg_http_listen(&mgr, s_listening_address, cb, &mgr)
mg_casecmp(s_enable_hexdump, "yes")
MG_INFO(("Mongoose version : v%s", MG_VERSION));
mg_mgr_poll(&mgr, 1000);
mg_mgr_free(&mgr);

我们看下函数原型:

1===================

_ACRTIMP _crt_signal_t __cdecl signal(_In_ int _Signal, _In_opt_ _crt_signal_t _Function);

可以猜测应该是为某一个信号增加一个控制方式: signal(SIGINT, signal_handler);

为SIGINT这个信号做一个处理动作,SIGINT是ctrl+C时触发

我们看源码中signal_handler的信号捕捉函数:

typedef void (__CRTDECL* _crt_signal_t)(int);

2===================

mg_log_set(s_debug_level);

设置日志等级,如果源码中没有给出注释,可以查看官网中的文档

这里应该是把日志设置成了INFO和ERROR等级

3=======================

mg_mgr_init(&mgr);

函数原型:

void mg_mgr_init(struct mg_mgr *mgr)

根据文档描述可以知道,他是为事件管理器mg_mgr进行初始化的,初始化工作做了什么:

1.将活动连接列表设置为NULL

2.设置默认的DNS server为IPv4和IPv6

3.设置默认的DNS查找超时

什么是mg_mgr呢?

事件管理结构,包含活动连接的列表以及一些内务管理信息

4===============================

mg_http_listen(&mgr, s_listening_address, cb, &mgr)

函数原型:

struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data);

创建一个HTTP服务

参数:

mgr:一个事件管理器

url:附加一个本机的IP和监听的端口,http://0.0.0.0:8000

fn:回调函数,当有监听到有连接进来的时候,就执行回调函数

fn_data:传给回调函数的参数

返回值:指向已创建连接的指针,或出现错误时为NULL

5===============================

mg_casecmp(s_enable_hexdump, "yes")

函数原型:

int mg_casecmp(const char *s1, const char *s2);

不分大小写的比较两个以NULL结束的字符串

参数:s1,s2指向这两个字符串的指针

返回值:如果返回0则说明这两个字符串相等,如果大于0则s1>s2,如果小于0则s1<s2

6===============================

MG_INFO(("Mongoose version : v%s", MG_VERSION));

函数原型:

#define MG_INFO(args) MG_LOG(MG_LL_INFO, args)

写日志

7==============================

mg_mgr_poll(&mgr, 1000);

函数原型:

void mg_mgr_poll(struct mg_mgr *mgr, int ms);

执行轮询迭代,对于在监听链表中的每一个连接

1.查看是否有传入数据,如果有,将其读入到读缓冲区中,并发送 MG_EV_READ事件

2.查看是否有数据在写缓冲区,并且写入,发送MG_EV_WRITE事件

3.如果连接正在侦听,则接受传入连接(如果有),并向其发送MG_EV_accept事件

4.发送MG_EV_POLL事件

参数:

mgr:一个事件管理器

ms:超时时间(毫秒)

8===============================

mg_mgr_free(&mgr);

函数原型:

void mg_mgr_free(struct mg_mgr *mgr);

关闭所有的连接,释放所有的资源


结合上面的描述我们做个总结:

日志部分我们这里不做探究,直接跳过。

1.我们需要准备一个事件管理器,有两个步骤:

struct mg_mgr mgr;

mg_mgr_init(&mgr);

2.然后对这个事件管理器设置监听事件并且指定IP和PORT:

准备一个回调函数 ==> 回调函数内部我们之后探究

准备指定的IP和端口

mg_http_listen();

3.调用消息循环

mg_mgr_poll();       // 指定阻塞的时长

4.停止循环和释放资源

mg_mgr_free()

HTTP SERVER - V1.0

// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
#include <signal.h>
#include "mongoose.h"
static const char *s_listening_address = "http://0.0.0.0:8000";
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
  if (ev == MG_EV_HTTP_MSG) {
    printf("有数据到达\n");
  }
  (void)fn_data;
}
int main(int argc, char *argv[]) {
  struct mg_mgr mgr;
  struct mg_connection *c;
  mg_mgr_init(&mgr);
  if ((c = mg_http_listen(&mgr, s_listening_address, cb, &mgr)) == NULL) {
    exit(EXIT_FAILURE);
  }
  // Start infinite event loop
  while (1) mg_mgr_poll(&mgr, 1000);
  mg_mgr_free(&mgr);
  return 0;
}

客户端用postman进行测试就可以了,这里不自己写客户端代码了

HTTP SERVER - v2.0

现在我们对回调函数好好分析下:

函数原型,mongoose是通过函数指针来实现回调的,其原理跟信号捕捉函数一样。

typedef void (*mg_event_handler_t)(struct mg_connection *, int ev, void *ev_data, void *fn_data);

官方文档给出了结构体中各个成员的解释我们经常会用到的:POST请求和GET请求

// 区别是POST请求还是GET请求
if (strstr(hm->method.ptr, "POST"))
{
  printf("这是POST请求\n");
  mg_http_reply(c, 200, "Content-Type: application/json\r\n", "{%s:%s}", "status", "okk");
}
else if(strstr(hm->method.ptr, "GET"))
{
  printf("这是GET请求\n");
  mg_http_reply(c, 200, "Content-Type: application/json\r\n", "{%s:%s}", "status", "okk");
}
else
{
  mg_http_reply(c, 500, NULL, "{%s:%s}", "status", "已经收到GET请求");
}

有时候会根据uri进行不同的处理: /hello

if (mg_http_match_uri(hm, "/hello"))
{
    if (strstr(hm->method.ptr, "POST"))
    {
      printf("这是POST请求\n");
        mg_http_reply(c, 200, "Content-Type: application/json\r\n", "{%s:%s}", "status", "okk");
    }
  else if (strstr(hm->method.ptr, "GET"))
    {
    printf("这是GET请求\n");
    mg_http_reply(c, 200, "Content-Type: application/json\r\n", "{%s:%s}", "status", "okk");
  }
  else
  {
    mg_http_reply(c, 500, NULL, "{%s:%s}", "status", "已经收到GET请求");
  }
}
else if (mg_http_match_uri(hm, "/app"))
{
    printf("------------------\n");
}

mongoose给出了几种不同的方式发送消息:

int mg_printf(struct mg_connection *, const char *fmt, ...);
void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...);

官网给出了很多函数,这些都是关于HTTP,官网有详细的介绍。

这里就不每一个都给大家解释了

HTTP-CLIENT

在mongoose的HTTP客户端中内部自动帮我们发送连接,因为数据可能比较复杂。在建立连接的时候会发送一个MG_EV_CONNECT事件,我们在回调函数中对该事件进行处理。

客户端代码与服务端基本相同,唯一不同的是连接和监听连接不同,其他基本相同

#include <stdio.h>
#include <string.h>
#include "mongoose.h"
static const char *s_url = "http://127.0.0.1:8080/hello";
void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data)
{
  const char *post_data = "aaaa";
  // 连接事件
  if (MG_EV_CONNECT == ev)
  {
    struct mg_str host = mg_url_host(s_url);   // 解析出主机
    // 发送请求
    mg_printf(c, "%s %s HTTP/1.1\r\n"
               "Host:%.*s\r\n"
               "\r\n"
               "%.*s", post_data == NULL ? "GET":"POST", 
               mg_url_uri(s_url), 
              (int)host.len, host.ptr, 
              post_data == NULL ? 0:(int)strlen(post_data), 
              post_data);
  }
  // 接受到回应
  if (MG_EV_HTTP_MSG == ev)
  {
    struct mg_str host = mg_url_host(s_url);   // 解析出主机
    struct mg_http_message *hm = (struct mg_http_message *)ev_data;
    printf("%s", hm->message);
    mg_printf(c, "%s %s HTTP/1.1\r\n"
      "Host:%.*s\r\n"
      "\r\n"
      "%.*s", post_data == NULL ? "GET" : "POST",
      mg_url_uri(s_url),
      (int)host.len, host.ptr,
      post_data == NULL ? 0 : (int)strlen(post_data),
      post_data);
  }
}
int main(void)
{
  // 1.创建一个
  struct mg_mgr mgr;
  mg_mgr_init(&mgr);
  // 2.连接HTTP服务器
  mg_http_connect(&mgr, "127.0.0.1:8080", cb, NULL);
  while (1)
  {
    // 消息循环
    mg_mgr_poll(&mgr, 1000);
  }
  // 释放资源
  mg_mgr_free(&mgr);
  return 0;
}

WEBSOCKET-SERVER

可能很多朋友对webSocket协议不是很了解,这里简单说下:

webSocket协议

       本质上与HTTP协议类型,webSocket是一种全双工通信的协议,是基于HTTP协议的不足提出的一种新的通信协议。

       http是一种单向的应用层协议,它采用了请求响应模型,通信请求只能由客户端发起,服务端对请求做出应答处理。这样的弊端显然是很大的,只要服务器状态连续变化,客户端就必须实时响应,这样显然很麻烦,同时轮询的效率低,非常的浪费资源。

       webSocket是一种全面双工通讯的网络技术,任意一方都可以建立连接将数据推向另一方,webSocket只需要建立一次连接就可以一直保持。

mongoose对webSocket的处理

       mongoose的处理很简单,还是通过HTTP进行处理的,只不过在回调函数中会发送一个MG_EV_WS_MSG的事件,通过mg_ws_upgrade()将数据转换成ws数据进行处理,当调用mg_ws_upgrade会发送一个MG_EV_WS_MSG事件。

#include "mongoose.h"
static const char *s_listen_on = "ws://localhost:8080";
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
  //if (ev == MG_EV_OPEN) {
  //  // c->is_hexdumping = 1;
  //}
  //else 
  if (ev == MG_EV_HTTP_MSG) {
    struct mg_http_message *hm = (struct mg_http_message *) ev_data;
    if (mg_http_match_uri(hm, "/websocket")) {
      // Upgrade to websocket. From now on, a connection is a full-duplex
      // Websocket connection, which will receive MG_EV_WS_MSG events.
      mg_ws_upgrade(c, hm, NULL);
    }
  }
  else if (ev == MG_EV_WS_MSG) {
    // Got websocket frame. Received data is wm->data. Echo it back!
    struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
    printf("%s", wm->data);
    mg_ws_send(c, wm->data.ptr, wm->data.len, WEBSOCKET_OP_TEXT);
  }
  (void)fn_data;
}
int main(void) {
  struct mg_mgr mgr;  // Event manager
  mg_mgr_init(&mgr);  // Initialise event manager
  mg_http_listen(&mgr, s_listen_on, fn, NULL);  // Create HTTP listener
  while (1)
  {
    mg_mgr_poll(&mgr, 1000);             // Infinite event loop
  }
  mg_mgr_free(&mgr);
  return 0;
}
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
JavaScript NoSQL MongoDB
100 # mongoose 的使用
100 # mongoose 的使用
62 0
|
NoSQL MongoDB
mongodb踩坑-Error: Cannot find module 'mongoose'
mongodb踩坑-Error: Cannot find module 'mongoose'
86 0
|
存储 前端开发 数据库
electron项目中使用本地数据库sqlite3和sequelize框架
electron项目中使用本地数据库sqlite3和sequelize框架
|
22天前
|
存储 数据库
mongoose
【10月更文挑战第30天】
24 4
|
2月前
|
JavaScript 前端开发 关系型数据库
使用Sequelize8
使用Sequelize8
25 0
|
5月前
|
JavaScript NoSQL 数据库连接
使用Nodejs + express连接数据库mongoose
【6月更文挑战第3天】这篇文章介绍了如何在Express应用中使用Mongoose连接MongoDB数据库。首先,需要创建一个`db.js`文件,然后通过`npm install mongoose`安装Mongoose驱动。接着,在应用中引入MongoDB模块,建立到数据库的连接。创建一个Mongoose schema定义数据模型,如用户信息表。最后,执行数据库操作,包括查询、插入、更新和删除文档,并在完成后关闭数据库连接。文中还提供了相关代码示例。
196 1
|
NoSQL JavaScript 关系型数据库
Mongoose-开篇
Mongoose 概述 • Mongoose和MySQL的Sequelize一样, 都是NodeJS中操作数据库的对象模型工具 • Mongoose使用面向对象的思想对原生的mongoDB命令进行了封装
85 0
|
NoSQL MongoDB
mongoose之bulkWrite
mongoose之bulkWrite
184 0
小满nestjs(第六章 nestjs cli 常用命令)
第一次使用这个命令的时候,除了生成文件之外还会自动使用 npm 帮我们更新资源,安装一些额外的插件,后续再次使用就不会更新了。
121 0
小满nestjs(第六章 nestjs cli 常用命令)
|
NoSQL MongoDB 数据库
Mongoose
定义schema 数据库中的schema,为数据库对象的集合,schema是mongoose会用到的一种数据模式,可以理解为表结构的定义:每个schema会映射到mongodb中的一个collection。他不具备操作数据库的能力
121 0
Mongoose