Detailed use of mongoose — how to build a server through mongoose

Preface

It is better to teach people to fish than to teach people to fish. This article introduces in detail how to quickly understand and get started with mongoose for a rookie who has never heard of mongoose

Some other open source libraries can learn with a similar approach

Tools that need to be prepared in advance

1. Official website documentation Mongoose :: Documentation

The official website provides many examples to explain. This article mainly explains HTTP server/client and webSocket Server/client.

2. Download the source code GitHub – cesanta/mongoose: Embedded Web Server

Download the latest version of the source code. This article mainly uses the examples in the source code to quickly understand how to use mongoose, and uses the documents provided by the official website as auxiliary tools.

3. Tools for reading source code (Source Insight is recommended here)

4. Development tools (IDE, this article uses VS2017, please choose according to your own development environment)

View source code through source insight

For how to use source insight, there is basically no big problem after installing the steps in the screenshot below. Here I also share the tool with you: source insight4.0 cracked version

Opened overall page number:

Introduction to mongoose

According to the description given by the official website document, it is roughly summarized as follows:

1. mongoose is a network library for C/C++, which implements event-driven non-blocking API for TCP, UDP, HTTP, WebSocket, MQTT. Mongoose makes embedded programming fast, robust, and easy.

2. mongoose can run on windows, Linux, Mac and many embedded architectures. It can run on existing operating systems and TCP/IP stacks such as FreeRTOS and lwIP, or on bare metal, utilizing Mongoose’s built-in TCP/IP stack and network drivers.

How to use mongoose

This part of the official website provides instructions. According to the instructions in this part of the 2-minute integration guide, we can guess:

In order to integrate Mongoose into an existing C/C++ application or firmware, use the following steps:

1. Copy mongoose.c and mongoose.h into the source code

It is very simple to use, you only need to add mongoose.c and mongoose.h to the source code to use it smoothly, no need to use cmake and vs compilation, fairy experience.

Analyze http-server

Let me talk about why it is recommended to look at the examples in the source code instead of the cases in other people’s blogs. Version issues, different versions may use different methods, and different versions support function sources may have some differences.

How to look at the source code: subject-predicate-object, the verb is the most important, that is to say, the function is the most important (a function is an action)

The important functions are extracted first:

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);

Let’s look at the function prototype:

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

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

It can be guessed that a control method should be added to a certain signal: signal(SIGINT, signal_handler);

Do a processing action for the signal SIGINT, which is triggered when SIGINT is ctrl + C

Let’s look at the signal capture function of signal_handler in the source code:

typedef void (__CRTDECL* _crt_signal_t)(int);

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

mg_log_set(s_debug_level);

Set the log level, if there is no comment in the source code, you can check the documentation on the official website

Here should be to set the log to INFO and ERROR level

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

mg_mgr_init( &mgr);

Function prototype:

void mg_mgr_init(struct mg_mgr *mgr)

According to the document description, it can be known that he is initialized for the event manager mg_mgr, and what the initialization work does:

1. Set the list of active connections to NULL

2. Set the default DNS server to IPv4 and IPv6

3. Set the default DNS lookup timeout

What is mg_mgr?

An event management structure, containing a list of active connections as well as some housekeeping information

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

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

Function prototype:

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

Create an HTTP service

parameter:

mgr: an event manager

url: add a local IP and listening port, http://0.0.0.0:8000

fn: Callback function, when there is a monitoring connection coming in, the callback function will be executed

fn_data: parameters passed to the callback function

Return Value: pointer to the created connection, or NULL on error

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

mg_casecmp(s_enable_hexdump, “yes”)

Function prototype:

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

Compares two NULL-terminated strings case-insensitively

Parameters: s1, s2 pointers to these two strings

Return value: If it returns 0, it means that the two strings are equal, if it is greater than 0, then s1>s2, if it is less than 0, then s1

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

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

Function prototype:

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

write log

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

mg_mgr_poll( & mgr, 1000);

Function prototype:

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

Execute polling iterations for each connection in the listening list

1. Check if there is incoming data, if so, read it into the read buffer, and send the MG_EV_READ event

2. Check whether there is data in the write buffer, and write, send MG_EV_WRITE event

3. If the connection is listening, accept the incoming connection (if any), and send it the MG_EV_accept event

4. Send MG_EV_POLL event

parameter:

mgr: an event manager

ms: timeout time (milliseconds)

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

mg_mgr_free( &mgr);

Function prototype:

void mg_mgr_free(struct mg_mgr *mgr);

Close all connections and release all resources

Combined with the above description, we make a summary:

We will not explore the log part here, just skip it.

1. We need to prepare an event manager, there are two steps:

struct mg_mgr mgr;

mg_mgr_init( &mgr);

2. Then set the listening event for this event manager and specify the IP and PORT:

