目录
5.用postman测试用户登录成功后跳转页面 ---302
1.ubuntu导入sql文件
sql文件:
使用命令mysql -uroot -p <im.sql导入
结果如下:
2.用.c文件生成库文件
3.序列化 反序列化测试
vi util.hpp:
#include<iostream>
#include<sstream>
#include<string>
#include<jsoncpp/json/json.h>
namespace im_sys{
class JsonUtil{
public:
static bool Serialize(Json::Value &value,std::string *jsonstr)
{
std::stringstream ss;
Json::StreamWriterBuilder swb;
Json::StreamWriter *sw = swb.newStreamWriter();
int ret = sw->write(value,&ss);
if(ret !=0)
{
std::cout<<"json writer failed!\n";
delete sw;
return true;
}
*jsonstr = ss.str();
delete sw;
return true;
}
static bool UnSerialize(const std::string &jsonstr,Json::Value *value)
{
Json::CharReaderBuilder crb;
Json::CharReader *cr = crb.newCharReader();
std::string err;
bool ret = cr->parse(jsonstr.c_str(),jsonstr.c_str() + jsonstr.size(),value,&err);
if(ret == false)
{
delete cr;
std::cout<<"json parse failed!"<<err<<std::endl;
return false;
}
delete cr;
return true;
}
};
}
vi json_test.cpp:
#include "util.hpp"
int main()
{
std::string str = R"({"username":"wj","password":"1111"})";
Json::Value val;
im_sys::JsonUtil::UnSerialize(str,&val);
std::cout<<val["username"].asString()<<std::endl;
std::cout<<val["password"].asString()<<std::endl;
Json::Value err;
err["result"]=false;
err["reason"] = "用户名已经被占用!";
std::string buf;
im_sys::JsonUtil::Serialize(err,&buf);
std::cout<<buf<<std::endl;
return 0;
}
测试结果:
4. 第一次用postman测试注册功能
server.hpp ---/register功能的相应实现
#include "data.hpp"
#include "mongoose.h"
#include "util.hpp"
namespace im_sys{
#define SERVER_PORT 20000
class Server
{
private:
static struct mg_mgr *_mgr;
static UserTable *_user;
private:
static void callback(struct mg_connection *c,int ev,void *ev_data,void *fn_data)
{
if(ev == MG_EV_HTTP_MSG)
{
struct mg_http_message *hm = (struct mg_http_message *)ev_data;
if(mg_http_match_uri(hm,"/register"))
{
// mg_http_reply(c,200,"","register success!");
// return;
//拿到请求信息的正文,是json格式的用户信息,进行解析
Json::Value user_info;
JsonUtil::UnSerialize(hm->body.ptr,&user_info);
std::string username = user_info["username"].asString();
std::string password = user_info["password"].asString();
//在数据库中进行查看用户名是否已经被占用
bool ret = _user->Exists(username);
if(ret == true)
{
Json::Value err;
err["result"] = false;
err["reason"] = "用户名已经被占用";
std::string body;
JsonUtil::Serialize(err,&body);
std::string header = "Content-Type:application/json\r\n";
mg_http_reply(c,400,header.c_str(),body.c_str());
return;
}
//返回结果--注册成功或失败
ret = _user->Insert(username,password);
if(ret == false)
{
Json::Value err;
err["result"] = false;
err["reason"] = "用户插入数据库失败!";
std::string body;
JsonUtil::Serialize(err,&body);
std::string header = "Content-Type: application/json\r\n";
mg_http_reply(c,200,header.c_str(),body.c_str());
return;
}
Json::Value err;
err["result"] = true;
err["reason"] = "注册成功!";
std::string body;
JsonUtil::Serialize(err,&body);
std::string header = "Content-Type:application/json\r\n";
mg_http_reply(c,200,header.c_str(),body.c_str());
return;
}
}
}
public:
Server()
{
_mgr = new struct mg_mgr();
_user = new UserTable();
}
~Server()
{
mg_mgr_free(_mgr);
delete _mgr;
delete _user;
}
bool RunModule(int port = SERVER_PORT)
{
std::string addr = "0.0.0.0:";
addr += std::to_string(port);
auto res = mg_http_listen(_mgr,addr.c_str(),callback,_mgr);
if(res ==NULL)
{
std::cout<<"init http listen failed!\n";
return false;
}
while(1)
{
mg_mgr_poll(_mgr,1000);
}
return true;
}
};
struct mg_mgr *Server::_mgr = NULL;
UserTable * Server::_user = NULL;
}
makefile:
main:data.hpp main.cpp
g++ -std=c++11 $^ -o $@ -L/usr/lib/mysql -lmysqlclient -L./lib -lmongoose -ljsoncpp
main.cpp:
#include "data.hpp"
#include "server.hpp"
#define OFFLINE 0
#define ONLINE 1
void DataTest()
{
im_sys::UserTable * _user = new im_sys::UserTable();
//插入用户
// _user->Insert("王五","1111");
//测试用户是否存在
// bool ret = _user->Exists("王五");
// std::cout<<ret<<std::endl;
// 判断用户是否在线
// int status = _user->Status("王五");
// if(status ==ONLINE)
// {
// std::cout<<"online!\n";
// }
// else if(status == OFFLINE)
// {
// std::cout<<"offline!\n";
// }
//修改用户状态
_user->UpdateStatus("王五",OFFLINE);
int status = _user->Status("王五");
if(status ==ONLINE)
{
std::cout<<"online!\n";
}
else if(status == OFFLINE)
{
std::cout<<"offline!\n";
}
//检验密码
// bool ret = _user->UserPassCheck("王五","1111");
// if(ret == false)
// {
// std::cout<<"用户名密码错误!\n";
// }
// else
// {
// std::cout<<"login success!\n";
// }
//}
//修改密码
// _user->UpdatePasswd("王五","1111");
// int ret =_user->UserPassCheck("王五","1111");
// if(ret == false)
// {
// std::cout<<"用户名密码错误!\n";
// }
// else
// {
// std::cout<<"login success!\n";
// }
}
void ServerTest()
{
im_sys::Server *server = new im_sys::Server();
server->RunModule();
return;
}
int main()
{
// DataTest();
ServerTest();
return 0;
}
data.hpp:
#ifndef __M_IM_DATA_H__
#define __M_IM_DATA_H__
#include<iostream>
#include<string>
#include<cstdlib>
#include<mutex>
#include<mysql/mysql.h>
namespace im_sys{
#define DB_HOST "127.0.0.1"
#define DB_USER "root"
#define DB_PASS "1111"
#define DB_NAME "db_96"
static MYSQL *MysqlInit()
{
MYSQL *mysql = mysql_init(NULL);
if(mysql == NULL)
{
std::cout<<"mysql init failed!\n";
return NULL;
}
if(mysql_real_connect(mysql,DB_HOST,DB_USER,DB_PASS,DB_NAME,0,NULL,0) == NULL)
{
std::cout<<"connect mysql server failed:"<<mysql_error(mysql)<<std::endl;
mysql_close(mysql);
return NULL;
}
if(mysql_set_character_set(mysql,"utf8") != 0)
{
std::cout<<"set mysql client character failed!:"<<mysql_error(mysql)<<std::endl;
mysql_close(mysql);
return NULL;
}
return mysql;
}
static void MysqlDestory(MYSQL *mysql)
{
if(mysql)
{
mysql_close(mysql);
}
return;
}
static bool MysqlQuery(MYSQL *mysql,const std::string &sql)
{
int ret = mysql_query(mysql,sql.c_str());
if(ret != 0)
{
std::cout<<sql<<std::endl;
std::cout<<"query failed:"<<mysql_error(mysql)<<std::endl;
return false;
}
return true;
}
class UserTable{
private:
MYSQL *_mysql;
std::mutex _mutex;
public:
UserTable():_mysql(NULL)
{
_mysql = MysqlInit();
if(_mysql == NULL)
{
exit(-1);
}
}
~UserTable()
{
MysqlDestory(_mysql);
}
bool Insert(const std::string &name,const std::string &pass)
{
#define USER_INSERT "insert im_user values(null,'%s',MD5('%s'),0,now(),now());"
char sql[4096] = {0};
sprintf(sql,USER_INSERT,name.c_str(),pass.c_str());
return MysqlQuery(_mysql,sql);
}
bool Exists(const std::string &name)
{
#define USER_EXISTS "select id from im_user where name='%s';"
char sql[4096] = {0};
sprintf(sql,USER_EXISTS,name.c_str());
bool ret = MysqlQuery(_mysql,sql);
if(ret == false)
{
return false;
}
MYSQL_RES *res = mysql_store_result(_mysql);
int num = mysql_num_rows(res);
if(num!=0)
{
mysql_free_result(res);
return true;
}
mysql_free_result(res);
return false;
}
bool UserPassCheck(const std::string &name,const std::string &pass)
{
#define USER_CHECK "select id from im_user where name='%s' and pass=MD5('%s');"
char sql[4096] = {0};
sprintf(sql,USER_CHECK,name.c_str(),pass.c_str());
bool ret = MysqlQuery(_mysql,sql);
if(ret == false)
{
return false;
}
MYSQL_RES *res = mysql_store_result(_mysql);
int num = mysql_num_rows(res);
if(num!=0)
{
mysql_free_result(res);
return true;
}
mysql_free_result(res);
return false;
}
int Status(const std::string &name)
{
#define USER_STATUS "select status from im_user where name='%s';"
char sql[4096]={0};
sprintf(sql,USER_STATUS,name.c_str());
bool ret = MysqlQuery(_mysql,sql);
if(ret == false)
{
return false;
}
MYSQL_RES *res = mysql_store_result(_mysql);
int num = mysql_num_rows(res);
if(num == 0)
{
mysql_free_result(res);
return -1;
}
MYSQL_ROW row = mysql_fetch_row(res);
int status = std::stoi(row[0]);
mysql_free_result(res);
return status;
}
bool UpdateStatus(const std::string &name,int status)
{
#define USER_UPDATE_STATUS "update im_user set status=%d,stime=now() where name='%s';"
char sql[4096] = {0};
sprintf(sql,USER_UPDATE_STATUS,status,name.c_str());
return MysqlQuery(_mysql,sql);
}
bool UpdatePasswd(const std::string &name,const std::string &pass)
{
#define USER_UPDATE_PASS "update im_user set pass=MD5('%s') where name='%s';"
char sql[4096] = {0};
sprintf(sql,USER_UPDATE_PASS,pass.c_str(),name.c_str());
return MysqlQuery(_mysql,sql);
}
};
}
#endif
结果如下:
5.用postman测试用户登录成功后跳转页面 ---302
server.hpp:
#include "data.hpp"
#include "mongoose.h"
#include "util.hpp"
#include <unordered_map>
#include <time.h>
namespace im_sys{
#define SERVER_PORT 20000
#define OFFLINE 0
#define ONLINE 1
#define WWWROOT "./wwwroot/"
struct IMSession
{
int status;
std::string session_id;//系统时间
std::string username;
time_t ctime;//会话创建时间
time_t ltime;//最后操作时间
struct mg_connection *conn;//当前用户对一个的mongoose连接
};
class Server
{
private:
static struct mg_mgr *_mgr;
static UserTable *_user;
static std::unordered_map<std::string,IMSession> _session;
private:
static std::string ConResp(bool res,const std::string &info)
{
Json::Value err;
err["result"] = res;
err["reason"] = info;
std::string body;
JsonUtil::Serialize(err,&body);
return body;
}
static std::string CreateIMSession(struct mg_connection *c,const std::string &username)
{
struct IMSession s;
s.conn = c;
s.username = username;
s.status = ONLINE;
s.ctime = time(NULL);
s.ltime = time(NULL);
s.session_id = std::to_string(time(NULL));
_session[s.session_id] = s;
return s.session_id;
}
static void Login(struct mg_connection *c,struct mg_http_message *hm)
{
//拿到请求正文,进行json反序列化得到用户名和密码
Json::Value user_info;
JsonUtil::UnSerialize(hm->body.ptr,&user_info);
std::string username = user_info["username"].asString();
std::string password = user_info["password"].asString();
//在数据库中验证用户名和密码
bool ret = _user->UserPassCheck(username,password);
if(ret == false)
{
std::string body = ConResp(false,"用户名密码错误");
std::string header = "Content-Type: application/json\r\n";
mg_http_reply(c,400,header.c_str(),body.c_str());
return;
}
//判断用户是否已经在线,若已经在线,则不能重复登录
ret = _user->Status(username);
if(ret == ONLINE)
{
std::string body = ConResp(false,"用户已登录");
std::string header = "Content-Type: application/json\r\n";
mg_http_reply(c,400,header.c_str(),body.c_str());
return;
}
//没在线,则修改用户为在线状态,并且返回登录成功,设置SetCookie包括用户名、用户状态
std::string ssid = CreateIMSession(c,username);
ret = _user->UpdateStatus(username,ONLINE);
if(ret == false)
{
std::string body = ConResp(false,"修改用户状态失败");
std::string header = "Content-Type: application/json\r\n";
mg_http_reply(c,500,header.c_str(),body.c_str());
return;
}
//登录成功需要让客户端去请求一个聊天页面
std::stringstream headers;
headers<< "Location: /chat.html\r\n";
headers <<"Set-Cookie: SSID=" <<ssid<<";Path=/\r\n";
std::string body = ConResp(true,"登录成功");
mg_http_reply(c,302,headers.str().c_str(),body.c_str());
return;
}
static void Register(struct mg_connection *c,struct mg_http_message *hm)
{
//拿到请求信息的正文,是json格式的用户信息,进行解析
Json::Value user_info;
JsonUtil::UnSerialize(hm->body.ptr,&user_info);
std::string username = user_info["username"].asString();
std::string password = user_info["password"].asString();
//在数据库中进行查看用户名是否已经被占用
bool ret = _user->Exists(username);
if(ret == true)
{
Json::Value err;
err["result"] = false;
err["reason"] = "用户名已经被占用";
std::string body;
JsonUtil::Serialize(err,&body);
std::string header = "Content-Type:application/json\r\n";
mg_http_reply(c,400,header.c_str(),body.c_str());
return;
}
//返回结果--注册成功或失败
ret = _user->Insert(username,password);
if(ret == false)
{
Json::Value err;
err["result"] = false;
err["reason"] = "用户插入数据库失败!";
std::string body;
JsonUtil::Serialize(err,&body);
std::string header = "Content-Type: application/json\r\n";
mg_http_reply(c,200,header.c_str(),body.c_str());
return;
}
Json::Value err;
err["result"] = true;
err["reason"] = "注册成功!";
std::string body;
JsonUtil::Serialize(err,&body);
std::string header = "Content-Type:application/json\r\n";
mg_http_reply(c,200,header.c_str(),body.c_str());
return;
}
static bool GetSession(struct mg_connection *c,IMSession *session)
{
auto it = _session.begin();
for(;it!=_session.end();++it)
{
if(it->second.conn == c)
{
*session = it->second;
return true;
}
}
return false;
}
static bool DelSession(IMSession &session)
{
auto it = _session.find(session.session_id);
if(it == _session.end())
{
std::cout<<"not find session\n";
return false;
}
_session.erase(it);
return true;
}
static void ConnClose(struct mg_connection *c)
{
//找到连接对应的session
IMSession session;
bool ret = GetSession(c,&session);
if(ret == false)
{
std::cout<<"have no session info!\n";
return;
}
//修改数据库中的用户状态
ret = _user->UpdateStatus(session.username,OFFLINE);
if(ret == false)
{
std::cout<<"update status offline failed!\n";
return;
}
//删除会话信息
DelSession(session);
}
static void callback(struct mg_connection *c,int ev,void *ev_data,void *fn_data)
{
if(ev == MG_EV_HTTP_MSG)
{
struct mg_http_message *hm = (struct mg_http_message *)ev_data;
if(mg_http_match_uri(hm,"/register"))
{
// mg_http_reply(c,200,"","register success!");
// return;
Register(c,hm);
}
else if(mg_http_match_uri(hm,"/login"))
{
//登录请求
Login(c,hm);
}
else if(mg_http_match_uri(hm,"/cws"))
{
//协议切换请求
}
else
{
//静态页面请求
struct mg_http_serve_opts opts = {.root_dir = WWWROOT};
mg_http_serve_dir(c,hm,&opts);
}
}
else if(ev == MG_EV_WS_OPEN)
{
//websocket握手成功
}
else if(ev == MG_EV_WS_MSG)
{
//收到聊天消息
}
else if(ev == MG_EV_CLOSE)
{
//表示连接断开
ConnClose(c);
}
}
public:
Server()
{
_mgr = new struct mg_mgr();
_user = new UserTable();
}
~Server()
{
mg_mgr_free(_mgr);
delete _mgr;
delete _user;
}
bool RunModule(int port = SERVER_PORT)
{
std::string addr = "0.0.0.0:";
addr += std::to_string(port);
auto res = mg_http_listen(_mgr,addr.c_str(),callback,_mgr);
if(res ==NULL)
{
std::cout<<"init http listen failed!\n";
return false;
}
while(1)
{
mg_mgr_poll(_mgr,1000);
}
return true;
}
};
struct mg_mgr *Server::_mgr = NULL;
UserTable * Server::_user = NULL;
std::unordered_map<std::string,IMSession>Server::_session;
}
makefile:
main:data.hpp main.cpp
g++ -std=c++11 $^ -o $@ -L/usr/lib/mysql -lmysqlclient -L./lib -lmongoose -ljsoncpp
结果如下:
6.项目网页聊天室总结
环境搭建:
1)ubuntu安装mysql
2)ubuntu安装mongoose http库
链接:https://pan.baidu.com/s/1kMaSz5rduAr5Cs0a88LhrQ 提取码:1111
---主要用到了mongoose.c和mongoose.h两个文件
3)ubuntu安装jsoncpp库
4)打开VM虚拟机,通过ifconfig获取主机名,在项目chat.html中修改ws_init方法中的主机名,端口号不用修改。
结果:
登录三个会话进行聊天,测试结果如下: