云计算百科
云计算领域专业知识百科平台

【在线五子棋对战】服务器业务分类请求处理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}

目录

Http请求处理

静态资源请求

注册请求

登录请求

信息获取请求

Websocket请求处理

打开大厅请求

关闭大厅请求

大厅消息请求

打开房间请求

关闭房间请求

房间消息请求

项目总结


通过定义一个服务器类对象,管理之前所有的模块类,并实现对http、open、close、message的请求处理方法

class Gomoku_Server
{
using websocket_server = websocketpp::server<websocketpp::config::asio>;

public:
Gomoku_Server(const std::string &host,
const std::string &user,
const std::string &passwd,
const std::string &db,
uint16_t port = 3306,
const std::string &wwwroot = WWWROOT)
: _webroot(wwwroot), _user(host, user, passwd, db, port), _room(&_user, &_online), _session(&_server), _matcher(&_room, &_online, &_user)
{
websocket_server &server = _server; // 为了方便下面的lambda方法引用
_server.set_access_channels(websocketpp::log::alevel::none); // 设置日志级别
_server.init_asio(); // 初始化asio调度器
_server.set_reuse_addr(true); // 开启地址重用

// 设置http回调方法
_server.set_http_handler([&server, this](websocketpp::connection_hdl hdl) {

});
_server.set_open_handler([&server](websocketpp::connection_hdl) {

});
_server.set_close_handler([&server](websocketpp::connection_hdl) {

});
_server.set_message_handler([&server](websocketpp::connection_hdl, websocket_server::message_ptr msg_ptr) {

});
}

void Start(uint16_t port)
{
_server.listen(port); // 设置端口号
_server.start_accept(); // 启动监听
_server.run(); // 启动服务端
}

private:
void File_Handler(websocket_server::connection_ptr &conn)
// 静态资源请求的处理
{
}

void Register(websocket_server::connection_ptr &conn)
// 用户注册功能请求的处理
{
}

void Login(websocket_server::connection_ptr &conn)
// 用户登录功能请求的处理
{
}

void Info(websocket_server::connection_ptr &conn)
// 用户信息获取功能请求的处理
{
}

private:
std::string _webroot; // 服务器根目录 例如 /register.html -> ./wwwroot/register.html

User_Table _user; // 用户管理
Online_Manager _online; // 在线用户管理
Room_Manager _room; // 房间管理
Matcher _matcher; // 匹配管理

Session_Manager _session; // session管理

websocket_server _server; // websocket服务端
};

当调用该类时,直接.Start(),服务器就跑起来了

Http请求处理

对于Http请求,分为静态资源请求(html资源)、和动态资源请求(注册、登录、信息获取…),因此在 _server.set_http_handler() 中先实现对请求的分类

_server.set_http_handler([&server, this](websocketpp::connection_hdl hdl)
{
// 获取连接
websocket_server::connection_ptr conn = server.get_con_from_hdl(hdl);
// 获取请求中的方法和uri
websocketpp::http::parser::request request = conn->get_request();
std::string method = request.get_method();
std::string uri = request.get_uri();
// 判断是什么类型的请求
if(method == "POST" && uri == "/register") // 注册请求
return Register(conn);
else if(method == "POST" && uri == "/login") // 登录请求
return Login(conn);
else if(method == "GET" && uri == "/info") // 消息请求
return Info(conn); // 静态资源请求
else
return File_Handler(conn); });

静态资源请求

当客户端请求静态资源时,File_Handler的任务是读取该文件并返回给客户端 若资源不存在则返回404

void File_Handler(websocket_server::connection_ptr &conn)
// 静态资源请求的处理
{
// 获取请求的uri资源路径
websocketpp::http::parser::request request = conn->get_request();
std::string uri = request.get_uri();
// 组合成真实路径
std::string filepath = _webroot + uri;
// 若路径是目录,增加一个后缀,例如login.html
if (filepath.back() == '/')
filepath += "login.html";

// 读取文件内容
Json::Value response;
std::string body;
if (File_Util::Read(filepath, body) == false) // 若文件不存在,返回404
{
body += "<html><head><meta charset=\\"UTF-8\\"/></head><body><h1> Not Found </h1></body></html>";
conn->set_status(websocketpp::http::status_code::not_found); // 设置404状态码
conn->set_body(body);
return;
}

// 设置响应正文
conn->set_body(body);
conn->set_status(websocketpp::http::status_code::ok);
}

注册请求

Register的任务是将客户端发来的 用户名&密码 存到数据库

void Register(websocket_server::connection_ptr &conn)
// 用户注册功能请求的处理
{
// 获取请求正文
// 对正文JSON反序列化,得到用户名&密码
std::string req_body = conn->get_request_body();
Json::Value user_info;
if (Json_Util::Deserialization(req_body, user_info) == false)
// 反序列化失败
{
LOG(LogLv::ERROR, "反序列化注册信息失败");
Set_Json_Response(conn, Protocol::MakeHttpError("请求的正文格式错误"),
websocketpp::http::status_code::bad_request);
return;
}
// 插入数据库
auto is_blank = [](const Json::Value &value)
{
/* 判断字符串是否为空 */
if (!value.isString())
return true;
const std::string text = value.asString();
return std::all_of(text.begin(), text.end(), [](unsigned char ch)
{ return std::isspace(ch) != 0; });
};
if (is_blank(user_info["username"]) || is_blank(user_info["password"]))
// 用户名|密码 有空
{
LOG(LogLv::ERROR, "用户名或密码为空");
Set_Json_Response(conn, Protocol::MakeHttpError("请输入用户名/密码"),
websocketpp::http::status_code::bad_request);
return;
}
if (_user.Register(user_info) == false)
// 用户名被占用
{
Set_Json_Response(conn, Protocol::MakeHttpError("用户名被占用"),
websocketpp::http::status_code::bad_request);
return;
}
// 成功返回200
LOG(LogLv::DEBUG, "注册成功");
Set_Json_Response(conn, Protocol::MakeHttpSuccess("注册成功"),
websocketpp::http::status_code::ok);
return;
}

登录请求

将前端发来的用户名&密码与数据库作比对,若成功,则让用户跳转到大厅界面

void Login(websocket_server::connection_ptr &conn)
// 用户登录功能请求的处理
{
// 获取请求正文
std::string req_body = conn->get_request_body();
// 对正文反序列化得到用户名&密码
Json::Value user_info;
if (Json_Util::Deserialization(req_body, user_info) == false)
// 反序列化失败
{
LOG(LogLv::ERROR, "反序列化注册登录失败");
Set_Json_Response(conn, Protocol::MakeHttpError("请求的正文格式错误"),
websocketpp::http::status_code::bad_request);
return;
}
// 校验正文完整性
auto is_blank = [](const Json::Value &value)
{
/* 判断字符串是否为空 */
if (!value.isString())
return true;
const std::string text = value.asString();
return std::all_of(text.begin(), text.end(), [](unsigned char ch)
{ return std::isspace(ch) != 0; });
};
if (is_blank(user_info["username"]) || is_blank(user_info["password"]))
// 用户名|密码 有空
{
LOG(LogLv::ERROR, "用户名或密码为空");
Set_Json_Response(conn, Protocol::MakeHttpError("请输入用户名/密码"),
websocketpp::http::status_code::bad_request);
return;
}
// 用户信息验证
if (_user.Login(user_info) == false)
// 校验失败
{
LOG(LogLv::ERROR, "用户名&密码错误");
Set_Json_Response(conn, Protocol::MakeHttpError("密码错误,请重试"),
websocketpp::http::status_code::bad_request);
return;
}

// 验证成功,创建Session
int uid = user_info["id"].asInt();
session_ptr ssp = _session.Create_Session(uid, Session_Status::LOGIN);
if (ssp.get() == nullptr)
// 创建session失败
{
LOG(LogLv::ERROR, "创建Session失败");
Set_Json_Response(conn, Protocol::MakeHttpError("创建Session失败"),
websocketpp::http::status_code::internal_server_error);
return;
}
// 创建Session成功,设置过期时间
_session.Set_Session_ExpireTime(ssp->Get_Ssid(), SESSION_TIMEOUT);
// 创建Session成功,设置为Cookie
std::string ssid = "SSID=" + std::to_string(ssp->Get_Ssid());
conn->append_header("Set-Cookie", ssid);
// 设置成功响应
LOG(LogLv::DEBUG, "登录成功");
Set_Json_Response(conn, Protocol::MakeHttpSuccess("登录成功"),
websocketpp::http::status_code::ok);
}

