[C + +] - Introduction to mongoose Network Library

Posted by iloveny on Thu, 03 Feb 2022 20:04:13 +0100


Mongoose is a C language network library, which implements event driven non blocking APIs for TCP, UDP, HTTP, WebSocket, CoAP and MQTT.

Mongoose Library

Mongoose is a famous embedded network programming library( https://github.com/cesanta/mongoose ); With only a small amount of static and runtime space, it realizes:

  • Ordinary TCP, ordinary UDP, SSL/TLS (one-way or two-way), client and server.
  • http client and server.
  • WebSocket client and server.
  • MQTT clients and servers.
  • CoAP client and server.
  • DNS clients and servers.
  • Asynchronous DNS resolver.

Design concept

Mongoose has three basic data structures:

struct mg_mgr;///Event manager, save all active links
struct mg_connection;///Describe a link
struct mbuf;///Data received and sent

Use mg for each link_ Connection. A connection can be:

  • Outbound link: by calling mg_connect() is generated;
  • Listening link: by calling mg_ Bind generation;
  • Inbound link: the link received by the listening link;

Mongoose application should follow the event driven mode through mg_mgr_poll() traverses all sockets, accepts new links, sends, receives data, and closes links; And call the event handler function for each event.

struct mg_mgr mgr;
mg_mgr_init(&mgr, NULL);	// Create and initialize event manager
struct mg_connection *c = mg_bind(&mgr, "80", ev_handler_function);
mg_set_protocol_http_websocket(c);	//create link
for (;;) {	//By calling mg_mgr_pool() creates an event loop
   mg_mgr_poll(&mgr, 1000);
}

buffer

Each connection has a send and receive buffer:

  • mg_connection::send_mbuf: add the received data to recv_ After MBUF and trigger an MG_EV_RECV events;
  • mg_connection::recv_mbuf: user sends data (mg_send() or Mg)_ Printf()), the output function appends the data to send_mbuf; After the data is successfully written to the socket, the data in the buffer is discarded and an Mg is triggered_ EV_ Send event;

After the connection is closed, an Mg is triggered_ EV_ Close event.

Event handler

Each link has its related event handling function, which is a key element of data processing.

static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
  switch (ev) {
    /* Event handler code that defines behavior of the connection */
    ...
  }
}

Parameter Description:

  • mg_connection *nc: the connection that triggers the event; Void * user inside_ Data field, which can save user-defined information;
  • int ev: event number;
  • void *ev_data: event data pointer (different events have different meanings)

event

Mongoose accepts incoming connections, reads and writes data, and triggers the corresponding specified events at an appropriate time:

  • Outbound connection: Mg_ EV_ CONNECT -> (MG_EV_RECV, MG_EV_SEND, MG_EV_POLL …) -> MG_ EV_ CLOSE
  • Inbound connection: Mg_ EV_ ACCEPT -> (MG_EV_RECV, MG_EV_SEND, MG_EV_POLL …) -> MG_ EV_ CLOSE

