Mongoose programming model
Mongoose uses a simple event-driven networking model. Instead of threads, blocking sockets, or complex frameworks, the application runs a small event loop that processes network activity and calls your code when something interesting happens.
The core idea is very simple:
event loop -> network activity -> event handler -> your application logic
Your application creates an event manager, opens network connections, and repeatedly polls the manager. During polling, Mongoose processes sockets, parses protocols like HTTP, WebSocket, MQTT, and generates events. Those events are delivered by calling a user-defined event handler function.
Core objects
The programming model revolves around two structures.
- struct mg_mgr - the event manager
- struct mg_connection - a network connection
struct mg_mgr stores all active connections. The application calls mg_mgr_poll() in a loop to process network traffic.
struct mg_connection represents a TCP, UDP, HTTP, WebSocket, or MQTT connection. Each connection has an associated event handler.
Event loop
Every Mongoose application runs an event loop. It repeatedly calls mg_mgr_poll().
struct mg_mgr mgr;
mg_mgr_init(&mgr);
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
During polling Mongoose:
- reads incoming data
- writes outgoing data
- parses protocol messages
- generates events
When an event happens, Mongoose calls the connection's event handler.
Event handler
The event handler is the place where application logic lives. It receives events such as connection open, HTTP request, WebSocket frame, MQTT message, or connection close.
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
mg_http_reply(c, 200, "", "hello\n");
}
}
The handler inspects the event type and reacts accordingly.
Typical flow
A minimal Mongoose server usually looks like this:
- initialise event manager
- create a listening connection
- run event loop
- handle events
Example:
#include "mongoose.h"
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
mg_http_reply(c, 200, "", "hello\n");
}
}
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://0.0.0.0:8000", http_ev_handler, NULL);
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
return 0;
}
Why this model works well for embedded devices
The event-driven model keeps the code small and efficient. There are no threads, no blocking calls, and memory usage is predictable. A single event loop can handle many connections at once, which is ideal for microcontrollers with limited RAM and CPU.
This model also simplifies networking code. Instead of managing multiple sockets, threads, or state machines, the application reacts to events generated by the networking stack.
Mongoose uses an event-driven architecture that differs from the traditional BSD socket API. With sockets, applications usually rely on blocking calls, polling sets of file descriptors, or managing multiple threads. In Mongoose, the library performs network I/O internally and delivers high-level events to your handler. If you want to understand this difference in detail, read the Mongoose architecture concept article.
Because of this design, Mongoose works well on microcontrollers such as STM32, ESP32, RP2040, and NXP devices, as well as on embedded Linux systems.
Once you understand the event loop and the event handler model, everything in Mongoose becomes straightforward: create connections, run the event loop, and react to events.