在登录成功后,也会给该用户创建一个Session,和响应一并返回给前端

信息获取请求

在用户登录进入大厅后,需要用过GET请求获取该用户的对战场次、积分等等信息

void Info(websocket_server::connection_ptr &conn)
// 用户信息获取功能请求的处理
{
// 通过SSID找到Session
session_ptr ssp = Get_Session_From_Cookie(conn);
if (ssp.get() == nullptr) // Session过期
{
LOG(LogLv::ERROR, "ssid对应Session失效");
Set_Json_Response(conn, Protocol::MakeHttpError("登录过期,请重新登录"),
websocketpp::http::status_code::bad_request);
return;
}
// 通过Session找到用户
int uid = ssp->Get_User();
// 取出用户信息并返回
Json::Value user_info;
if (_user.SelectById(uid, user_info) == false) // 获取用户信息失败
{
LOG(LogLv::ERROR, "获取用户信息失败");
Set_Json_Response(conn, Protocol::MakeHttpError("找不到用户信息,请重新登录"),
websocketpp::http::status_code::bad_request);
return;
}
Set_Json_Response(conn, user_info, websocketpp::http::status_code::ok);
// 刷新Session过期时间
_session.Set_Session_ExpireTime(ssp->Get_Ssid(), SESSION_TIMEOUT);
}

Websocket请求处理

在初始化server类时,会设置该服务端对于websocoket打开/关闭/消息/http的回调函数,对于上面的http请求,都在_server.set_http_handler中设置好了

下面一一对设置的打开/关闭/消息回调函数内的接口进行实现

Gomoku_Server(const std::string &host,
const std::string &user,
const std::string &passwd,
const std::string &db,
uint16_t port = 3306,
const std::string &wwwroot = WWWROOT)
: _webroot(wwwroot), _user(host, user, passwd, db, port), _room(&_user, &_online), _session(&_server), _matcher(&_room, &_online, &_user)
{
websocket_server &server = _server; // 为了方便下面的lambda方法引用
_server.set_access_channels(websocketpp::log::alevel::none); // 设置日志级别
_server.init_asio(); // 初始化asio调度器
_server.set_reuse_addr(true); // 开启地址重用

// 设置http回调方法
_server.set_http_handler([&server, this](websocketpp::connection_hdl hdl)
{
// 获取连接
websocket_server::connection_ptr conn = server.get_con_from_hdl(hdl);
// 获取请求中的方法和uri
websocketpp::http::parser::request request = conn->get_request();
std::string method = request.get_method();
std::string uri = request.get_uri();
// 判断是什么类型的请求
if(method == "POST" && uri == "/register") // 注册请求
return Register(conn);
else if(method == "POST" && uri == "/login") // 登录请求
return Login(conn);
else if(method == "GET" && uri == "/info") // 消息请求
return Info(conn); // 静态资源请求
else
return File_Handler(conn); });
_server.set_open_handler([&server, this](websocketpp::connection_hdl hdl)
{
// websocket长连接建立成功后的回调函数
// 获取请求uri
websocket_server::connection_ptr conn = server.get_con_from_hdl(hdl);
websocketpp::http::parser::request request = conn->get_request();
std::string uri = request.get_uri();
// 通过uri判断 大厅/房间 websocket请求
if(uri == "/hall") // 大厅websocket请求
{
return WsOpen_Hall(conn);
}
else if(uri == "/room") // 房间websocket请求
{
return WsOpen_Room(conn);
} });
_server.set_close_handler([&server, this](websocketpp::connection_hdl hdl)
{
// websocket长连接断开后的回调函数
// 获取请求uri
websocket_server::connection_ptr conn = server.get_con_from_hdl(hdl);
websocketpp::http::parser::request request = conn->get_request();
std::string uri = request.get_uri();
// 通过uri判断 大厅/房间 websocket请求
if(uri == "/hall") // 大厅websocket请求
{
return WsClose_Hall(conn);
}
else if(uri == "/room") // 房间websocket请求
{
return WsClose_Room(conn);
} });
_server.set_message_handler([&server, this](websocketpp::connection_hdl hdl, websocket_server::message_ptr msg_ptr)
{
// websocket长连接通信的回调函数
// 获取请求uri
websocket_server::connection_ptr conn = server.get_con_from_hdl(hdl);
websocketpp::http::parser::request request = conn->get_request();
std::string uri = request.get_uri();
// 通过uri判断 大厅/房间 websocket请求
if(uri == "/hall") // 大厅websocket请求
{
return WsMessage_Hall(conn, msg_ptr);
}
else if(uri == "/room") // 房间websocket请求
{
return WsMessage_Room(conn, msg_ptr);
} });
}

