.. | ||
README.md |
Overview
Introduction
Mongoose is a networking library for C/C++. It implements event-driven non-blocking APIs for TCP, UDP, HTTP, WebSocket, MQTT. It has been designed for connecting devices and bringing them online. On the market since 2004, used by vast number of open source and commercial products - it even runs on the International Space Station! Mongoose makes embedded network programming fast, robust, and easy.
Features
- Cross-platform: works on Linux/UNIX, MacOS, QNX, eCos, Windows, Android, iPhone, FreeRTOS, etc
- Supported hardware platforms: TI CC3200, TI MSP432, NRF52, STM32, PIC32, ESP8266, ESP32 and more
- Builtin protocols:
- plain TCP, plain UDP, SSL/TLS (over TCP, one-way or two-way)
- HTTP client, HTTP server
- WebSocket client, WebSocket server
- MQTT client
- DNS client, async DNS resolver
- Single-threaded, asynchronous, non-blocking core with simple event-based API
- Native support for LWIP embedded TCP/IP stack
- Tiny static and run-time footprint
- Source code is both ISO C and ISO C++ compliant
- Very easy to integrate: just copy mongoose.c and mongoose.h files to your build tree
Concept
Mongoose has three basic data structures:
struct mg_mgr
- an event manager that holds all active connectionsstruct mg_connection
- describes a connectionstruct mg_iobuf
- describes data buffer (received or sent data)
Connections could be either listening, outbound or inbound. Outbound
connections are created by the mg_connect()
call. Listening connections are
created by the mg_listen()
call. Inbound connections are those accepted by a
listening connection. Each connection is described by a struct mg_connection
structure, which has a number of fields. All fields are exposed to the
application by design, to give an application a full visibility into the
Mongoose's internals.
An application that uses mongoose should follow a standard pattern of event-driven application:
- Declare and initialise an event manager:
struct mg_mgr mgr;
mg_mgr_init(&mgr);
- Create connections. For example, a server application should create listening connections. When any connection is created (listening or outgoing), an event handler function must be specified. An event handler function defines connection's behavior.
struct mg_connection *c = mg_http_listen(&mgr, "0.0.0.0:8000", fn, arg);
- Create an event loop by calling
mg_mgr_poll()
:
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_poll()
iterates over all sockets, accepts new connections, sends and
receives data, closes connections and calls event handler functions for the
respective events.
Since the Mongoose's core is not protected against the concurrent access,
make sure that all mg_*
API functions are called from the same thread
or RTOS task.
Send and receive buffers
Each connection has a send and receive buffer:
struct mg_connection::send
- data to be sent to a peerstruct mg_connection::recv
- data received from a peer
When data arrives, Mongoose appends received data to the recv
and triggers an
MG_EV_RECV
event. The user may send data back by calling one of the output
functions, like mg_send()
or mg_printf()
. Output functions append data to
the send
buffer. When Mongoose successfully writes data to the socket, it
discards data from struct mg_connection::send
and sends an MG_EV_SEND
event.
Event handler function
Each connection has an event handler function associated with it. That function must be implemented by the user. Event handler is the key element of the Mongoose application, since it defines the connection's behaviour. This is what an event handler function looks like:
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
switch (ev) {
/* Event handler code that defines behavior of the connection */
...
}
}
struct mg_connection *c
- a connection that received an eventint ev
- an event number, defined in mongoose.h. For example, when data arrives on an inbound connection, ev would beMG_EV_RECV
.void *ev_data
- points to the event-specific data, and it has a different meaning for different events. For example, for anMG_EV_RECV
event,ev_data
is anint *
pointing to the number of bytes received from a remote peer and saved into thec->recv
IO buffer. The exact meaning ofev_data
is described for each event. Protocol-specific events usually haveev_data
pointing to structures that hold protocol-specific information.void *fn_data
- a user-defined pointer for the connection, which is a placeholder for application-specific data. Mongoose does not use that pointer. An event handler can store any kind of information there
Events
Below is the list of events trigged by Mongoose, taken as-is from mongoose.h
.
For each event, a comment describes a meaning of the ev_data
pointer passed
to an event handler:
enum {
MG_EV_ERROR, // Error char *error_message
MG_EV_POLL, // mg_mgr_poll iteration unsigned long *millis
MG_EV_RESOLVE, // Host name is resolved NULL
MG_EV_CONNECT, // Connection established NULL
MG_EV_ACCEPT, // Connection accepted NULL
MG_EV_READ, // Data received from socket struct mg_str *
MG_EV_WRITE, // Data written to socket int *num_bytes_written
MG_EV_CLOSE, // Connection closed NULL
MG_EV_HTTP_MSG, // HTTP request/response struct mg_http_message *
MG_EV_WS_OPEN, // Websocket handshake done NULL
MG_EV_WS_MSG, // Websocket message received struct mg_ws_message *
MG_EV_MQTT_MSG, // MQTT message struct mg_mqtt_message *
MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code
MG_EV_SNTP_TIME, // SNTP time received struct timeval *
MG_EV_USER, // Starting ID for user events
};
Connection flags
struct mg_connection
has a bitfield with connection flags. Flags are binary,
they can be either 0 or 1. Some flags are set by Mongoose and must be not
changed by an application code, for example is_udp
flag tells application if
that connection is UDP or not. Some flags can be changed by application, for
example, is_drainig
flag, if set by an application, tells Mongoose to send
the remaining data to peer, and when everything is sent, close the connection.
User-changeable flags are: is_hexdumping
, is_drainig
, is_closing
.
This is taken from mongoose.h
as-is:
struct mg_connection {
...
unsigned is_listening : 1; // Listening connection
unsigned is_client : 1; // Outbound (client) connection
unsigned is_accepted : 1; // Accepted (server) connection
unsigned is_resolving : 1; // Non-blocking DNS resolv is in progress
unsigned is_connecting : 1; // Non-blocking connect is in progress
unsigned is_tls : 1; // TLS-enabled connection
unsigned is_tls_hs : 1; // TLS handshake is in progress
unsigned is_udp : 1; // UDP connection
unsigned is_websocket : 1; // WebSocket connection
unsigned is_hexdumping : 1; // Hexdump in/out traffic
unsigned is_draining : 1; // Send remaining data, then close and free
unsigned is_closing : 1; // Close and free the connection immediately
unsigned is_readable : 1; // Connection is ready to read
unsigned is_writable : 1; // Connection is ready to write
};
Build options
Mongoose source code ships in two files:
- mongoose.h - API definitions
- mongoose.c - implementation
Therefore to integrate Mongoose into an application, simply copy these two files to the application's source tree.
The mongoose.c
and mongoose.h
files are, actually, an amalgamation -
a non-amalgamated sources can be found at https://github.com/cesanta/mongoose/tree/master/src
Mongoose source code uses a bunch of build constants defined at https://github.com/cesanta/mongoose/blob/master/src/config.h, together with their default values.
In order to change the constant during build time, use the -D <PREPROCESSOR_FLAG>
compiler option. For example, to disable both MQTT,
compile the application my_app.c
like this (assumed UNIX system):
$ cc my_app.c mongoose.c -D MG_MQTT_ENABLE=0
Here is a list of build constants and their default values:
Name | Default | Description |
---|---|---|
MG_ENABLE_LWIP |
0 | Use LWIP low-level API instead of BSD sockets |
MG_ENABLE_SOCKET |
1 | Use BSD socket low-level API |
MG_ENABLE_MBEDTLS |
0 | Enable Mbed TLS library |
MG_ENABLE_OPENSSL |
0 | Enable OpenSSL library |
MG_ENABLE_FS |
1 | Enable API that use filesystem, like mg_http_send_file() |
MG_ENABLE_IPV6 |
0 | Enable IPv6 |
MG_ENABLE_LOG |
1 | Enable LOG() macro |
MG_ENABLE_MD5 |
0 | Use native MD5 implementation |
MG_ENABLE_DIRECTORY_LISTING |
0 | Enable directory listing for HTTP server |
MG_ENABLE_HTTP_DEBUG_ENDPOINT |
0 | Enable /debug/info debug URI |
MG_ENABLE_SOCKETPAIR |
0 | Enable mg_socketpair() for multi-threading |
MG_IO_SIZE |
512 | Granularity of the send/recv IO buffer growth |
MG_MAX_RECV_BUF_SIZE |
(3 * 1024 * 1024) | Maximum recv buffer size |
MG_MAX_HTTP_HEADERS |
40 | Maximum number of HTTP headers |
Minimal example
This example is a simple static HTTP server that serves current directory:
#include "mongoose.h"
static const char *s_web_root_dir = ".";
static const char *s_listening_address = "http://localhost:8000";
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) mg_http_serve_dir(c, ev_data, s_web_root_dir);
}
int main(int argc, char *argv[]) {
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, s_listening_address, cb, &mgr);
for (;;) mg_mgr_poll(&mgr, 1000);
mg_mgr_free(&mgr);
return 0;
}