Mongooseフレームワークに基づくWeb通信

17439 ワード

以前C++とLinuxを勉强していた时は、简単なプログラムを书くだけだったので、一つのプロジェクトの开発プロセスと各コンポーネントの组み合わせプロセスを理解するために、プロジェクトの练习をするつもりでした.
の準備を
Mongooseフレームワーク:このフレームワークは使いやすいです.cファイルと.hファイルはプロジェクトフォルダの下にコピーすれば使用できます.私が使っている6.14バージョンです.MySqlデータベース:使用する5.6.45バージョンは、C++を使用してこのプロジェクトを行うため、データベースリンクはC connectorを選択しました.JsonCpp:JsonCppは、対応するテキスト情報を格納し、転送し、前後のデータ相互作用を実現するためのC++ベースのオープンソースライブラリです.フロントエンドページ:これはネット上でテンプレートをダウンロードすることができます.jQueryライブラリ:ダウンロードして使用することも、CDN(コンテンツ配信ネットワーク)で参照することもできます.websocketプロトコル:独立した永続的な接続でフルデュプレクス通信を提供できます.クライアントとサーバは、データを自発的に相手に送信および受信することができます.
かいはつ
まず、Mongooseフレームワークに基づくサーバを作成し、Mongooseの基本インタフェースについて他の人のブログ:Mongoose 6を参照します.11公式マニュアル原版はMongooseライブラリの下にもexampleが参考になります.
項目機能整理:
  • ログイン登録機能.
  • チャット機能.
  • キャッシュ機能.

  • コアコード
    プロジェクトファイルツリー:
    .
    ├── ImServer
    ├── ImServer.cc
    ├── ImServer.hpp
    ├── Makefile
    ├── mongoose
    │   ├── mongoose.c
    │   └── mongoose.h
    ├── mysql
    │   ├── include
    │   └── lib
    ├── tags
    ├── Util.hpp
    └── web
        ├── bak
        ├── css
        ├── images
        ├── index.html
        ├── js
        ├── login.html
        └── register.html -> login.html
    

    まずMongooseフレームワークを導入し、コアコードフレームワークは以下の通りである.
    class ImServer{
        private:
            std::string m_port;
            struct mg_mgr mgr;
            struct mg_connection *nc;
            volatile bool quit;
        public:
            ImServer(std::string port = "8080"):m_port(port),quit(false)
            {}
        
            static void EventHandler(mg_connection *nc, int ev, void *data)
            {
                switch(ev){
                    case MG_EV_HTTP_REQUEST:
                        break;
                    case MG_EV_WEBSOCKET_HANDSHAKE_DONE:
                        break;
                    case MG_EV_WEBSOCKET_FRAME:
                    	break;
                    case MG_EV_CLOSE:
                        break;
                    default:                    
                    break;
                }
            }
       
            void InitServer()
            {
                mg_mgr_init(&mgr, NULL);
                nc = mg_bind(&mgr, m_port.c_str(), EventHandler);
                mg_set_protocol_http_websocket(nc);
                s_http_server_opts.document_root = "web";
            }
       
            void Start()
            {
                int timeout = 1000000;
                while (!quit){
                    mg_mgr_poll(&mgr, timeout);
                }
            }
       
            ~ImServer()
            {
                mg_mgr_free(&mgr);
            }
    };
    

    struct mg_mgr構造体はイベントマネージャで、struct mg_が含まれています.connection,struct mg_接続は、接続のステータスを記述するために使用されます.struct mg_接続の一部:
      struct mg_connection *next, *prev; /* mg_mgr::active_connections linkage */
      struct mg_connection *listener;    /* Set only for accept()-ed connections */
      struct mg_mgr *mgr;                /* Pointer to containing manager */
     
      sock_t sock; /* Socket to the remote peer */
    

    InitServer()では、まずイベントマネージャmgrを初期化し、次にイベントトリガ時にイベント処理を行うコールバック関数とポートをバインドし、リスニング接続を作成します.最後に、Webルートディレクトリパスを指定します.mg_mgr_pollの下部ではselectを使用してイベントの到来を傍受し、コールバック関数を呼び出して処理します.EventHandlerでは,傍受されたイベントに対して処理を行う.以上がMongooseフレームワークに基づくコアコードであり,次の作業はこのフレームワークを埋め込み,設計機能を一つ一つ実現することである.
    チャット機能の実現
    Mongooseのチャットページにアクセスすると、チャットメッセージを送信するとイベントMG_がトリガーされますEV_WEBSOCKET_FRAME、それからmgrを遍歴して、このwebsocket_を放送しますmessage:
    case MG_EV_WEBSOCKET_FRAME:{                                                                                                                   
                        struct websocket_message *wm = (struct websocket_message*)data;
                        struct mg_str ms = {(const char*)wm->data, wm->size};
                        std::string msg = Util::mgStrToString(&ms);
                        Broadcast(nc, msg);
                        }  
                        break;
    

    ログイン登録
    ログイン登録機能は、ユーザー情報をローカルに格納する必要があります.この作業はMysqlが行います.
    データベース接続
    class MysqlClient{
        private:    
            MYSQL *my;
        private:    
            bool ConnectMysql()
            {       
                my = mysql_init(NULL);
                mysql_set_character_set(my, "utf8");
                if (!mysql_real_connect(my, "localhost", "root", "", IM_DB, DB_PORT, NULL, 0)){
                    std::cerr << "connection mysql error" << std::endl;
                    return false;
                }   
                std::cout << "connect mysql success" << std::endl;
                    
                return true;
            }       
                    
        public:     
            MysqlClient()
            {}  
                    
            bool InsertUser(std::string name, std::string passwd)
            {      
                ConnectMysql();
                std::string sql = "INSERT INTO user values(\"";
                sql += name;
                sql += "\", \"";
                sql += passwd;
                sql += "\")";
                std::cout << sql << std::endl;
                int ret = mysql_query(my, sql.c_str());
                if (0 == ret)
                {   
                    std::cout << "insert success:  " << ret << std::endl;
                    return true;
                }   
                    
                std::cout << "insert fail:  " << ret << std::endl;
                return false;
            }       
                    
            bool SelectUser(std::string name, std::string passwd)
            {       
                ConnectMysql();
                std::string sql = "SELECT * FROM user WHERE name=\"";
                sql += name;
                sql += "\" AND passwd=\"";
                sql += passwd;
                sql += "\"";
                std::cout << sql << std::endl;
                bool result = false;
                if (0 == mysql_query(my, sql.c_str())){
                    MYSQL_RES *res = mysql_store_result(my);
                    if (mysql_num_rows(res) > 0){
                        std::cout << "debug...:" << mysql_num_rows(res) << std::endl;
                        result = true;
                    }
                    free(res);
                }   
                    
                mysql_close(my);
                return result;
            }       
                    
            ~MysqlClient()
            {       
                mysql_close(my);    
            }       
    };              
    
    

    Mysqlの基本インタフェースについて他の人のブログを参考にします:C言語接続mysql簡単なクエリーインスタンス入門
    ログイン登録機能の実装
    登録登録は特定のイベントなのでmg_register_http_endpoint()は、特定のコールバック関数を登録します.
    		static void RegisterHandler(mg_connection *nc, int ev, void* data)
            {              
                std::string code = "0";
                std::string echo_json = "{\"result\": ";
                struct http_message *hm = (struct http_message*)data;
                std::string method = Util::mgStrToString(&(hm->method));
                if (method == "POST"){
                    std::string body = Util::mgStrToString(&hm->body);
                    std::string name, passwd;
                    if (Util::GetNameAndPasswd(body, name, passwd) && !name.empty() && !passwd.empty()){
                        if (mc.InsertUser(name, passwd)){
                            code = "0";
                        }else{
                            code = "1";
                        }   
                    }       
                    else{   
                        code = "2"; 
                    }       
                    echo_json += code;
                    echo_json += "}";
                    mg_printf(nc, "HTTP/1.1 200 OK\r
    "); mg_printf(nc, "Content-Length: %lu\r
    \r
    ", echo_json.size()); mg_printf(nc, echo_json.data()); } else{ mg_serve_http(nc, hm, s_http_server_opts); } nc->flags |= MG_F_SEND_AND_CLOSE; } static void LoginHandler(mg_connection *nc, int ev, void *data) { if (ev == MG_EV_CLOSE){ return; } std::string code = "0"; std::string echo_json = "{\"result\": "; std::string shead = ""; struct http_message *hm = (struct http_message*)data; std::cout << "loginHandler ev: " << ev << std::endl; mg_printf(nc, "HTTP/1.1 200 OK\r
    "); std::string method = Util::mgStrToString(&(hm->method)); if (method == "POST"){ std::string body = Util::mgStrToString(&hm->body); //std::cout << "login handler" << body << std::endl; std::string name,passwd; if (Util::GetNameAndPasswd(body, name, passwd) && !name.empty() && !passwd.empty()){ if (mc.SelectUser(name, passwd)){ uint64_t id = 0; if (sn.CreateSession(name, id)){ std::stringstream ss; ss << "Set-Cookie: " << SESSION_ID << "=" << id << "; path = /\r
    "; ss << "Set-Cookie: " << SESSION_NAME << "=" << name << "; path=/\r
    "; shead = ss.str(); mg_printf(nc, shead.data()); code = "0"; }else{ code = "3"; } } else{ code = "1"; } } else{ code = "2"; } echo_json += code; echo_json += "}"; mg_printf(nc, "Content-Length: %lu\r
    \r
    ", echo_json.size()); mg_printf(nc, echo_json.data()); //mg_serve_http(nc, hm, s_http_server_opts); }else{ mg_serve_http(nc, hm, s_http_server_opts); } nc->flags |= MG_F_SEND_AND_CLOSE; }

    キャッシュメカニズム
    キャッシュメカニズムの実装手順:ログイン時の要求メッセージヘッダにクッキーがある場合、その中のSSESSION_IDはローカルセッションと一致し、マッチングに成功するとチャットページに入りセッション状態を更新し、そうでない場合はログインページにリダイレクトします.最初のログインが成功すると、セッションがローカルセッションに保持されるように生成されます.あるセッションの最近の使用時間が一定の時間を超えた場合、そのセッションは破棄されます.
    typedef struct session{     
        uint64_t id;                    
        std::string name;               
        double created;                 
        double last_used;               
    }session_t;                        
                                   
    class Session{  
        private:
            session_t sessions[NUM];   
        public:             
            Session()       
            {                           
                for (auto i = 0; i < NUM; i++){
                    sessions[i].id = 0;
                    sessions[i].name = "";
                    sessions[i].created = 0.0;
                    sessions[i].last_used = 0.0;
                }            
            }       
                
            bool IsLogin(http_message *hm)
            {                    
                return GetSession(hm); 
            }                           
                                        
            bool GetSession(http_message *hm)
            {                          
                uint64_t sid;      
                char ssid[64];     
                char *s_ssid = ssid;   
            
                struct mg_str *cookie_header = mg_get_http_header(hm, "cookie");
                if (nullptr == cookie_header){
                    return false;      
                }        
            
                if (!mg_http_parse_header2(cookie_header, SESSION_ID, &s_ssid, sizeof(ssid))){
                    return false;      
                }                       
                                        
                sid = strtoull(ssid, NULL, 10);
                for (auto i = 0; i < NUM; i++){
                    if (sessions[i].id == sid){
                        sessions[i].last_used = mg_time();
                        return true;   
                    }                  
                }   
                      
                return false;          
            }        
               
            bool CreateSession(std::string name, uint64_t &id)
            {  
                int i = 0;             
                for (; i < NUM; i++){  
                    if (sessions[i].id == 0){
                        break;         
                    }
                }    
                     
                if (i == NUM){         
                    return false;      
                }    
                sessions[i].id = (uint64_t)(mg_time()*1000000L);
                sessions[i].name = name;
                sessions[i].last_used = sessions[i].created = mg_time();
                id = sessions[i].id;   
                     
                return true;           
            }        
                     
            void DestroySession(session_t *s)
            {        
                s->id = 0;             
            }        
                     
            void CheckSession()        
            {        
                double threadhold = mg_time() - SESSION_TTL;
                for (auto i = 0; i < NUM; i++)
                {    
                    if (sessions[i].id > 0 && sessions[i].last_used < threadhold){
                        DestroySession(sessions+i); 
                    }
                }    
            }        
                     
            ~Session()
            {        
                     
            }        
    };   
    

    jQueryコード
        var user = document.getElementById("name");
        var passwd = document.getElementById("password");
                     
        var ruser = document.getElementById("Name");
        var rpasswd = document.getElementById("rPassword");
        var rrpasswd = document.getElementById("rrPassword");
                     
        function login(){
            $.ajax({ 
                url: "/LH",
                type: "POST",
                data: JSON.stringify({
                    name: user.value,
                    passwd: passwd.value
                }),  
                dataType: "json",
                contentType: "application/x-www-form-urlencoded; charset=UTF-8",
                success: function(data){
                    if (data.result == 1 ){
                        alert("       ,       !")
                    }
                    else if(data.result == 2){
                        alert("      !")
                    }
                    else{
                        window.location.href='index.html'
                    }
                },  
                error: function(e){
                    alert("    ,     !")
                }   
            }); 
        } 
        function register(){
            if(rpasswd.value != rrpasswd.value){
                alert("       ,     !")
                return;
            } 
            $.ajax({
                url: "/RH",
                type: "POST",
                data: JSON.stringify({
                    name: ruser.value,
                    passwd: rpasswd.value
                }), 
                dataType: "json",
                contentType: "application/x-www-form-urlencoded; charset=UTF-8",
                success: function(data){
                    if (data.result == 1 ){
                        alert("       ,       !")
                    }
                    else if(data.result == 2){
                        alert("      !")
                    }
                    else{
                        alert("    ,     !")
                        window.location.href='login.html'
                    }
                },  
                error: function(e){
                    alert("    ,     !")
                }
            });
          
        } 
    

    拡張性
    このプロジェクトでは、プライベートメッセージやモーメンツなど、他の機能を追加することもできますが、フロントエンドには高い要求があります.私が使っているチャットページはMongooseライブラリに付属しているので、これらの機能を拡張するには、チャットのページを再構築する必要があります.サーバは基本的に、このイベントに対して専用のコールバック関数(ログインイベントや登録イベントなど)を登録します.私発メッセージの処理は,1対1の接続を確立し,メッセージのブロードキャストを行わないことである.モーメンツはグループチャットと少し似ていますが、テキストチャットウィンドウではなくページにメッセージを書いただけです.
    プロジェクトで発生した問題
  • データベース接続の問題で、データベースを挿入すると、どうしてもデータが挿入されないことに気づき、検索の問題でデータベース接続に失敗したことに気づきました.しかし、バックグラウンドではデータベースに接続したり、データベースを操作したりすることができます.また、命令sudo netstat-nlt検索でmysqlポートが正しいことを確定し、mysqlサービス業もオープンした.そこでmysql_real_connectの「localhost」を「127.0.0.1」に置き換えてもだめです.最後にgdbでデバッグしmysql_real_connectこのステップではclient.が見つからないことを示します.cファイル.データベースに問題があると思います.CentOSがデフォルトでインストールしているデータベースはmariadb、mariadbはmysqlのブランチです.そこでmariadbを削除してmysqlを再オンラインでインストールしました.しかし、問題は解決されず、ネット上でブログを見て、インストールの手順が間違っていることに気づいた.mysqlを再アンインストールしmysqlオフラインインストールの手順で再インストールしました.実行時にデータベースにデータを挿入しました.
  • フロントエンドコードの問題、jQueryとC++は往復して切り替えて、jQueryは多くセミコロンを打った.開発者モードに入り、コードモジュールをクリックすると、実行時にエラーが表示されますが、この問題は解決しやすいです.
  • このプロジェクトは仮想マシンで完了したが、windowsホスト上のブラウザは仮想マシン上のwebサーバにアクセスできず、ホスト上では仮想マシンipにpingできることが分かったが、仮想マシン上ではpingがホストに通じない.インターネットで検索すると、ホストファイアウォールの構成の問題であることがわかり、構成手順に従って構成すると、仮想マシンはホストをpingすることができますが、ホスト上のブラウザは仮想マシン上のwebサーバにアクセスできません.仮想マシンのファイアウォールの問題かもしれないと思い、コマンドsudo systemctl stop firewalldでファイアウォールを消しました.ホスト上のブラウザが仮想マシン上のWebサーバに正常にアクセスしました.