Mongoose-6.14源码剖析之基础篇(一)

文章目录

1 Mongoose介绍

     Mongoose 是一款嵌入式 web服务器。使用C语言编写,它可以很容易的嵌入到其他平台或是程序中,并为其提供了web接口,它具有很强的可移植性,而且对跨平台编译支持得非常出色。它是用于嵌入式网络编程的瑞士军刀。为客户端和服务器模式实现了用于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解析器
· 微小的静态和运行时占用空间
· 源代码均符合ISO C和ISO C ++
· 易于集成:只需将 mongoose.c和 mongoose.h 文件复制到构建树中


2 Mongoose 安装

2.1 Mongoose源码下载

     Mongoose最新的的版本号是6.16。其源码下载地址是[mongoose/release], 该链接中有着Mongoose历代的所有版本。点击链接进入到mongoose的下载地址,如下所示:
在这里插入图片描述
图1 mongoose源码下载
.

     下载mongoose-x.xx.tar.gz格式的压缩包,然后将其拷贝到待安装的操作系统上面某目录之下解压,若是类UNIX操作系统,则tar -zxvf mongoose-x.xx.tar.gz,便可得到以mongoose-为前缀,再加上主版本、次版本号组成的目录,进入该目录下,则可看到mongoose.c和mongoose.h两个文件,已经其他一些相关跨平台的.h和.c文件。目录结构如下:

在这里插入图片描述
图2 mongoose目录结构

2.2 Mongoose目录结构介绍

     src目录下为mongoose所支持的所有相关网络协议,如http、tcp、udp等的定义和实现;而src目录下的common目录中内容是mongoose中所需要的日志打印、系统时间、MD5加密等公用文件,除此之外,该common目录下还有一个platforms目录,该目录下是mongoose跨平台所依赖的所有条件编译文件。值得注意的是,实际项目开发中,我们只需要mongoose.c + mongoose.h两个文件就可以了。因为mongoose.h头文件中通过宏的方式,已经将 src/comon/platforms目录下的所有跨平台的文件定义嵌入到了其中。所以不再需要我们将src目录下的所有文件及目录都嵌套在我们的项目里。在程序编译的时候,通过操作系统内部宏去检测编译和判断当前的平台及操作系统是什么,然后开启相应的功能。 关于操作系统内部宏请参考博客“ 《操作系统、硬件平台、编译器预处理宏》 ”。
在这里插入图片描述
图3 mongoose编译检测当前操作系统平台

      CS_PLATFORM 宏定义代表当前平台的类型,如果当前平台在其所支持的平台架构类型列表中没有找到,则报错提示。

//mongoose.h 头文件开头的条件编译宏控制
#define CS_P_CUSTOM 0
#define CS_P_UNIX 1
#define CS_P_WINDOWS 2
#define CS_P_ESP32 15
#define CS_P_ESP8266 3
#define CS_P_CC3100 6
#define CS_P_CC3200 4
#define CS_P_CC3220 17
#define CS_P_MSP432 5
#define CS_P_TM4C129 14
#define CS_P_MBED 7
#define CS_P_WINCE 8
#define CS_P_NXP_LPC 13
#define CS_P_NXP_KINETIS 9
#define CS_P_NRF51 12
#define CS_P_NRF52 10
#define CS_P_PIC32 11
#define CS_P_STM32 16
/* Next id: 18 */

/* 
 * 如果没有明确指定,我们通过定义来猜测平台
 */
#ifndef CS_PLATFORM

