Mongoose is known as one of the best embedded web servers currently available. From starting as a single-click-to-run executable to now being an embedded networking library. Read on to find out how it all started, where we are today and the basic concepts you need to understand before using Mongoose.

How Mongoose got started

Mongoose development started in 2004. It was initially designed to be a single-click-to-run executable for multiple platforms, - at the time those were *BSD, Linux, and Windows. Mongoose was then actually called SHTTPD and was hosted on Sourceforge. Later on, the project was renamed to Mongoose, moved to GoogleCode and, eventually to GitHub.

Mongoose is small, cross platform, with no dependencies and easily embeddable. These features made it  gain popularity in the embedded space. Now, it’s one of the most popular products in the space; backed by a dedicated team of professionals that develop Mongoose further and provide support and service.

Mongoose - More than an embedded web server

What started as an embedded web server evolved over time as more features were added to it. Today, it’s not just a web server, it’s a networking library with built-in support of a variety of protocols:

  • HTTP
  • WebSocket
  • MQTT
  • CoAP
  • DNS
  • plain TCP and UDP
  • Asynchronous DNS resolver.

For each protocol, both client and server APIs are present, which makes Mongoose a swiss army knife for embedded network programming.

Getting started

Now let me jump to the basic Mongoose concepts. There are two core data structures: event manager data structure (struct mg_mgr) and connection data structure (struct mg_connection). By the way, all Mongoose API functions and data structures have mg_ prefix, and constants have capital MG_ prefix.

A manager essentially is just a linked list of all current connections. Connections can be either listening (server connection), inbound (those accepted by server connections) or outbound (initiated from our side).

A program structure that Mongoose expects is quite simple: first, a manager should be initialised. Then, either an outbound or server connection should be added to the manager. Then, user code must have a loop which calls mg_mgr_poll() function. This loop is called an event loop.

Why is that? Every connection has few important pieces associated with it. First, there are IO buffers - for incoming and outgoing data. And second, a function that is called at certain points. Those “certain points” are called “events” and a function is called an “event handler”. API functions that create connections expect event handler functions as a parameter. An event loop, in particular, mg_mgr_poll() function, checks every connection for the availability of new data from remote peers, and also it sends any pending outgoing data to the remote peers.

Let’s put those pieces together with one of the simplest servers, a TCP echo server. This server accepts connections from the network, and echoes back any data that client has sent:

include "mongoose.h"

static void ev_handler(struct mg_connection *nc, int ev, void *p) {
struct mbuf *io = &nc->recv_mbuf;
(void) p;
switch (ev) {
case MG_EV_RECV:
mg_send(nc, io->buf, io->len); // Echo message back
mbuf_remove(io, io->len); // Discard message from recv buffer
break;
default:
break;
}
}
int main(void) {
struct mg_mgr mgr;
const char *port = "1234";
mg_mgr_init(&mgr, NULL);
mg_bind(&mgr, port, ev_handler);
printf("Starting echo mgr on port %s\n", port);
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
return 0;
}

  1. #include "mongoose.h" line includes Mongoose API definitions.
  2. Initialising event manager.
  3. Create listening connection
  4. Create event loop
  5. In the event handler, when MG_EV_RECV event is triggered, copy all received data from “receive” IO buffer to the “send” IO buffer, and clean up “receive” IO buffer

In the beginning, a manager has only one connection, a listening connection (connectionA). At some point, a client connects, and mg_mg_poll() will notice that and will create a new inbound connection (connection B). Thus, there will be two connections in the manager. The listening connection will continue to listen for new incoming connections. When the client sends some data, mg_mg_poll() will notice that, copy incoming data to the “receive” IO buffer of the inbound connection (connectionB) and call the event handler with three parameters: connectionB, MG_EV_RECV and number of bytes received.

We can see that an event handler is called mg_mgr_poll() function with three parameters: a connection, an event number and event data. In that particular case, the MG_EV_RECV event is triggered when Mongoose receives data from the remote peer and copies that data to the “receive” IO buffer.

Plain TCP and UDP protocol handlers can trigger following events:

  • MG_EV_ACCEPT
  • MG_EV_CONNECT
  • MG_EV_RECV
  • MG_EV_SEND
  • MG_EV_POLL
  • MG_EV_TIMER

For each event, the meaning of the third parameter, “ev_data” is different. For example, for MG_EV_CONNECT “ev_data” contains a connection error value. If it is 0, then the connection was successful. Event handlers for other protocols, for example, HTTP, trigger other events. More on that in other tutorials.

To contact: send us a message or ask on the developer forum.