打开大厅请求

当用户进入大厅时,需要判断是否有该用户的Session,若有,就将该用户加入在线用户大厅管理结构 同时将Session时限设为永久,防止在大厅时就过期

void WsOpen_Hall(websocket_server::connection_ptr conn) // 进入大厅的处理
{
// 1. 判断客户端是否成功登录
session_ptr ssp = Get_Session_From_Cookie(conn);
if (ssp.get() == nullptr)
return;
// 2. 判断客户端是否重复登录
if (_online.IsHall(ssp->Get_Ssid()) || _online.IsRoom(ssp->Get_Ssid()))
{
LOG(LogLv::ERROR, "该用户重复登录");
Send_WebSocket_Response(conn, Protocol::MakeHallReady(false, "玩家重复登录"));
return;
}
// 3. 将当前客户端连接加入到游戏大厅管理结构
_online.EnterHall(ssp->Get_User(), conn);
// 4. 给客户端响应游戏大厅连接成功
Send_WebSocket_Response(conn, Protocol::MakeHallReady(true));

// 5. 将该用户Session设为永久存在
_session.Set_Session_ExpireTime(ssp->Get_Ssid(), SESSION_FOREVER);
}

关闭大厅请求

大厅关闭时,需要移出在线用户管理结构,并且将Session重新设为有期

void WsClose_Hall(websocket_server::connection_ptr conn) // 退出大厅的处理
{
// 获取到该连接的session
session_ptr ssp = Get_Session_From_Cookie(conn);
if (ssp.get() == nullptr)
return;
// 将该连接移出大厅
_online.ExitHall(ssp->Get_User());
// 将该Session重新设为有期
//if (!_online.IsRoom(ssp->Get_User()))
_session.Set_Session_ExpireTime(ssp->Get_Ssid(), SESSION_TIMEOUT);
}

大厅消息请求

大厅的消息请求分为两种:开始匹配和取消匹配 也就是将用户放入/取出匹配队列

void WsMessage_Hall(websocket_server::connection_ptr conn, websocket_server::message_ptr msg_ptr) // 大厅消息的处理
{
// 获取到该连接的session
session_ptr ssp = Get_Session_From_Cookie(conn);
if(ssp.get() == nullptr)
return;
// 获取请求正文
std::string req_body_str = msg_ptr->get_payload();
Json::Value req_body_json;
if (Json_Util::Deserialization(req_body_str, req_body_json) == false)
{
LOG(LogLv::ERROR, "反序列化失败");
Send_WebSocket_Response(conn, Protocol::MakeHallError("请求信息解析失败"));
return;
}
// 若是开始匹配请求:将用户添加到匹配队列中
if (!req_body_json["optype"].isNull() && req_body_json["optype"].asString() == "match_start")
{
_matcher.Add(ssp->Get_User());
Send_WebSocket_Response(conn, Protocol::MakeHallOperationResult("match_start", true));
return;
}
// 若是取消匹配请求:将用户从匹配队列中移出
else if (!req_body_json["optype"].isNull() && req_body_json["optype"].asString() == "match_stop")
{
_matcher.Del(ssp->Get_User());
Send_WebSocket_Response(conn, Protocol::MakeHallOperationResult("match_stop", true));
return;
}
// 错误请求类型
else
{
Send_WebSocket_Response(conn, Protocol::MakeHallUnknownOperation());
return;
}
}

打开房间请求

当匹配成功进入房间后,从一个websocket连接切换到了另一个websocket连接 将该用户加入在线用户房间管理结构,并将Session设为永久