Core event description:

  • MG_EV_ACCEPT: new connection received, void *ev_data is the union socket at the remote end_ address.
  • MG_EV_CONNECT: when mg_ When connect() creates a new outbound link (whether the connection is successful or not), void *ev_data is int *success; When success is 0, the connection has been established. Otherwise, it contains an error code (mg_connect_opt() function to view the error code).
  • MG_EV_RECV: receive data and append to recv_ Triggered on MBUF. void *ev_data is int *num_received_bytes (length of data received). Generally, the event handler function should pass NC - > recv_ The MBUF checks the received data and passes the mbuf_remove() discards the processed data (it is the user's responsibility to discard the processed data from the receive buffer).
  • MG_EV_SEND: the data has been written to the socket and has been discarded_ connection::send_ MBUF data; void *ev_data is int *num_sent_bytes.
  • MG_EV_POLL: mg per call_ mgr_ Triggered when poll(). This event is used to do anything, such as checking whether a timeout has expired and closing the connection or sending a heartbeat message.
  • MG_EV_TIMER: when mg_ set_ Triggered after the timer() call.

Connect flags

Each link has a flags bit field.

List of link flags set by the event handler:

  • MG_F_FINISHED_SENDING_DATA: Tell mongoose that all data has been appended to send_mbuf, as long as mongoose writes data to the socket, the link will be closed.
  • MG_F_BUFFER_BUT_DONT_SEND: Tell mongoose to append data to send_mbuf, but do not send the data immediately, because the data will be modified later. Then by clearing mg_ F_ BUFFER_ BUT_ DONT_ The send flag sends data.
  • MG_F_CLOSE_IMMEDIATELY: Tell mongoose to close the link immediately. This event is usually sent after an error occurs.
  • MG_USER_1,MG_USER_2,MG_USER_3,MG_USER_4: Developers can use it to store the status of specific applications

flags set by mongoose:

  • MG_F_SSL_HANDSHAKE_DONE: set when ssl handshake is completed;
  • MG_F_CONNECTING: in mg_ After the connect() call, the link is in the link state, but the setting is not completed;
  • MG_F_LISTENING: listening;
  • MG_F_UDP: set when the link is udp;
  • MG_F_WEBSOCKET: set when the link is websocket;
  • MG_F_WEBSOCKET_NO_DEFRAG: this flag is set by the user if the user wants to turn off the automatic frame defragmentation function of websocket.

Http example

The main message formats in the HTTP request are:

struct http_message {
    struct mg_str message; /* Whole message: request line + headers + body */
    struct mg_str body;    /* Message body. 0-length for requests with no body */

    /* HTTP Request line (or HTTP response line) */
    struct mg_str method; /* "GET" */
    struct mg_str uri;    /* "/foo/bar" */
    struct mg_str proto;  /* "HTTP/1.1" -- for both request and response */

    /* For responses, code and response status message are set */
    int resp_code;
    struct mg_str resp_status_msg;

    /*
     * Query-string part of the URI. For example, for HTTP request
     *    GET /foo/bar?param1=val1&param2=val2
     *       |   uri  |     query_string     |
     *
     * Note that question mark character doesn't belong neither to the uri,
     * nor to the query_string
     */
    struct mg_str query_string;

    /* Headers: Up to 40 by default, which can be modified as needed */
    struct mg_str header_names[MG_MAX_HTTP_HEADERS];
    struct mg_str header_values[MG_MAX_HTTP_HEADERS];
};

RESTful Server

For Http requests, MG_EV_HTTP_MSG is the event triggered when a message is received.

mg_ Http_ The message stored in the message is the message requested by Http, which is passed through mg_http_reply to answer Http requests.

std::string getMgStr(struct mg_str str) {
    return std::string(str.ptr, str.len);
}

// We use the same event handler function for HTTP and HTTPS connections
// fn_data is NULL for plain HTTP, and non-NULL for HTTPS
static void funCallback(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
    std::cout << "Connect from: " << c->peer.ip << ":" << c->peer.port << ", type: " << ev << std::endl;

    if (ev == MG_EV_ACCEPT && fn_data != NULL) {
        struct mg_tls_opts opts = {
                //.ca = "ca.pem",         // Uncomment to enable two-way SSL
                .cert = "server.pem",    // Certificate PEM file
                .certkey = "server.pem", // This pem contains both cert and key
        };
        mg_tls_init(c, &opts);
    } else if (ev == MG_EV_HTTP_MSG) {
        struct mg_http_message *hm = (struct mg_http_message *) ev_data;

        std::cout << "Http request: Method=" << getMgStr(hm->method) << ", URI=" << getMgStr(hm->uri)
                  << ", query=" << getMgStr(hm->query) << ", proto=" << getMgStr(hm->proto) << std::endl;
        std::cout << "Request body: " << getMgStr(hm->body) << std::endl;
        if (mg_vcmp(&hm->uri, "/api/f1") == 0) {// Serve REST
            json jData = json::parse(hm->body.ptr, hm->body.ptr + hm->body.len);
            std::cout << "##iterate data\n";
            for (auto it:jData) {
                std::cout << "value: " << it << '\n';
            }
            std::cout << "##iterate items\n";
            for (auto &el:jData.items()) {
                std::cout << "Key: " << el.key() << ", value: " << el.value() << '\n';
            }
            mg_http_reply(c, 200, "", "{\"result\": %d}\n", 123);
        } else if (mg_http_match_uri(hm, "/api/f2/*")) {
            mg_http_reply(c, 200, "", "{\"result\": \"%.*s\"}\n", (int) hm->uri.len, hm->uri.ptr);
        } else {
            // struct mg_http_serve_opts opts = {.root_dir = s_root_dir};
            // mg_http_serve_dir(c, ev_data, &opts);

            mg_http_reply(c, 200, "", "Unknown request\n"); // Serve REST
        }
    }
    (void) fn_data;
}

void startHttpServer() {
    struct mg_mgr mgr;       // Event manager
    mg_log_set("2");    	 // Set to 3 to enable debug
    mg_mgr_init(&mgr);       // Initialise event manager
    mg_http_listen(&mgr, s_http_addr, funCallback, NULL);   // Create HTTP listener
//    mg_http_listen(&mgr, s_https_addr, funCallback, (void *) 1); // HTTPS listener
    for (;;)
        mg_mgr_poll(&mgr, 1000); // Infinite event loop
    mg_mgr_free(&mgr);
}

Topics: C++ Mongoose network