#if defined(TARGET_IS_MSP432P4XX) || defined(__MSP432P401R__)
#define CS_PLATFORM CS_P_MSP432
#elif defined(cc3200) || defined(TARGET_IS_CC3200)
#define CS_PLATFORM CS_P_CC3200
#elif defined(cc3220) || defined(TARGET_IS_CC3220)
#define CS_PLATFORM CS_P_CC3220
#elif defined(__unix__) || defined(__APPLE__)
#define CS_PLATFORM CS_P_UNIX
#elif defined(WINCE)
#define CS_PLATFORM CS_P_WINCE
#elif defined(_WIN32)
#define CS_PLATFORM CS_P_WINDOWS
#elif defined(__MBED__)
#define CS_PLATFORM CS_P_MBED
#elif defined(__USE_LPCOPEN)
#define CS_PLATFORM CS_P_NXP_LPC
#elif defined(FRDM_K64F) || defined(FREEDOM)
#define CS_PLATFORM CS_P_NXP_KINETIS
#elif defined(PIC32)
#define CS_PLATFORM CS_P_PIC32
#elif defined(ESP_PLATFORM)
#define CS_PLATFORM CS_P_ESP32
#elif defined(ICACHE_FLASH)
#define CS_PLATFORM CS_P_ESP8266
#elif defined(TARGET_IS_TM4C129_RA0) || defined(TARGET_IS_TM4C129_RA1) || \
    defined(TARGET_IS_TM4C129_RA2)
#define CS_PLATFORM CS_P_TM4C129
#elif defined(STM32)
#define CS_PLATFORM CS_P_STM32
#endif

/*如果CS_PLATFORM宏没有定义,则表示当前的特殊平台不在mongoose支持的范围中,也无法测试出来
 *所以报错处理.
 */
#ifndef CS_PLATFORM
#error "CS_PLATFORM is not specified and we couldn't guess it."
#endif
复制代码

比如说,如果当前平台是linux操作系统,则#define CS_PLATFORM CS_P_UNIX, 而CS_P_UNIX宏为: #define CS_P_UNIX 1。 在初始化mongoose(mg_mgr_init)句柄的时候,就会根据CS_PLATFORM 的值而获取对应的初始化函数去初始化mongoose句柄。这个具体细节后面会介绍。


3 Mongoose嵌入到自己项目

     2.2节说过,mongoose.h文件中已经通条件编译宏的方式将其所支持的所有平台都嵌入到了其中,在项目编译的时候,通过操作系统平台内部的宏来判断当前的平台架构,然后初始化宏CS_PLATFORM ,这个宏是整个mongoose的核心,因为后面mgr句柄的初始化操作(包括网络数据接收、发送、销毁句柄等待)都是根据这个宏来选择相应的从初始化函数进行。因此,实际项目开发中,只需要mongoose.c和mongoose.h两个文件就足够了。当然,也可以将mongoose.c文件编译为动态库链接到项目中,至于是mongoose.c源文件嵌入到项目,还是采用动态库的形式,你开心就好。下面我将用最简单、直观的一个小demo来演示怎么在真正的项目开发中来使用这个mongoose。
因为是演示,所以demo很简单,就只有一个 main.c, mongoose.c, mongoose.h和Makefile共四个文件,其中main.c为项目启动的main函数,而Makefile文件中内容则为make命令编译成果物所需的条件。有了Makefile,你就不用每次都按部就班的去执行因此手动gcc …操作,省时省力,可为妙哉。
在这里插入图片描述
图4 mongoose嵌入到开发项目

     main.c文件中代码主要是初始化mongoose句柄,并创建监听端口,开始等待接收连接请求,并响应,然后释放句柄操作。

/** @file  main.c
*  @note   LXG, Ltd. All Right Reserved.
*  @brief  开启mongoose服务
*  @author Li XiaoGang(lixiaogang5@hikvision.com.cn)
*  @date   2019/10/26
*  @note   v1.0.0 Created
*  @history
*  @warning
*/
#include "mongoose.h"

