前言 

        授人以鱼不如授人以渔,这篇文章详细介绍了,对于一个从来没有听说过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;
}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