void WsOpen_Room(websocket_server::connection_ptr conn) // 进入房间的处理
{
Json::Value response;
// 获取Session
session_ptr ssp = Get_Session_From_Cookie(conn);
if(ssp.get() == nullptr)
return;
// 判断是否重复登录(在大厅 | 房间)
if(_online.IsHall(ssp->Get_User()) || _online.IsRoom(ssp->Get_User()))
{
response["optype"] = "room_ready";
response["result"] = false;
response["reason"] = "玩家重复登录";
Send_WebSocket_Response(conn, response);
}
// 判断是否已经创好了该用户的房间
room_ptr roomp = _room.Get_Room_FromUid(ssp->Get_User());
if(roomp.get() == nullptr)
{
response["optype"] = "room_ready";
response["result"] = false;
response["reason"] = "没有找到该用户的房间信息";
Send_WebSocket_Response(conn, response);
}
// 将该用户列入在线用户管理(房间)
_online.EnterRoom(ssp->Get_User(), conn);
// 将Session设为永久
_session.Set_Session_ExpireTime(ssp->Get_Ssid(), SESSION_FOREVER);
// 给客户端返回响应
Send_WebSocket_Response(conn, Protocol::MakeRoomReady(
roomp->GetId(),
ssp->Get_User(),
roomp->GetUser_White(),
roomp->GetUser_Black()));
}

关闭房间请求

关闭房间时,需要将用户移出在线用户房间管理结构,并且因为断开了连接,所以要重新将Session设为有期

void WsClose_Room(websocket_server::connection_ptr conn)
{
// 获取Session
session_ptr ssp = Get_Session_From_Cookie(conn);
if(ssp.get() == nullptr)
return;

// 将该用户移出在线用户的房间管理
_online.ExitRoom(ssp->Get_User());
// 将该用户移出房间
_room.Remove_RoomUser(ssp->Get_User());
// 将Session设置为有期
_session.Set_Session_ExpireTime(ssp->Get_Ssid(), SESSION_TIMEOUT);
}

房间消息请求

房间内的消息请求,都转给房间消息处理总借口,让它自行判断是哪种类型(下棋、退出、聊天)

void WsMessage_Room(websocket_server::connection_ptr conn, websocket_server::message_ptr msg_ptr)
{
Json::Value response;
// 获取Sesssion
session_ptr ssp = Get_Session_From_Cookie(conn);
if(ssp.get() == nullptr)
return;
// 获取房间信息
room_ptr roomp = _room.Get_Room_FromUid(ssp->Get_User());
if(roomp.get() == nullptr)
{
response["optype"] = "unknown";
response["result"] = false;
response["reason"] = "没有找到用户的房间信息";
return Send_WebSocket_Response(conn, response);
}
// 对请求进行反序列化
Json::Value request;
std::string body = msg_ptr->get_payload();
if(Json_Util::Deserialization(body, request) == false)
// 反序列化失败
{
response["optype"] = "unknown";
response["result"] = false;
response["reason"] = "请求解析失败";
return Send_WebSocket_Response(conn, response);
}
// 交给房间请求的总处理接口
return roomp->Handle_Request(request, ssp->Get_User());

}

项目总结

项目名称:在线五子棋对战

项目简介:实现五子棋服务器,能够让用户通过浏览器访问服务器,进行用户的注册,登录,对战匹配,实时对战,实时聊天等功能。

开发环境:AlmaLinux,vim/vscode/g++/gdb/makefile

项目实现:

  • 数据管理模块:基于mysql数据库进行数据管理以及封装数据管理模块实现数据库访问。
  • 网络服务器模块:基于websocketpp库搭建websocket服务器,实现与客户端网络通信。
  • session管理模块:封装session管理,实现http客户端通信状态的维护及身份识别。
  • 在线用户管理模块:对于进入游戏大厅&游戏房间的长连接通信进行管理,实现随时能够获取客户端连接进行消息的主动推送。
  • 游戏房间管理:对于同一个房间中的用户及动作进行处理(对战匹配,下棋,聊天,退出)
  • 对战匹配管理:将所有玩家根据分数进行等级划分,进行不同等级的对战匹配。
  • 业务处理:通过网络通信获取到客户端的请求,提供不同的业务处理

I

赞(0)
未经允许不得转载:网硕互联帮助中心 » 【在线五子棋对战】服务器业务分类请求处理
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!