序文
人 に 魚 を 教える より , 人 に 魚 を 教える 方 が よい. この 記事 で は , マングース を 聞い た こと が ない 新人 に マングース を すぐ に 理解 し 始める 方法 を 詳しく 紹介 し ます .
他のいくつかのオープンソース ライブラリは、同様のアプローチで学習できます
事前に準備するツール
1. 公式ウェブサイトのドキュメント Mongoose :: ドキュメンテーション
公式サイトには説明例がたくさんありますが、この記事では主にHTTPサーバー/クライアントとwebSocketサーバー/クライアントについて説明します。
2. GitHub からソース コードをダウンロードします - cesanta/mongoose: Embedded Web Server
ソースコードの最新バージョンをダウンロードする この記事では、主にソースコードの例を使用してマングースの使用方法をすばやく理解し、公式 Web サイトで提供されているドキュメントを補助ツールとして使用します。
3. ソース コードを読み取るためのツール (ここでは Source Insight をお勧めします)
4. 開発ツール (IDE、この記事では VS2017 を使用しています。各自の開発環境に合わせて選択してください)
ソース インサイトを通じてソース コードを表示する
ソース インサイトの使用方法については、下のスクリーンショットの手順をインストールした後、基本的に大きな問題はありません. ここでもツールを共有します:ソース インサイト 4.0 クラック バージョン
全体のページ番号を開く:
マングースの紹介
公式サイトのドキュメントによると、大まかに以下のようにまとめられています。
1. mongoose は C/C++ 用のネットワーク ライブラリであり、TCP、UDP、HTTP、WebSocket、MQTT 用のイベント駆動型ノンブロッキング API を実装します。Mongoose を使用すると、組み込みプログラミングを高速、堅牢、かつ簡単に行うことができます。
2. mongoose は、Windows、Linux、Mac、および多くの組み込みアーキテクチャで実行できます。既存のオペレーティング システムと FreeRTOS や lwIP などの TCP/IP スタック、またはMongoose の組み込み TCP/IP スタックとネットワーク ドライバーを利用するベア メタル上で実行できます。
マングースの使い方
公式ウェブサイトのこの部分には手順が記載されています. 2 分間の統合ガイドのこの部分の手順によると、次のように推測できます。
Mongoose を既存の C/C++ アプリケーションまたはファームウェアに統合するには、次の手順を使用します。
1. mongoose.c と mongoose.h をソース コードにコピーします。
使い方はとても簡単で、mongoose.c と mongoose.h をソース コードに追加するだけでスムーズに使用できます。cmake や vs コンパイル、妖精の経験を使用する必要はありません。
http サーバーを分析する
他の人のブログのケースではなく、ソース コードの例を見ることが推奨される理由について話しましょう。バージョンの問題、異なるバージョンは異なる方法を使用する場合があり、異なるバージョンは関数ソースをサポートするためにいくつかの違いがある場合があります。
ソースコードの見方:主語-述語-目的語、動詞が最重要、つまり関数が最重要(関数はアクション)
重要な機能が最初に抽出されます。
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);
関数プロトタイプを見てみましょう。
1===================
_ACRTIMP _crt_signal_t __cdecl signal(_In_ int _Signal, _In_opt_ _crt_signal_t _Function);
特定のシグナルに制御メソッドを追加する必要があると推測できます。 signal(SIGINT, signal_handler);
SIGINT が ctrl+C のときにトリガーされるシグナル SIGINT の処理アクションを実行します。
ソース コードの signal_handler のシグナル キャプチャ関数を見てみましょう。
typedef void (__CRTDECL* _crt_signal_t)(int);
2===================
mg_log_set(s_debug_level);
ログ レベルを設定します。ソース コードにコメントがない場合は、公式 Web サイトのドキュメントを確認できます。
ログをINFOおよびERRORレベルに設定する必要があります
3=======================
mg_mgr_init(&mgr);
関数プロトタイプ:
void mg_mgr_init(struct mg_mgr *mgr)
ドキュメントの説明によると、彼はイベント マネージャ mg_mgr 用に初期化されていることがわかり、初期化作業の内容は次のとおりです。
1.アクティブな接続のリストをNULLに設定します
2. デフォルトの DNS サーバーを IPv4 および IPv6 に設定します
3. デフォルトの DNS ルックアップ タイムアウトを設定する
mg_mgr とは何ですか?
アクティブな接続のリストといくつかのハウスキーピング情報を含むイベント管理構造
4===============================
mg_http_listen(&mgr, s_listening_address, cb, &mgr)
関数プロトタイプ:
struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data);
HTTP サービスを作成する
パラメータ:
mgr: イベント マネージャー
url: ローカル IP とリスニング ポートを追加します。http://0.0.0.0:8000
fn: コールバック関数。モニタリング接続が入ってくると、コールバック関数が実行されます
fn_data: コールバック関数に渡されるパラメーター
戻り値: 作成された接続へのポインター、またはエラーの場合は NULL
5===============================
mg_casecmp(s_enable_hexdump, "はい")
関数プロトタイプ:
int mg_casecmp(const char *s1, const char *s2);
2 つの NULL で終わる文字列を大文字と小文字を区別せずに比較します
パラメータ: s1、s2 これら 2 つの文字列へのポインタ
戻り値: 0 を返す場合は 2 つの文字列が等しいことを意味し、0 より大きい場合は s1>s2 0 より小さい場合は s1<s2
6===============================
MG_INFO(("マングースのバージョン: v%s", MG_VERSION));
関数プロトタイプ:
#定義 MG_INFO(args) MG_LOG(MG_LL_INFO, args)
ログを書く
7==============================
mg_mgr_poll(&mgr, 1000);
関数プロトタイプ:
void mg_mgr_poll(struct mg_mgr *mgr, int ms);
リスニング リスト内の接続ごとにポーリングの繰り返しを実行する
1. 着信データがあるかどうかを確認し、ある場合はそれを読み取りバッファに読み込み、MG_EV_READ イベントを送信します。
2. 書き込みバッファにデータがあるかどうかを確認し、書き込み、MG_EV_WRITE イベントを送信します。
3. 接続がリッスンしている場合は、着信接続 (存在する場合) を受け入れ、MG_EV_accept イベントを送信します。
4. MG_EV_POLL イベントを送信する
パラメータ:
mgr: イベント マネージャー
ms: タイムアウト時間 (ミリ秒)
8===============================
mg_mgr_free(&mgr);
関数プロトタイプ:
void mg_mgr_free(struct mg_mgr *mgr);
すべての接続を閉じ、すべてのリソースを解放します
上記の説明と組み合わせて、要約を作成します。
ここではログの部分については調べません。スキップしてください。
1. イベント マネージャーを準備する必要があります。次の 2 つの手順があります。
struct mg_mgr mgr;
mg_mgr_init(&mgr);
2. 次に、このイベント マネージャーのリッスン イベントを設定し、IP とポートを指定します。
コールバック関数の準備 ==> コールバック関数の内部については後で調べます
指定したIPとポートを用意
mg_http_listen();
3. メッセージ ループを呼び出す
mg_mgr_poll(); // ブロッキング期間を指定
4. ループを停止してリソースを解放する
mg_mgr_free()
HTTP サーバー - 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) { if (ev == MG_EV_HTTP_MSG) { printf("有数据到达\n"); } (void)fn_data; } int main(int argc, char *argv[]) { struct mg_mgr mgr; struct mg_connection *c; 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); return 0; }
クライアントは postman を使用してテストできますが、ここではクライアント コードを記述しません。
HTTP サーバー - v2.0
それでは、コールバック関数を分析しましょう。
関数プロトタイプである mongoose は、関数ポインターを介してコールバックを実装し、その原理はシグナル キャプチャ関数と同じです。
typedef void (*mg_event_handler_t)(struct mg_connection *, int ev, void *ev_data, void *fn_data);
公式ドキュメントには、私たちがよく使用する構造の各メンバーの説明が記載されています: POST リクエストと GET リクエスト
// 区别是POST请求还是GET请求 if (strstr(hm->method.ptr, "POST")) { printf("这是POST请求\n"); mg_http_reply(c, 200, "Content-Type: application/json\r\n", "{%s:%s}", "status", "okk"); } else if(strstr(hm->method.ptr, "GET")) { printf("这是GET请求\n"); mg_http_reply(c, 200, "Content-Type: application/json\r\n", "{%s:%s}", "status", "okk"); } else { mg_http_reply(c, 500, NULL, "{%s:%s}", "status", "已经收到GET请求"); }
uri: /hello によって処理が異なる場合があります。
if (mg_http_match_uri(hm, "/hello")) { if (strstr(hm->method.ptr, "POST")) { printf("这是POST请求\n"); mg_http_reply(c, 200, "Content-Type: application/json\r\n", "{%s:%s}", "status", "okk"); } else if (strstr(hm->method.ptr, "GET")) { printf("这是GET请求\n"); mg_http_reply(c, 200, "Content-Type: application/json\r\n", "{%s:%s}", "status", "okk"); } else { mg_http_reply(c, 500, NULL, "{%s:%s}", "status", "已经收到GET请求"); } } else if (mg_http_match_uri(hm, "/app")) { printf("------------------\n"); }
mongoose は、メッセージを送信するいくつかの異なる方法を提供します。
int mg_printf(struct mg_connection *, const char *fmt, ...); void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...);
公式 Web サイトには、すべて HTTP に関する多くの機能があり、公式 Web サイトには詳細な紹介があります。
ここですべてを説明するわけではありません。
HTTP クライアント
mongoose の HTTP クライアントでは、データがより複雑になる可能性があるため、接続を内部的に自動的に送信します。接続が確立されると MG_EV_CONNECT イベントが送信され、コールバック関数でイベントを処理します。
クライアント コードは基本的にサーバーと同じです。唯一の違いは、接続とリッスン接続が異なることです。その他は基本的に同じです。
#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"; // 连接事件 if (MG_EV_CONNECT == ev) { struct mg_str host = mg_url_host(s_url); // 解析出主机 // 发送请求 mg_printf(c, "%s %s HTTP/1.1\r\n" "Host:%.*s\r\n" "\r\n" "%.*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); } // 接受到回应 if (MG_EV_HTTP_MSG == ev) { struct mg_str host = mg_url_host(s_url); // 解析出主机 struct mg_http_message *hm = (struct mg_http_message *)ev_data; printf("%s", hm->message); mg_printf(c, "%s %s HTTP/1.1\r\n" "Host:%.*s\r\n" "\r\n" "%.*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.创建一个 struct mg_mgr mgr; mg_mgr_init(&mgr); // 2.连接HTTP服务器 mg_http_connect(&mgr, "127.0.0.1:8080", cb, NULL); while (1) { // 消息循环 mg_mgr_poll(&mgr, 1000); } // 释放资源 mg_mgr_free(&mgr); return 0; }
WEBSOCKET-SERVER
多くの友人は webSocket プロトコルについてあまり知らないかもしれません。簡単な紹介を次に示します。
webSocket は HTTP プロトコルと本質的に同じタイプの全二重通信プロトコルであり、HTTP プロトコルの欠点に基づいて提案された新しい通信プロトコルです。
HTTP は、要求応答モデルを採用する一方向のアプリケーション層プロトコルです. 通信要求はクライアントによってのみ開始でき、サーバーは要求に応答します. これの欠点は明らかに大きく、サーバーの状態が絶えず変化する限り、クライアントはリアルタイムで応答する必要があり、これは明らかに非常に面倒です. 同時に、ポーリングの効率が低く、リソースの無駄です.
webSocket は、全二重通信のためのネットワーク技術です. 誰でも接続を確立して、相手にデータをプッシュできます. webSocket は、一度接続を確立するだけで、永久に維持することができます.
Mongoose の webSocket の処理mongoose の処理は非常に単純で、HTTP を介して処理されますが、コールバック関数で MG_EV_WS_MSG イベントが送信され、データは mg_ws_upgrade() を介して処理するために ws データに変換されます。 MG_EV_WS_MSG イベントが送信されます。
#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(&mgr); // Initialise event manager 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; }