//mongoose服务监听8000端口的连接请求
static const char *s_http_port = "8000";
static void ev_handler(struct mg_connection *c, int ev, void *p) {
	if (ev == MG_EV_HTTP_REQUEST) {
		struct http_message *hm = (struct http_message *) p;

		//We have received an HTTP request. Parsed request is contained in `hm`.
		// Send HTTP reply to the client which shows full original request.
		printf("message:\n%s\n", hm->message.p);

		mg_send_head(c, 200, hm->message.len, "Content-Type: text/plain");
		mg_printf(c, "%.*s[fn:%s] [file:%s] [line:%d]", (int)hm->message.len, hm->message.p,
			__func__, __FILE__, __LINE__);
	}
}

int main(void) {
	struct mg_mgr mgr;
	struct mg_connection *c;
	
	//初始化mongoose句柄
	mg_mgr_init(&mgr, NULL);
	//创建8000端口监听连接
	c = mg_bind(&mgr, s_http_port, ev_handler);
	mg_set_protocol_http_websocket(c);

	for (;;) {
	    //接收连接、消息处理并响应,并关闭相应处理的tcp连接
		mg_mgr_poll(&mgr, 2000*10/** 60*/);
	}
	
	//释放mongoose句柄申请的内存空间资源,避免内存泄漏
	mg_mgr_free(&mgr);
	return 0;
}
复制代码

3.1 开启线程

     默认情况下,是不需要连接其他库的,比如线程库 -lpthread ,尽管UNIX平台编译宏中有#include<pthread.h>,但是并没有被使用,因为宏MG_ENABLE_THREADS被定义会0,所有线程系列函数API不会用到,如下:

#ifndef MG_ENABLE_THREADS /* ifdef-ok */
//如果系统平台为windows,则开启线程,否则,则关闭
#ifdef _WIN32
#define MG_ENABLE_THREADS 1
#else
#define MG_ENABLE_THREADS 0
#endif
复制代码

     Makefile文件中的内容如下所示:

#!/bin/sh
OBJ:=mongoose.o main.o
all:$(OBJ)
	gcc $(OBJ) -o $@
mongoose.o: mongoose.c mongoose.h

main.o: main.c mongoose.c


.PHONY:clean
clean:
	-$(RM) $(OBJ) all
复制代码

     其中all是我们编译后的最终成果物。

3.2 编译成果物

在main.c函数的当前目录下,直接键入: make,便可得到all成果物
在这里插入图片描述

3.3 执行成功物

     ./all 或是以后台的方式(./all &)启动all进程,之后8000端口就开启监听来自网络的请求该端口的连接并进程处理响应。
在这里插入图片描述
图5 启动mongoose服务,并监听8000端口

3.3.1 开启Debug模式

     从另一个终端窗口可以看到,all进程已经起来,并且8000端口开始监听。注意:默认情况下,mongoose的日志是被宏禁掉了的,如果想要其打印日志信息,则需要开启日志打印宏(#define CS_ENABLE_STDIO 1)。

#ifndef CS_COMMON_CS_DBG_H_
#define CS_COMMON_CS_DBG_H_

/* Amalgamated: #include "common/platform.h" */

#if CS_ENABLE_STDIO
#include <stdio.h>
#endif

#ifndef CS_ENABLE_DEBUG
#define CS_ENABLE_DEBUG 0  //开启日志打印宏------>1
#endif

#ifndef CS_LOG_PREFIX_LEN
#define CS_LOG_PREFIX_LEN 24
#endif

#ifndef CS_LOG_ENABLE_TS_DIFF
#define CS_LOG_ENABLE_TS_DIFF 0
#endif
复制代码

3.4 web浏览器下发请求

在这里插入图片描述
浏览器输入ip地址处,需填写你mongoose服务器所跑设备的具体IP地址。可以看得,mongoose服务器成功收到了来自web浏览器的请求,并将其请求报文内容响应给了web请求浏览器。

4 总结

     至此,mongoose的基础篇已讲解结束,其中主要讲解了如何将mongoose服务嵌入到自己的开发项目中的具体详细流程。欢迎留言交流,相互学习,共同进步。如有失误之处,还望耐心赐教。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享