server.hpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. /*
  2. 服务器模块
  3. 通过对之前所有模块进行整合以及进行服务器搭建,最终封装实现出⼀个gobang_server的服务器模块类,向外提供搭建五⼦棋对战服务器的接⼝。
  4. 达到通过实例化的对象就可以简便的完成服务器搭建的目的
  5. */
  6. #ifndef __SERVER_HPP__
  7. #define __SERVER_HPP__
  8. #include "util.hpp"
  9. #include "db.hpp"
  10. #include "online.hpp"
  11. #include "room.hpp"
  12. #include "matcher.hpp"
  13. #include "session.hpp"
  14. #define WWWROOT "./wwwroot"
  15. typedef websocketpp::server<websocketpp::config::asio> wsserver_t;
  16. class gobang_server {
  17. private:
  18. /*http静态资源请求处理函数(注册界面、登录界面、游戏大厅界面)*/
  19. void file_handler(wsserver_t::connection_ptr conn) {
  20. // 获取http请求对象与请求uri
  21. websocketpp::http::parser::request req = conn->get_request();
  22. std::string uri = req.get_uri();
  23. // 根据uri组合出文件路径,如果文件路径是目录(/结尾)则追加login.html,否则返回相应界面
  24. std::string pathname = _wwwroot + uri;
  25. if(pathname.back() == '/') {
  26. pathname += "login.html";
  27. }
  28. // 读取文件内容,如果文件不存在,则返回404
  29. std::string body;
  30. if(file_util::read(pathname.c_str(), body) == false) {
  31. body += "<html><head><meta charset='UTF-8'/></head><body><h1> 404 Not Found </h1></body></html>";
  32. // 设置响应状态码
  33. conn->set_status(websocketpp::http::status_code::not_found);
  34. }
  35. else conn->set_status(websocketpp::http::status_code::ok);
  36. // 添加响应头部
  37. conn->append_header("Content-Length", std::to_string(body.size()));
  38. // 设置响应正文
  39. conn->set_body(body);
  40. }
  41. /*处理http响应的子功能函数*/
  42. void http_resp(wsserver_t::connection_ptr conn, bool result, websocketpp::http::status_code::value code, const std::string &reason) {
  43. // 设置响应正文及其序列化
  44. Json::Value resp;
  45. std::string resp_body;
  46. resp["result"] = result;
  47. resp["reason"] = reason;
  48. json_util::serialize(resp, resp_body);
  49. // 设置响应状态码,添加响应正文以及正文类型
  50. conn->set_status(code);
  51. conn->append_header("Content-Type", "application/json");
  52. conn->set_body(resp_body);
  53. }
  54. /*http动态功能请求处理函数 -- 用户注册*/
  55. void reg(wsserver_t::connection_ptr conn) {
  56. // 获取json格式的请求正文
  57. std::string req_body = conn->get_request_body();
  58. // 将正文反序列化得到username和password
  59. Json::Value user_info;
  60. if(json_util::deserialize(req_body, user_info) == false) {
  61. return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请求正文格式错误");
  62. }
  63. // 数据库新增用户
  64. if(user_info["username"].isNull() || user_info["password"].isNull()) {
  65. return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名/密码");
  66. }
  67. if(_ut.registers(user_info) == false) {
  68. return http_resp(conn, false, websocketpp::http::status_code::bad_request, "该用户名已被占用");
  69. }
  70. return http_resp(conn, true, websocketpp::http::status_code::ok, "用户注册成功");
  71. }
  72. /*http动态功能请求处理函数 -- 用户登录*/
  73. void login(wsserver_t::connection_ptr conn) {
  74. // 获取请求正文并反序列化
  75. std::string req_body = conn->get_request_body();
  76. Json::Value user_info;
  77. if(json_util::deserialize(req_body, user_info) == false) {
  78. return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请求正文格式错误");
  79. }
  80. if(user_info["username"].isNull() || user_info["password"].isNull()) {
  81. return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名/密码");
  82. }
  83. // 用户登录 -- 登录失败返回404
  84. if(_ut.login(user_info) == false) {
  85. return http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名/密码错误");
  86. }
  87. // 登录成功则为用户创建session信息以及session生命周期
  88. session_ptr ssp = _sm.create_session(user_info["id"].asUInt64());
  89. if(ssp.get() == nullptr) {
  90. return http_resp(conn, false, websocketpp::http::status_code::internal_server_error, "用户会话创建失败");
  91. }
  92. _sm.set_session_expire_time(ssp->get_ssid(), SESSION_TIMEOUT);
  93. // 设置过响应头部 将cookie返回给客户端
  94. std::string cookie_ssid = "SSID=" + std::to_string(ssp->get_ssid());
  95. conn->append_header("Set-Cookie", cookie_ssid);
  96. return http_resp(conn, true, websocketpp::http::status_code::ok, "用户登录成功");
  97. }
  98. /*从http请求头部Cookie中获取指定key对应的value*/
  99. bool get_cookie_val(const std::string &cookie_str, const std::string &key, std::string &val) {
  100. // cookie_str格式:SSID=XXX; path=/XXX
  101. // 先以逗号为分割将cookie_str中的各个cookie信息分割开
  102. std::vector<std::string> cookies;
  103. string_util::split(cookie_str, ";", cookies);
  104. // 再以等号为分割将单个cookie中的key与val分割开,比对查找目标key对应的val
  105. for(const auto cookie : cookies) {
  106. std::vector<std::string> kv;
  107. string_util::split(cookie, "=", kv);
  108. if(kv.size() != 2) continue;
  109. if(kv[0] == key) {
  110. val = kv[1];
  111. return true;
  112. }
  113. }
  114. return false;
  115. }
  116. /*http动态功能请求处理函数 -- 获取用户信息*/
  117. void info(wsserver_t::connection_ptr conn) {
  118. // 通过http请求头部中的cookie字段获取用户ssid
  119. std::string cookie_str = conn->get_request_header("Cookie");
  120. if(cookie_str.empty()) {
  121. return http_resp(conn, false, websocketpp::http::status_code::bad_request, "找不到Cookie信息,请重新登录");
  122. }
  123. std::string ssid_str;
  124. if(get_cookie_val(cookie_str, "SSID", ssid_str) == false) {
  125. return http_resp(conn, false, websocketpp::http::status_code::bad_request, "找不到Session信息,请重新登录");
  126. }
  127. // 根据ssid_str获取用户Session信息
  128. session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
  129. if(ssp.get() == nullptr) {
  130. return http_resp(conn, false, websocketpp::http::status_code::bad_request, "Session已过期,请重新登录");
  131. }
  132. // 通过用户session获取用户id,再根据用户id获取用户详细信息
  133. uint64_t uid = ssp->get_user();
  134. Json::Value user;
  135. if(_ut.select_by_id(uid, user) == false) {
  136. return http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户信息不存在");
  137. }
  138. // 返回用户详细信息
  139. std::string body;
  140. json_util::serialize(user, body);
  141. std::string resp_cookie = "SSID=" + ssid_str;
  142. conn->set_status(websocketpp::http::status_code::ok);
  143. conn->append_header("Content-Type", "application/json");
  144. conn->append_header("Set-Cookie", resp_cookie);
  145. conn->set_body(body);
  146. // 更新用户session过期时间
  147. _sm.set_session_expire_time(ssp->get_ssid(), SESSION_TIMEOUT);
  148. }
  149. private:
  150. /*http请求回调函数*/
  151. void http_callback(websocketpp::connection_hdl hdl) {
  152. wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);
  153. websocketpp::http::parser::request req = conn->get_request();
  154. std::string method = req.get_method();
  155. std::string uri = req.get_uri();
  156. // 根据不同的请求方法和请求路径类型调用不同的处理函数
  157. // 动态功能请求
  158. if(method == "POST" && uri == "/reg") reg(conn);
  159. else if(method == "POST" && uri == "/login") login(conn);
  160. else if(method == "GET" && uri == "/info") info(conn);
  161. // 静态资源请求
  162. else file_handler(conn);
  163. }
  164. /*游戏大厅websocket长连接建立后的响应子函数*/
  165. void game_hall_resp(wsserver_t::connection_ptr conn, bool result, const std::string &reason = "") {
  166. Json::Value resp;
  167. resp["optype"] = "hall_ready";
  168. resp["result"] = result;
  169. // 只有错误才返回错误信息reason
  170. if(result == false) resp["reason"] = reason;
  171. std::string body;
  172. json_util::serialize(resp, body);
  173. conn->send(body);
  174. }
  175. /*wsopen_callback子函数 -- 游戏大厅websocket长连接建立后的处理函数*/
  176. void wsopen_game_hall(wsserver_t::connection_ptr conn) {
  177. // 检查用户是否登录 -- 检查cookie&session信息
  178. // 通过http请求头部中的cookie字段获取用户ssid
  179. std::string cookie_str = conn->get_request_header("Cookie");
  180. if(cookie_str.empty()) {
  181. return game_hall_resp(conn, false, "找不到Cookie信息,请重新登录");
  182. }
  183. std::string ssid_str;
  184. if(get_cookie_val(cookie_str, "SSID", ssid_str) == false) {
  185. return game_hall_resp(conn, false, "找不到Session信息,请重新登录");
  186. }
  187. // 根据ssid_str获取用户Session信息
  188. session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
  189. if(ssp.get() == nullptr) {
  190. return game_hall_resp(conn, false, "Session已过期,请重新登录");
  191. }
  192. // 通过用户session获取用户id
  193. uint64_t uid = ssp->get_user();
  194. // 检查用户是否重复登录 -- 用户游戏大厅长连接/游戏房间长连接是否已经存在
  195. if(_om.is_in_game_hall(uid) == true) {
  196. return game_hall_resp(conn, false, "玩家重复登录");
  197. }
  198. // 将玩家及其连接加入到在线游戏大厅中
  199. _om.enter_game_hall(uid, conn);
  200. // 返回响应
  201. game_hall_resp(conn, true);
  202. // 将用户Session过期时间设置为永不过期
  203. _sm.set_session_expire_time(ssp->get_ssid(), SESSION_FOREVER);
  204. }
  205. /*游戏房间websocket长连接建立后的响应子函数*/
  206. void game_room_resp(wsserver_t::connection_ptr conn, bool result, const std::string &reason,
  207. uint64_t room_id = 0, uint64_t self_id = 0, uint64_t white_id = 0, uint64_t black_id = 0) {
  208. Json::Value resp;
  209. resp["optype"] = "room_ready";
  210. resp["result"] = result;
  211. // 如果成功返回room_id,self_id,white_id,black_id等信息,如果错误则返回错误信息
  212. if(result == true) {
  213. resp["room_id"] = (Json::UInt64)room_id;
  214. resp["uid"] = (Json::UInt64)self_id;
  215. resp["white_id"] = (Json::UInt64)white_id;
  216. resp["black_id"] = (Json::UInt64)black_id;
  217. }
  218. else resp["reason"] = reason;
  219. std::string body;
  220. json_util::serialize(resp, body);
  221. conn->send(body);
  222. }
  223. /*wsopen_callback子函数 -- 游戏房间websocket长连接建立后的处理函数*/
  224. void wsopen_game_room(wsserver_t::connection_ptr conn) {
  225. // 获取cookie&session信息
  226. std::string cookie_str = conn->get_request_header("Cookie");
  227. if(cookie_str.empty()) {
  228. return game_room_resp(conn, false, "找不到Cookie信息,请重新登录");
  229. }
  230. std::string ssid_str;
  231. if(get_cookie_val(cookie_str, "SSID", ssid_str) == false) {
  232. return game_room_resp(conn, false, "找不到Session信息,请重新登录");
  233. }
  234. // 根据ssid_str获取用户Session信息
  235. session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
  236. if(ssp.get() == nullptr) {
  237. return game_room_resp(conn, false, "Session已过期,请重新登录");
  238. }
  239. // 判断用户是否已经处于游戏大厅/房间中了(在创建游戏房间长连接之前,游戏大厅的长连接已经断开了) -- 在线用户管理
  240. if(_om.is_in_game_hall(ssp->get_user()) || _om.is_in_game_room(ssp->get_user())) {
  241. return game_room_resp(conn, false, "玩家重复登录");
  242. }
  243. // 判断游戏房间是否被创建 -- 游戏房间管理
  244. room_ptr rp = _rm.get_room_by_uid(ssp->get_user());
  245. if(rp.get() == nullptr) {
  246. return game_room_resp(conn, false, "找不到房间信息");
  247. }
  248. // 将玩家加入到在线游戏房间中
  249. _om.enter_game_room(ssp->get_user(), conn);
  250. // 返回响应信息
  251. game_room_resp(conn, true, "", rp->_room_id, ssp->get_user(), rp->_white_user_id, rp->_black_user_id);
  252. // 将玩家session设置为永不过期
  253. _sm.set_session_expire_time(ssp->get_ssid(), SESSION_FOREVER);
  254. }
  255. /*websocket长连接建立之后的处理函数*/
  256. void wsopen_callback(websocketpp::connection_hdl hdl) {
  257. // 获取通信连接、http请求对象和请求uri
  258. wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);
  259. websocketpp::http::parser::request req = conn->get_request();
  260. std::string uri = req.get_uri();
  261. // 进入游戏大厅与进入游戏房间需要分别建立websocket长连接
  262. if(uri == "/hall") wsopen_game_hall(conn);
  263. else if(uri == "/room") wsopen_game_room(conn);
  264. }
  265. /*wsclose_callback子函数 -- 游戏大厅websocket长连接断开后的处理函数*/
  266. void wsclose_game_hall(wsserver_t::connection_ptr conn) {
  267. // 获取cookie&session,如果不存在则说明websocket长连接未建立(websocket长连接建立后Session永久存在),直接返回
  268. std::string cookie_str = conn->get_request_header("Cookie");
  269. if(cookie_str.empty()) return;
  270. std::string ssid_str;
  271. if(get_cookie_val(cookie_str, "SSID", ssid_str) == false) return;
  272. session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
  273. if(ssp.get() == nullptr) return;
  274. // 将玩家从游戏大厅移除
  275. _om.exit_game_hall(ssp->get_user());
  276. // 将玩家session设置为定时删除
  277. _sm.set_session_expire_time(ssp->get_ssid(), SESSION_TIMEOUT);
  278. }
  279. /*wsclose_callback子函数 -- 游戏房间websocket长连接断开后的处理函数*/
  280. void wsclose_game_room(wsserver_t::connection_ptr conn) {
  281. // 获取cookie&session,如果不存在直接返回
  282. std::string cookie_str = conn->get_request_header("Cookie");
  283. if(cookie_str.empty()) return;
  284. std::string ssid_str;
  285. if(get_cookie_val(cookie_str, "SSID", ssid_str) == false) return;
  286. session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
  287. if(ssp.get() == nullptr) return;
  288. // 将玩家从在线用户管理的游戏房间中移除
  289. _om.exit_game_room(ssp->get_user());
  290. // 将玩家从游戏房间管理的房间中移除
  291. _rm.remove_room_user(ssp->get_user());
  292. // 设置玩家session为定时删除
  293. _sm.set_session_expire_time(ssp->get_ssid(), SESSION_TIMEOUT);
  294. }
  295. /*websocket长连接断开之间的处理函数*/
  296. void wsclose_callback(websocketpp::connection_hdl hdl) {
  297. // 获取通信连接、http请求对象和请求uri
  298. wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);
  299. websocketpp::http::parser::request req = conn->get_request();
  300. std::string uri = req.get_uri();
  301. // 离开游戏大厅与离开游戏房间需要分别断开websocket长连接
  302. if(uri == "/hall") wsclose_game_hall(conn);
  303. else if(uri == "/room") wsclose_game_room(conn);
  304. }
  305. /*wsmsg_callback子函数 -- 游戏大厅通信处理函数*/
  306. void wsmsg_game_hall(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg) {
  307. // 获取cookie&session,如果不存在则返回错误信息
  308. std::string cookie_str = conn->get_request_header("Cookie");
  309. if(cookie_str.empty()) {
  310. return game_hall_resp(conn, false, "找不到Cookie信息,请重新登录");
  311. }
  312. std::string ssid_str;
  313. if(get_cookie_val(cookie_str, "SSID", ssid_str) == false) {
  314. return game_hall_resp(conn, false, "找不到Session信息,请重新登录");
  315. }
  316. session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
  317. if(ssp.get() == nullptr) {
  318. return game_hall_resp(conn, false, "Session已过期,请重新登录");
  319. }
  320. // 获取请求信息
  321. std::string req_msg_body = msg->get_payload();
  322. Json::Value req_msg;
  323. if(json_util::deserialize(req_msg_body, req_msg) == false) {
  324. return game_hall_resp(conn, false, "请求信息解析失败");
  325. }
  326. // 处理请求信息 -- 开始对战匹配与停止对战匹配
  327. Json::Value resp = req_msg;
  328. std::string resp_body;
  329. // 开始对战匹配请求则将用户加入到匹配队列中,取消对战匹配请求则将用户从匹配队列中移除
  330. if(req_msg["optype"].isNull() == false && req_msg["optype"].asString() == "match_start") {
  331. _mm.add(ssp->get_user());
  332. resp["result"] = true;
  333. json_util::serialize(resp, resp_body);
  334. conn->send(resp_body);
  335. } else if(req_msg["optype"].isNull() == false && req_msg["optype"].asString() == "match_stop") {
  336. _mm.remove(ssp->get_user());
  337. resp["result"] = true;
  338. json_util::serialize(resp, resp_body);
  339. conn->send(resp_body);
  340. } else {
  341. resp["optype"] = "unknown";
  342. resp["result"] = false;
  343. json_util::serialize(resp, resp_body);
  344. conn->send(resp_body);
  345. }
  346. }
  347. /*wsmsg_callback子函数 -- 游戏房间通信处理函数*/
  348. void wsmsg_game_room(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg) {
  349. // 获取cookie&session,如果不存在则返回错误信息
  350. std::string cookie_str = conn->get_request_header("Cookie");
  351. if(cookie_str.empty()) {
  352. return game_room_resp(conn, false, "找不到Cookie信息,请重新登录");
  353. }
  354. std::string ssid_str;
  355. if(get_cookie_val(cookie_str, "SSID", ssid_str) == false) {
  356. return game_room_resp(conn, false, "找不到Session信息,请重新登录");
  357. }
  358. session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
  359. if(ssp.get() == nullptr) {
  360. return game_room_resp(conn, false, "Session已过期,请重新登录");
  361. }
  362. // 获取房间信息
  363. room_ptr rp = _rm.get_room_by_uid(ssp->get_user());
  364. if(rp.get() == nullptr) {
  365. return game_room_resp(conn, false, "找不到房间信息");
  366. }
  367. // 获取请求信息
  368. std::string req_msg_body = msg->get_payload();
  369. Json::Value req_msg;
  370. if(json_util::deserialize(req_msg_body, req_msg) == false) {
  371. return game_room_resp(conn, false, "请求信息解析失败");
  372. }
  373. // 处理请求信息 -- 下棋动作与聊天动作
  374. rp->handler(req_msg);
  375. }
  376. /*websocket长连接建立后通信的处理函数*/
  377. void wsmsg_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg) {
  378. // 获取通信连接、http请求对象和请求uri
  379. wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);
  380. websocketpp::http::parser::request req = conn->get_request();
  381. std::string uri = req.get_uri();
  382. // 游戏大厅通信处理与游戏房间通信处理
  383. if(uri == "/hall") wsmsg_game_hall(conn, msg);
  384. else if(uri == "/room") wsmsg_game_room(conn, msg);
  385. }
  386. public:
  387. /*成员初始化与服务器回调函数设置*/
  388. gobang_server(const std::string &host, const std::string &user, const std::string &passwd, \
  389. const std::string db = "gobang", uint16_t port = 4106)
  390. : _wwwroot(WWWROOT), _ut(host, user, passwd, db, port), _sm(&_wssrv), _rm(&_ut, &_om), _mm(&_ut, &_om, &_rm) {
  391. // 设置日志等级
  392. _wssrv.set_access_channels(websocketpp::log::alevel::none);
  393. // 初始化asio调度器
  394. _wssrv.init_asio();
  395. // 设置回调函数
  396. _wssrv.set_http_handler(std::bind(&gobang_server::http_callback, this, std::placeholders::_1));
  397. _wssrv.set_open_handler(std::bind(&gobang_server::wsopen_callback, this, std::placeholders::_1));
  398. _wssrv.set_close_handler(std::bind(&gobang_server::wsclose_callback, this, std::placeholders::_1));
  399. _wssrv.set_message_handler(std::bind(&gobang_server::wsmsg_callback, this, std::placeholders::_1, std::placeholders::_2));
  400. }
  401. /*启动服务器*/
  402. void start(uint16_t port) {
  403. // 设置监听端口
  404. _wssrv.listen(port);
  405. _wssrv.set_reuse_addr(true);
  406. // 开始获取新连接
  407. _wssrv.start_accept();
  408. // 启动服务器
  409. _wssrv.run();
  410. }
  411. private:
  412. std::string _wwwroot; // 静态资源根目录
  413. user_table _ut; // 用户数据管理模块句柄
  414. session_manager _sm; // 用户session信息管理模块句柄
  415. online_manager _om; // 用户在线信息管理模块句柄
  416. room_manager _rm; // 游戏房间管理模块句柄
  417. matcher _mm; // 用户对战匹配管理模块句柄
  418. wsserver_t _wssrv; // websocketpp::server 句柄
  419. };
  420. #endif