Prepare a callback function ==> We will explore the interior of the callback function later

Prepare the specified IP and port

mg_http_listen();

3. Call the message loop

mg_mgr_poll(); // Specify the duration of blocking

4. Stop the loop and release resources

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) {
\t
if (ev == MG_EV_HTTP_MSG) {
printf("Data has arrived\\
");
}

(void)fn_data;
}


int main(int argc, char *argv[]) {

struct mg_mgr mgr;
struct mg_connection *c;
\t
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);
\t
return 0;
}

The client can use postman to test, and I will not write the client code here.

HTTP SERVER-v2.0

Now let’s analyze the callback function:

The function prototype, mongoose implements the callback through the function pointer, and its principle is the same as the signal capture function.

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

The official document gives an explanation of each member in the structure that we often use: POST request and GET request

// The difference is a POST request or a GET request
if (strstr(hm->method.ptr, "POST"))
{
printf("This is a POST request\\
");
mg_http_reply(c, 200, "Content-Type: application/json\r\\
", "{%s:%s}", "status", "okk");
}
else if(strstr(hm->method.ptr, "GET"))
{
printf("This is a GET request\\
");
mg_http_reply(c, 200, "Content-Type: application/json\r\\
", "{%s:%s}", "status", "okk");
}
else
{
mg_http_reply(c, 500, NULL, "{%s:%s}", "status", "GET request has been received");
}

Sometimes it will be processed differently according to the uri: /hello

if (mg_http_match_uri(hm, "/hello"))
{
    if (strstr(hm->method.ptr, "POST"))
    {
printf("This is a POST request\\
");
        mg_http_reply(c, 200, "Content-Type: application/json\r\\
", "{%s:%s}", "status", "okk");
    }
else if (strstr(hm->method.ptr, "GET"))
    {
printf("This is a GET request\\
");
mg_http_reply(c, 200, "Content-Type: application/json\r\\
", "{%s:%s}", "status", "okk");
}
else
{
mg_http_reply(c, 500, NULL, "{%s:%s}", "status", "GET request has been received");
}
}
else if (mg_http_match_uri(hm, "/app"))
{
printf("------------------\\
");
}

mongoose provides several different ways to send messages:

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

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

The official website gives a lot of functions, which are all about HTTP, and the official website has a detailed introduction.

Not all of them are explained here.

HTTP-CLIENT

In mongoose’s HTTP client, we automatically send the connection internally, because the data may be more complicated. A MG_EV_CONNECT event will be sent when the connection is established, and we process the event in the callback function.

The client code is basically the same as the server, the only difference is that the connection and listening connection are different, and the others are basically the same

#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";

// connection event
if (MG_EV_CONNECT == ev)
{
struct mg_str host = mg_url_host(s_url); // resolve the host
\t\t
\t\t// send request
mg_printf(c, "%s %s HTTP/1.1\r\\
"
"Host:%.*s\r\\
"
"\r\\
"
"%.*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);
}

// received the response
if (MG_EV_HTTP_MSG == ev)
{
struct mg_str host = mg_url_host(s_url); // resolve the host

struct mg_http_message *hm = (struct mg_http_message *)ev_data;
printf("%s", hm->message);

mg_printf(c, "%s %s HTTP/1.1\r\\
"
"Host:%.*s\r\\
"
"\r\\
"
"%.*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. Create a
struct mg_mgr mgr;
mg_mgr_init( &mgr);
    
// 2. Connect to the HTTP server
mg_http_connect( & amp; mgr, "127.0.0.1:8080", cb, NULL);

while (1)
{
// message loop
mg_mgr_poll( & mgr, 1000);
}

// Release resources
mg_mgr_free( &mgr);

return 0;
}

WEBSOCKET-SERVER

Many friends may not know much about the webSocket protocol, here is a brief introduction:

webSocket protocol

Essentially the same type as the HTTP protocol, webSocket is a full-duplex communication protocol and a new communication protocol proposed based on the shortcomings of the HTTP protocol.

HTTP is a one-way application layer protocol, which adopts a request-response model. Communication requests can only be initiated by the client, and the server responds to the request. The disadvantages of this are obviously great. As long as the server status changes continuously, the client must respond in real time, which is obviously very troublesome. At the same time, the efficiency of polling is low, which is a waste of resources.

webSocket is a network technology for full-duplex communication. Any party can establish a connection to push data to the other party. webSocket only needs to establish a connection once and it can be maintained forever.
Mongoose’s processing of webSocket

The processing of mongoose is very simple, and it is still processed through HTTP, but an MG_EV_WS_MSG event will be sent in the callback function, and the data will be converted into ws data for processing through mg_ws_upgrade(). When mg_ws_upgrade is called, an MG_EV_WS_MSG event will be sent.

#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( & amp; mgr); // Initialize event manager
\t
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;
}