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が参考になります.
項目機能整理:ログイン登録機能. チャット機能. キャッシュ機能.
コアコード
プロジェクトファイルツリー:
まずMongooseフレームワークを導入し、コアコードフレームワークは以下の通りである.
struct mg_mgr構造体はイベントマネージャで、struct mg_が含まれています.connection,struct mg_接続は、接続のステータスを記述するために使用されます.struct mg_接続の一部:
InitServer()では、まずイベントマネージャmgrを初期化し、次にイベントトリガ時にイベント処理を行うコールバック関数とポートをバインドし、リスニング接続を作成します.最後に、Webルートディレクトリパスを指定します.mg_mgr_pollの下部ではselectを使用してイベントの到来を傍受し、コールバック関数を呼び出して処理します.EventHandlerでは,傍受されたイベントに対して処理を行う.以上がMongooseフレームワークに基づくコアコードであり,次の作業はこのフレームワークを埋め込み,設計機能を一つ一つ実現することである.
チャット機能の実現
Mongooseのチャットページにアクセスすると、チャットメッセージを送信するとイベントMG_がトリガーされますEV_WEBSOCKET_FRAME、それからmgrを遍歴して、このwebsocket_を放送しますmessage:
ログイン登録
ログイン登録機能は、ユーザー情報をローカルに格納する必要があります.この作業はMysqlが行います.
データベース接続
Mysqlの基本インタフェースについて他の人のブログを参考にします:C言語接続mysql簡単なクエリーインスタンス入門
ログイン登録機能の実装
登録登録は特定のイベントなのでmg_register_http_endpoint()は、特定のコールバック関数を登録します.
キャッシュメカニズム
キャッシュメカニズムの実装手順:ログイン時の要求メッセージヘッダにクッキーがある場合、その中のSSESSION_IDはローカルセッションと一致し、マッチングに成功するとチャットページに入りセッション状態を更新し、そうでない場合はログインページにリダイレクトします.最初のログインが成功すると、セッションがローカルセッションに保持されるように生成されます.あるセッションの最近の使用時間が一定の時間を超えた場合、そのセッションは破棄されます.
jQueryコード
拡張性
このプロジェクトでは、プライベートメッセージやモーメンツなど、他の機能を追加することもできますが、フロントエンドには高い要求があります.私が使っているチャットページは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サーバに正常にアクセスしました.
の準備を
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の接続を確立し,メッセージのブロードキャストを行わないことである.モーメンツはグループチャットと少し似ていますが、テキストチャットウィンドウではなくページにメッセージを書いただけです.
プロジェクトで発生した問題