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([email protected])
*  @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;
}

     默認情況下,是不需要連接其他庫的,比如線程庫 -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.1 編譯成果物

在main.c函數的當前目錄下,直接鍵入: make,便可得到all成果物
在這裏插入圖片描述

3.2 執行成功物

     ./all 或是以後臺的方式(./all &)啓動all進程,之後8000端口就開啓監聽來自網絡的請求該端口的連接並進程處理響應。
在這裏插入圖片描述
                           圖5 啓動mongoose服務,並監聽8000端口

     從另一個終端窗口可以看到,all進程已經起來,並且8000端口開始監聽。注意:默認情況下,mongoose的日誌是被宏禁掉了的,如果想要其打印日誌信息,則需要開啓日誌打印宏(#define CS_ENABLE_STDIO 1

#ifndef CS_COMMON_PLATFORMS_PLATFORM_UNIX_H_
#define CS_COMMON_PLATFORMS_PLATFORM_UNIX_H_
#if CS_PLATFORM == CS_P_UNIX

... 
/*
 * mongoose.h頭文件中,如果CS_PLATFORM == CS_P_UNIX,則表示當前的平臺架構是類UNIX, 而在該模塊內部,
 * 默認是不允許向終端除數日誌打印信息的,CS_ENABLE_STDIO 宏被定義爲0,若需要開啓日誌打印提示.則將其
 * 修改爲:#define CS_ENABLE_STDIO 1  
 * /
#ifndef CS_ENABLE_STDIO
#define CS_ENABLE_STDIO 0  
#endif
...

#endif /* CS_PLATFORM == CS_P_UNIX */
#endif /* CS_COMMON_PLATFORMS_PLATFORM_UNIX_H_ */

3.3 web瀏覽器下發請求

在這裏插入圖片描述
     瀏覽器輸入ip地址處,需填寫你mongoose服務器所跑設備的具體IP地址。可以看得,mongoose服務器成功收到了來自web瀏覽器的請求,並將其請求報文內容響應給了web請求瀏覽器。

4 總結

     至此,mongoose的基礎篇已講解結束,其中主要講解了如何將mongoose服務嵌入到自己的開發項目中的具體詳細流程。歡迎留言交流,相互學習,共同進步。如有失誤之處,還望耐心賜教。
     







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章