成都网站建设公,wordpress判断文章id,网站建设需求说明书模板,古德设计网gooood官网目录
1.项目介绍
2.开发环境
3.核心技术
4.环境搭建
5.WebSocketpp介绍
5.1WebSocketpp是什么
5.2为什么使用WebSocketpp
5.3原理解析#xff1a;
5.4WebSocketpp主要特性
6.WebSocketpp使用
7.JsonCpp使用
8.MySQL API
9.项目模块设计以及流程图
10.封装日志宏…目录
1.项目介绍
2.开发环境
3.核心技术
4.环境搭建
5.WebSocketpp介绍
5.1WebSocketpp是什么
5.2为什么使用WebSocketpp
5.3原理解析
5.4WebSocketpp主要特性
6.WebSocketpp使用
7.JsonCpp使用
8.MySQL API
9.项目模块设计以及流程图
10.封装日志宏
11.封装util工具类
12.数据管理模块的实现
12.1数据库的设计
12.2数据库类(user_table)的设计
13.在线用户管理模块
14.游戏房间管理模块
14.1游戏房间类的设计
14.2游戏房间管理类的设计
15.session管理模块
15.1session是什么
15.2session工作原理
15.3session模块设计
15.4session管理模块设计
16.玩家匹配管理模块
16.1玩家匹配队列
16.2玩家匹配管理模块
17.服务器模块
17.1通信接口的设计协议定制
17.1.1静态资源的请求
17.1.2注册用户
17.1.3用户登录
17.1.4获取客户端信息
17.1.5websocket长连接协议切换请求进入游戏大厅
17.1.6开始对战匹配
17.1.7停⽌匹配
17.1.8websocket长连接协议切换请求进入游戏房间
17.1.9走棋
17.1.9聊天
17.2服务器模块实现
18.前端模块
18.1注册页面
18.2登录页面
18.3游戏大厅页面
18.4游戏房间页面
19.项目扩展
20.项目源码 1.项目介绍 此项目实现了一个网页版的五子棋对战的游戏玩家可以通过浏览器访问服务器来实现人与人之间的对战主要支持一下功能 1.用户管理实现用户注册用户登录展示用户的信息包括分数比赛场次等 2.匹配对战两个用户在相同的分数段进行匹配对战胜利一方加分失败扣分 3.聊天功能实现两个玩家在下棋的同时还能支持实时聊天的功能 项目成果展示
注册页面 登录页面 登录成功之后游戏大厅页面 匹配进入游戏房间并进行下棋和聊天的功能展示 比赛获胜以及对局中实时聊天展示 2.开发环境 LinuxCentos-7.6 VSCode/Vim g/gdb Makefile 3.核心技术 HTTP/WebSocket 用于网络通信 Websocketpp 用于服务器可以主动向客户端发送信息 JsonCpp 用于序列化和反序列化 Mysql 用于保存用户的数据 C11 包装器bind智能指针等的使用 BlockQueue 使用list来封装原因后面讲述 HTML/CSS/JS/AJAX 用于前端页面的展示 4.环境搭建 通过yum来安装各种需要使用到的工具 gcc/g编译器、gdb调试器、git、boost库、jsoncpp库、MySQL、cmake工具 5.WebSocketpp介绍 5.1WebSocketpp是什么 WebSocket是从HTML5开始⽀持的⼀种⽹⻚端和服务端保持⻓连接的消息推送机制。 5.2为什么使用WebSocketpp 传统的web程序都是属于⼀问⼀答的形式即客⼾端给服务器发送了⼀个HTTP请求服务器给客⼾端返回⼀个HTTP响应。这种情况下服务器是属于被动的⼀⽅如果客⼾端不主动发起请求服务器就⽆法主动给客户端响应。 像⽹⻚即时聊天或者我们做的五⼦棋游戏这样的程序都是⾮常依赖消息推送的即需要服务器主动推动消息到客⼾端。如果只是使⽤原⽣的HTTP协议要想实现消息推送⼀般需要通过轮询的⽅式实现⽽轮询的成本⽐较⾼并且也不能及时的获取到消息的响应。 基于上述两个问题就产⽣了WebSocket协议。WebSocket更接近于TCP这种级别的通信⽅式⼀旦连接建⽴完成客⼾端或者服务器都可以主动的向对⽅发送数据。 这是http下的通信模式 这是websocket的通信模式 从上图就可以知道websocket下的通信方式是比http的效率高很多的 5.3原理解析 WebSocket协议本质上是⼀个基于TCP的协议。为了建⽴⼀个WebSocket连接客户端浏览器⾸先要向服务器发起⼀个HTTP请求这个请求和通常的HTTP请求不同包含了⼀些附加头信息通过这个附加头信息完成握⼿过程并升级协议的过程。 协议升级流程 5.4WebSocketpp主要特性 事件驱动的接口 ⽀持HTTP/HTTPS、WS/WSS、IPv6 灵活的依赖管理—Boost库/C11标准库 可移植性Posix/Windows、32/64bit、Intel/ARM 线程安全 6.WebSocketpp使用 6.1常见接口使用 namespace websocketpp {typedef lib::weak_ptrvoid connection_hdl;template typename configclass endpoint : public config::socket_type {typedef lib::shared_ptrlib::asio::steady_timer timer_ptr;typedef typename connection_type::ptr connection_ptr;typedef typename connection_type::message_ptr message_ptr;typedef lib::functionvoid(connection_hdl) open_handler;typedef lib::functionvoid(connection_hdl) close_handler;typedef lib::functionvoid(connection_hdl) http_handler;typedef lib::functionvoid(connection_hdl,message_ptr)
message_handler;/* websocketpp::log::alevel::none 禁⽌打印所有⽇志*/void set_access_channels(log::level channels);/*设置⽇志打印等级*/void clear_access_channels(log::level channels);/*清除指定等级的⽇志*/ /*设置指定事件的回调函数*/void set_open_handler(open_handler h);/*websocket握⼿成功回调处理函数*/ void set_close_handler(close_handler h);/*websocket连接关闭回调处理函数*/ void set_message_handler(message_handler h);/*websocket消息回调处理函数*/ void set_http_handler(http_handler h);/*http请求回调处理函数*//*发送数据接⼝*/void send(connection_hdl hdl, std::string payload,
frame::opcode::value op);void send(connection_hdl hdl, void* payload, size_t len,
frame::opcode::value op);/*关闭连接接⼝*/void close(connection_hdl hdl, close::status::value code, std::string
reason);/*获取connection_hdl 对应连接的connection_ptr*/connection_ptr get_con_from_hdl(connection_hdl hdl);/*websocketpp基于asio框架实现init_asio⽤于初始化asio框架中的io_service调度器*/void init_asio();/*设置是否启⽤地址重⽤*/void set_reuse_addr(bool value);/*设置endpoint的绑定监听端⼝*/void listen(uint16_t port);/*对io_service对象的run接⼝封装⽤于启动服务器*/std::size_t run();/*websocketpp提供的定时器以毫秒为单位*/timer_ptr set_timer(long duration, timer_handler callback);};template typename configclass server : public endpointconnectionconfig,config {/*初始化并启动服务端监听连接的accept事件处理*/void start_accept();}template typename configclass connection: public config::transport_type::transport_con_type, public config::connection_base{/*发送数据接⼝*/error_code send(std::stringpayload, frame::opcode::value
opframe::opcode::text);/*获取http请求头部*/std::string const get_request_header(std::string const key)/*获取请求正⽂*/std::string const get_request_body();/*设置响应状态码*/void set_status(http::status_code::value code);/*设置http响应正⽂*/void set_body(std::string const value);/*添加http响应头部字段*/void append_header(std::string const key, std::string const val);/*获取http请求对象*/request_type const get_request();/*获取connection_ptr 对应的 connection_hdl */connection_hdl get_handle();};namespace http {namespace parser {class parser {std::string const get_header(std::string const key)}class request : public parser {/*获取请求⽅法*/std::string const get_method()/*获取请求uri接⼝*/std::string const get_uri()};}};namespace message_buffer {/*获取websocket请求中的payload数据类型*/frame::opcode::value get_opcode();/*获取websocket中payload数据*/std::string const get_payload();};namespace http {namespace status_code {enum value {uninitialized 0,continue_code 100,switching_protocols 101,ok 200,created 201,accepted 202,non_authoritative_information 203,no_content 204,reset_content 205,partial_content 206,multiple_choices 300,moved_permanently 301,found 302,see_other 303,not_modified 304,use_proxy 305,temporary_redirect 307,bad_request 400,unauthorized 401,payment_required 402,forbidden 403,not_found 404,method_not_allowed 405,not_acceptable 406,proxy_authentication_required 407,request_timeout 408,conflict 409,gone 410,length_required 411,precondition_failed 412,request_entity_too_large 413,request_uri_too_long 414,unsupported_media_type 415,request_range_not_satisfiable 416,expectation_failed 417,im_a_teapot 418,upgrade_required 426,precondition_required 428,too_many_requests 429,request_header_fields_too_large 431,internal_server_error 500,not_implemented 501,bad_gateway 502,service_unavailable 503,gateway_timeout 504,http_version_not_supported 505,not_extended 510,network_authentication_required 511};}}
} 这是我们之后使用到的主要的接口其中status_code中主要使用到的是ok:200 、bad_request:400 、not found:404 通过了解上面的接口我们可以做出一个简单的服务器 实现步骤1.创建服务器设置日志等级设置调度器asio然后设置各个websocket的各个回调函数 因为传入的是一个包装器所以这里需要使用的是C11中bind开绑定参数解决。例如连接成功和断开以及http返回页面还有信息的处理这里简单处理用于测试) 2.监听建立新连接启动服务器 #include iostream
#include string
#include functional
#include websocketpp/server.hpp
#include websocketpp/config/asio_no_tls.hpp
using namespace std;using wsserver_t websocketpp::serverwebsocketpp::config::asio;void http_callback(wsserver_t* wsserver,websocketpp::connection_hdl hdl)
{//给用户返回一个hello world界面wsserver_t::connection_ptr conn wsserver-get_con_from_hdl(hdl);std::coutbody: conn-get_request_body()std::endl;websocketpp::http::parser::request req conn-get_request();std::couturi: req.get_uri()std::endl;std::coutmethod: req.get_method()std::endl;std::string body htmlbodyh1Hello World/h1/body/html;conn-set_body(conn-get_request_body());//conn-set_body(body);//conn-append_header(Content-Type,text/html);conn-set_status(websocketpp::http::status_code::ok);
}
void open_callback(wsserver_t* wsserver,websocketpp::connection_hdl hdl)
{cout握手成功endl;
}void close_callback(wsserver_t* wsserver,websocketpp::connection_hdl hdl)
{cout连接断开endl;
}void message_callback(wsserver_t* wsserver,websocketpp::connection_hdl hdl,wsserver_t::message_ptr msg)
{wsserver_t::connection_ptr conn wsserver-get_con_from_hdl(hdl);std::coutwsserver message: msg-get_payload()std::endl;std::string resp client say: msg-get_payload();conn-send(resp,websocketpp::frame::opcode::text);
}int main()
{//使用websocket创建服务器wsserver_t wsserver;//设置日志等级wsserver.set_access_channels(websocketpp::log::alevel::none);//设置调度器asiowsserver.init_asio();//设置回调函数wsserver.set_http_handler(bind(http_callback,wsserver,std::placeholders::_1));wsserver.set_open_handler(bind(open_callback,wsserver,std::placeholders::_1));wsserver.set_close_handler(bind(close_callback,wsserver,std::placeholders::_1));wsserver.set_message_handler(bind(message_callback,wsserver,std::placeholders::_1,std::placeholders::_2));//监听wsserver.listen(3389);//建立新连接wsserver.start_accept();//启动服务器wsserver.run();return 0;
} 我们很容易就可以获得到通过上述代码获取到一个我们返回的html页面 html代码 meta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0titleTest Websocket/title/headbodyinput typetext idmessagebutton idsubmit提交/buttonscriptlet websocket new WebSocket(ws://43.139.37.242:3389);// 处理连接打开的回调函数websocket.onopen function() {console.log(连接建立);}// 处理收到消息的回调函数// 控制台打印消息websocket.onmessage function(e) {console.log(收到消息: e.data);}// 处理连接异常的回调函数websocket.onerror function() {console.log(连接异常);}// 处理连接关闭的回调函数websocket.onclose function() {console.log(连接关闭);}// 实现点击按钮后, 通过 websocket实例 向服务器发送请求let input document.querySelector(#message);let button document.querySelector(#submit);button.onclick function() {console.log(发送消息: input.value);websocket.send(input.value);}/script
/body
/html 7.JsonCpp使用 Json是什么Json 是⼀种数据交换格式它采⽤完全独⽴于编程语⾔的⽂本格式来存储和表⽰数据。 数据存储格式 对象使⽤花括号 {} 括起来的表⽰⼀个对象 数组使⽤中括号 [] 括起来的表⽰⼀个数组 字符串使⽤常规双引号 括起来的表⽰⼀个字符串 数字包括整形和浮点型直接使⽤ JsonCpp库中提供的数据对象表示 class Json::Value{Value operator(const Value other); //Value重载了[]和因此所有的赋值和获取数据都可以通过Value operator[](const std::string key);//简单的⽅式完成 val[name] xx;Value operator[](const char* key);Value removeMember(const char* key);//移除元素const Value operator[](ArrayIndex index) const; //val[score][0]Value append(const Value value);//添加数组元素val[score].append(88); ArrayIndex size() const;//获取数组元素个数 val[score].size();bool isNull(); //⽤于判断是否存在某个字段std::string asString() const;//转string string name
val[name].asString();const char* asCString() const;//转char* char *name
val[name].asCString();Int asInt() const;//转int int age val[age].asInt();float asFloat() const;//转float float weight val[weight].asFloat(); bool asBool() const;//转 bool bool ok val[ok].asBool();
}; JsonCpp提供的序列化和反序列化接口 序列化接口 class JSON_API StreamWriter {virtual int write(Value const root, std::ostream* sout) 0;
}
class JSON_API StreamWriterBuilder : public StreamWriter::Factory {virtual StreamWriter* newStreamWriter() const;
} 反序列化接口 class JSON_API CharReader {virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, std::string* errs) 0;
}
class JSON_API CharReaderBuilder : public CharReader::Factory {virtual CharReader* newCharReader() const;
} 测试用例编写 序列化步骤构造一个streamwritebuilder对象来构造一个streamwrite对象通过steamwrite中的write函数进行序列化 反序列化步骤构造一个charreadbuilder对象来构造一个charread对象通过steamwrite中的read函数进行反序列化最后输出结果(如果是数组要获取它的长度再进行打印) #include iostream
#include string
#include sstream
#include vector
#include jsoncpp/json/json.hstd::string serialize()
{// 使用json::value对象存储数据Json::Value root;root[姓名] 小明;root[年龄] 20;root[成绩].append(90);root[成绩].append(80);root[成绩].append(70);// 构建一个StreamWriterBuilder对象Json::StreamWriterBuilder swb;// 通过StreamWriterBuilder来构建一个StreamWriter对象Json::StreamWriter *sw swb.newStreamWriter();// 使用json进行序列化std::stringstream ss;int ret sw-write(root, ss);if (ret ! 0){std::cerr json serilize failed! std::endl;return ;}std::cout 序列化结果: ss.str() std::endl;delete sw;return ss.str();
}//反序列化
void unserialize(std::string s)
{//构建一个CharReaderBuilder对象Json::CharReaderBuilder crb;//通过CharReaderBuilder构建出一个CharReader对象Json::CharReader* cb crb.newCharReader();//构建json::value对象来接受反序列化结果Json::Value root;std::string err;//反序列化bool ret cb-parse(s.c_str(),s.c_str()s.size(),root,err);if(ret false){std::cerrunserialize failed!std::endl;return;}//输出结果 std::cout姓名root[姓名].asString()std::endl;std::cout年龄root[年龄].asInt()std::endl;int sz root[成绩].size();for(int i 0;isz;i){std::cout成绩root[成绩][i]std::endl;}delete cb;
}int main()
{std::string s serialize();unserialize(s);return 0;
}结果 8.MySQL API MySQL 是 C/S 模式 C API 其实就是⼀个 MySQL 客⼾端提供⼀种⽤ C 语⾔代码操作数据库的流程 其中的主要操作 1.初始化MySQL操作句柄 2.连接服务器对各个数据进行传入 3.设置字符集为了保证编码格式相同统一使用utf8编码格式 4.进行MySQL的操作这里增删改操作都是对数据库的修改我们可以通过数据库来查看的到结果但是查就不行了因为查是需要保存结果到本地的 5.如果有查那么就要保存结果到本地然后获取本地行数和列数然后进行本地的查看即可 6.最后一定要记得关闭MySQL操作句柄以防止资源泄露。 #include stdio.h
#include string.h
#include mysql/mysql.h//定义连接服务器所需要的宏
#define HOST 127.0.0.1
#define USER root
#define PASSWD NULL
#define DBNAME gobang
#define PORT 3306int main()
{//1.初始化mysql操作句柄MYSQL* mysql mysql_init(NULL);if(mysql NULL){printf(mysql init fail\n);mysql_close(mysql);return -1;}//2.连接服务器if(mysql_real_connect(mysql,HOST,USER,PASSWD,DBNAME,PORT,NULL,0) NULL){printf(mysql connect fail\n);mysql_close(mysql);return -1;}//3.设置字符集if(mysql_set_character_set(mysql,utf8) ! 0){printf(set character fail\n);mysql_close(mysql);return -1;} //4.连接数据库在第二步已经完成//5.进行数据库的增删改操作const char* sql insert into stu values(1,18,曹操);//const char* sql update stu set age20 where id 1;//const char* sql delete from stu where id 1;//const char* sql select * from stu;//const char* sql select * from user;int ret mysql_query(mysql,sql);if(ret ! 0){printf(%s ,sql);printf(mysql query fail: %s,mysql_errno(mysql));mysql_close(mysql);return -1;}// //6.如果有读那么就先保存查询结果到本地// MYSQL_RES* res mysql_store_result(mysql);// if(res NULL)// {// printf(mysql store result fail\n);// mysql_close(mysql);// return -1;// }// //7.获取结果的行列数// int row mysql_num_rows(res);// int field mysql_num_fields(res);// //8.遍历结果集得到结果// for(int i 0;irow;i)// {// //获取列// MYSQL_ROW rows mysql_fetch_row(res);// for(int j 0;jfield;j)// {// printf(%s\t,rows[j]);// }// printf(\n);// }// //9.释放结果集// mysql_free_result(res);// //10.释放mysql操作句柄// mysql_close(mysql);return 0;
} 9.项目模块设计以及流程图
模块设计 首先我们应该分成3大模块 1.数据管理模块我们在管理用户数据的时候我们需要知道用户的游戏信息必须要保存到MySQL数据库中以便访问 2.前端模块通过JS实现前端页面(注册、登录、游戏大厅、游戏房间)动态控制以及与服务器进行通信来完成前后端的联合。 3.业务处理模块(后端处理)通过搭建WebSocket服务器也客户端进行通信接受到客户端的请求并进行业务处理。 业务处理模块中的子模块 1.网络通信模块通过WebSocket以及 HTTP服务器的搭建来提供网络通信的功能以便实现前后端完成业务处理这个模块是存在是必然的没有这个模块无法完成网络通信。 2.会话管理模块对客户端的连接进行cookie以及session管理实现对用户是否登录进行管理如果没有登录或者在http短连接中超时那么就会销毁会话保证客户端的身份识别这个模块可以帮我们确定那些玩家是已经登录过了同时可以帮我们在用户退出页面时更新session销毁时间保证session的合理性。 3.在线管理模块可以获取到该用户是否在线方便我们在进入游戏大厅和游戏房间的管理可以提供用户是否在线以及用户的连接。 4.房间管理模块在匹配 成功后为用户创建游戏房间提供五子棋对战以及实时聊天的供功能。 5.用户匹配模块在不同的分数段中有不用的匹配队列为分数在同一个阶段的玩家创建房间并加入房间。 1.用户角度的流程 2.站在服务器角度的流程图
10.封装日志宏 到这里就正式开始了项目的编写了首先最重要的就是日志输出因为我们在测试项目的时候可能并不清楚到底哪里有问题我们就可以通过日志打印来确定大概的位置方便调试 如何封装 我们需要知道以下的信息时间、哪个文件、行数 时间我们可以使用time函数来获取时间那么如何把时间分割成时分秒呢已经有库给我们提供了一个函数strftime 怎么获取到这个struct tm的结构体呢我们可以通过localtime函数来获取 根据这两个函数我们就可以输出时间那文件和行数呢 在C语言中预定义符号就提供了方法 同时我们还可以设置日志等级实现如下 #pragma once
#include cstdio
#include ctime#define INF 0
#define DEG 1
#define ERR 2
#define DEFAULT_LEVEL INF#define LOG(level,format,...) do{\if(DEFAULT_LEVEL level) break;\time_t t time(nullptr); \struct tm* st localtime(t);\char buf[32] {0};\strftime(buf,31,%H-%M-%S,st);\fprintf(stdout,[%s-%s-%d] format \n,buf,__FILE__,__LINE__,##__VA_ARGS__);\
}while(0)#define ILOG(format,...) LOG(INF,format,##__VA_ARGS__);
#define DLOG(format,...) LOG(DEG,format,##__VA_ARGS__);
#define ELOG(format,...) LOG(ERR,format,##__VA_ARGS__); 11.封装util工具类 util工具类主要是为了实现在项目中所需要使用的到的工具例如MySQL、Json、string、file MySQL用来存储用户的数据Json用来进行序列化和反序列化string用于字符串切割(这个后面会使用到),file用于文件的读取这里主要是在进行前后端交付的时候就可以读取本地文件资源返回给前端。这里我们工具类的类成员函数都要设计成static因为工具类是提供给外部使用的设计成静态就可以不实例化出对象就可以直接使用该函数了。 MySQL工具类设计接口 1.完成实现创建MySQL的操作句柄通过传入多个MySQL必须参数其中主要进行初始化mysql操作句柄连接服务器设置字符集最后返回mysql操作句柄。 static MYSQL *mysql_create(const std::string host, const std::string username, const std::string password,const std::string dbname, uint16_t port) 2.进行MySQL的CURD操作主要使用一个函数——mysql_query我们通过sql语言和mysql操作句柄就可以实现 static bool mysql_exec(MYSQL *mysql, const std::string sql) 3.MySQL操作句柄的销毁防止资源泄露 static void mysql_destroy(MYSQL *mysql) Json工具类设计接口 1.完成序列化 通过工厂类对象创建StreamWriter然后使用write进行str序列化 static bool serialize(const Json::Value root, std::string str) 2.完成反序列化通过工厂类对象创建CharReader然后通过prase进行反序列化 static bool unserialize(const std::string s, Json::Value root) string工具类设计接口 1.完成字符串的分割给定一个分割串对string中进行分割放入一个vectorstring容器中 要考虑重复字符的情况 static int spilt(const std::string str,const std::string sep,std::vectorstd::string res) file工具类设计接口 1.读取文件获取文件路径然后获取文件大小通过C文件流来读取文件最后关闭文件 static bool read(const std::string filename,std::string body) 实现方式见后面源码中的util.hpp文件中 12.数据管理模块的实现 数据管理模块主要负责对于数据库中数据进⾏统⼀的增删改查管理其他模块要对数据操作都必须通过数据管理模块完成。 12.1数据库的设计 1.首先必须要有用户idid可以设置成主键并且是自增的。 2.然后我们必须要设计名字名字当然要唯一设置成唯一键。 3.还有用户密码以上内容都是不能为空的 4.我们需要设计用户得分而得分的初始值我们可以设计成1000每赢一局得30分输一局扣30分 5.总场数以供用户知道自己对局的场数 6.胜利场次让用户知道自己赢了多少局 下面3个数据我们可以在初始化的时候完成 12.2数据库类(user_table)的设计 功能接口设计 1.我们需要通过名字或者id来查找数据中的用户信息因此我们需要select_by_name和select_by_id函数来获取用户信息 select_by_name方法实现:通过名字来组织sql语言进行数据库的访问通过获取到用户的信息然后通过Json::Value对象来接收 bool select_by_name(const std::string name, Json::Value user) select_by_id方法实现:通过id来组织sql语言进行数据库的访问通过获取到用户的信息然后通过Json::Value对象来接收 bool select_by_id(uint64_t id, Json::Value user) 2.我们在注册的时候需要新增用户所以我们需要向数据库中插入用户 insert实现方法通过参数传递进来的Json::Value对象中的用户名和密码来组织sql语言向数据库进行插入 bool insert(Json::Value user) 3.我们在登录的时候查看用户是否在数据库中所以我们login函数来查看是否有用户数据 login函数实现方法通过参数传递进来的Json::Value对象中的用户名和密码来组织sql语言来查询数据库如果不存在那么就返回错误如果存在那么就把数据库中全部用户信息返回 bool login(Json::Value user) 4.在进行游戏过后肯定要输的一方或者赢的一方肯定要更新数据那么就需要两个函数来更新数据库 win函数实现方法通过传入的id组织sql语言来对数据库修改 bool win(uint64_t id) lose函数实现方法通过传入的id组织sql语言来对数据库修改 bool lose(uint64_t id) 其中赢了就增加30分胜利场次加1总场次加1失败就扣30分胜利场次不变总场次加1 13.在线用户管理模块 在线用户管理是对于当前游戏⼤厅和游戏房间中的用户进⾏管理主要是建⽴起用户与Socket连接的映射关系。 这个模块的作用是1.能够根据用户信息找到能与用户进行通信socket连接这样可以和用户进行通信。 2.同时可以判断一个用户是否在线或者用户是否已经掉线 设计思想 我们既然需要游戏大厅和游戏房间的管理那么我们就可以使用unordered_map来对用户id和用户的连接关联起来这样就可以通过用户id快速找到用户的连接但是数据结构使用时并不是线程安全的我们我们需要锁来对操作数据时进行保护 成员函数设计 1.当用户进入游戏大厅或者游戏房间的时候我们需要将其id和连接关联起来 我们只需要将用户id和用户的连接使用unordered_map关联起来即可 void enter_game_hall(uint64_t id, wsserver_t::connection_ptr conn)
void enter_game_room(uint64_t id, wsserver_t::connection_ptr conn) 2.当用户退出游戏大厅或者游戏房间的时候我们需要将id和关联解除 我们可以使用id来取消它们的关联 void exit_game_hall(uint64_t id)
void exit_game_room(uint64_t id) 3.我们需要判断一个用户是否在游戏大厅或者游戏房间中 通过id来遍历unordered_map如果找得到就在其中找不到就不在 bool is_in_game_hall(uint64_t id)
bool is_in_game_room(uint64_t id) 4.当我们需要某个用户的连接时我们可以通过用户id来获取用户连接 查找unordered_map找到就返回找不到返回空的智能指针对象 wsserver_t::connection_ptr get_conn_from_hall(uint64_t id)
wsserver_t::connection_ptr get_conn_from_room(uint64_t id) 14.游戏房间管理模块 14.1游戏房间类的设计 游戏房间主要针对匹配成功的玩家建立一个关联关系一个房间中的任意动作都广播给房间中的所有用户其中房间中的动作有两个1.五子棋对战 2.实时聊天 设计思想(类成员的设计) 1.每个房间一定有一个房间的id用于管理然后我们需要设计游戏房间的状态这样可以标识游戏是处理游戏进行中还是游戏已经结束这个有利于其他地方的判读。 2.在游戏房间中我们有两个用户一个白棋用户另一个黑棋用户那么我们是一定是需要两个id即白棋用户id和黑棋用户id。 3.这里我们需要添加一个游戏房间的总用户目的是为了保证如果两个人都退出了游戏房间那么游戏房间就可以销毁了。如果还有人没有退出游戏房间那房间就不能销毁。 4.既然是五子棋那么我们肯定是需要一个二维数组来充当棋盘。 5.那我们有没有用到其他的模块呢当然有我们需要知道游戏房间中的用户和连接这样才能保证在下棋或者聊天的时候能发送给它们。 6.我们还需要使用到数据库管理模块因为在下棋结束之后一定是有人胜利并且有人失败的所以我们在游戏结束之后要更新数据库的数据。 因此类成员如下 类成员函数的设计 1.首先我们肯定是需要知道游戏房间的id以及白棋和黑棋用户的id所以我们需要设计函数让外部能访问这些成员另外外部还需要知道这个房间中还有多少人在房间管理中就可以判断是否需要销毁房间了。 uint64_t id()
room_statu statu()
int player_count()
uint64_t get_white_user()
uint64_t get_black_user() 2.我们可以通过成员函数来添加白棋和黑棋用户这样就可以通过房间管理模块来帮我们完成用户的添加 void add_white_user(uint64_t id)
void add_black_user(uint64_t id) 3.我们是一定需要处理用户下棋动作的通过用户的请求来处理下棋动作 Json::Value handler_chess(Json::Value req) 实现步骤以及细节处理 3.1对方掉线我方不战而胜 3.2处理当前位置是否已经有棋子(这步其实可以放在前端页面来做) 3.3判断当前的是否已经有人胜利。 在3.3中我们需要单独封装一个函数来判断当前是否有人胜利。那如果去判断 我们可以根据当前位置来判断横排、纵列、正斜、反斜来判断判断方式如下 我们就可以通过一个函数就可以判断输赢这里给出判断输赢的代码 // 判断是否有获胜情况bool five(int row, int col, int row_off, int col_off, int color){int rows row row_off;int cols col col_off;int count 1;while (rows BROADROW rows 0 cols BROADCOL cols 0 _broad[rows][cols] color){count;// 向后偏移rows row_off;cols col_off;}// 另外一个方向rows row - row_off;cols col - col_off;while (rows BROADROW rows 0 cols BROADCOL cols 0 _broad[rows][cols] color){count;// 向前偏移rows - row_off;cols - col_off;}return count 5;} 至于判断获胜我们只需要分别对横排、纵列、正斜、反斜分别校验即可。最后我们返回一个响应。 4.如果正在游戏中突然有玩家退出游戏那么我们就需要对这种情况进行处理 我们需要一个退出游戏的处理方法我们需要拿到退出用户的id这样才能判断哪个人输赢最后广播给房间中的所有人最后记得将房间状态设置成结束以便后续处理。 void hander_exit(uint64_t uid) 5.处理房间中聊天我们拿到一个信息我们只要判断信息中是否有敏感词如果有就不能发送最后返回一个响应。 Json::Value handler_chat(Json::Value req) 6.我们需要一个综合处理所有请求的接口在我们拿到一个请求时需要校验房间号是否正确我们可以通过查看其中第一个optype字段这里是协议定制后面会详细说明这个字段是用于判断当前请求是什么请求下棋还是聊天还是其他根据不用的请求来进行不同的处理。最后将处理的结果广播给房间中的所有人。 void hander_request(Json::Value req) 7.我们需要一个广播操作对一个已经处理好的用户的响应我们需要通知房间中的所有人。 我们要将响应反序列化并通过在线管理模块获得用户的连接通过连接发送给用户。 void broad_cast(Json::Value resp) 14.2游戏房间管理类的设计 类的设计类成员函数 1.这个类主要是对所有房间进行管理因此我们需要设计的一个关联房间id和房间的指针的关联这样我们可以通过房间id快速找到房间从而对房间进行操作 2.另外我们需要建立用户id和房间id的关联因为我们肯定是会用到通过用户id来查找房间的操作而我们本来建立了房间id和房间指针的联系这里我们只需要再使用一个unordered_map来映射用户id和房间id这样就可以把这三者的关系建立起来。 3.因为需要使用unorderded_map为了保证线程安全需要设计锁 4.房间id是不断增加的我们需要一个next_rid让其不断自增保证创建的每个房间id不同 5.在我们创建房间的时候是需要查看该用户是否在游戏大厅中如果该用户没有游戏大厅中那么我们就不能为其创建游戏房间。所以我们需要在线用户管理模块的指针来进行操作所以需要online_user这个成员 6.因为我们在创建房间时需要使用到数据库所以我们有数据库管理模块的指针。 所以我们需要以下成员 类成员函数的设计以及步骤实现 1.我们必须提供一个创建房间的成员函数通过两个用户id来创建一个房间返回房间指针这里所有的指针都采用智能指针防止资源泄露。 1.1首先判断用户是否在线如果不在线就不能为其创建房间 1.2创建房间将两个用户添加到房间中 1.3将房间信息管理起来也就是维护好哈希表。 1.4最后一定要每次next_rid room_ptr room_create(uint64_t uid1, uint64_t uid2) 2.我们说肯定需要通过用户id或者房间id来查找房间 2.1先加锁保证线程安全2.2通过哈希表来查找房间如果是用户id查找就需要查找两次。 room_ptr get_room_by_rid(uint64_t rid)
room_ptr get_room_by_uid(uint64_t uid) 3.当我们要销毁房间时需要使用房间id来销毁房间这里相当于删除哈希表中的信息 3.1首先我们要获取到房间指针 3.2得到用户信息并去除用户和房间id的关联 3.3去除房间id和房间的关联。 void destroy_room(uint64_t rid) 4.当有一方用户退出时那么我们要讲该用户从房间中移除所以我们需要提供一个通过用户id来将该用户移除房间的函数 4.1获取房间信息 4.2通过房间指针调用房间类中的handler_exit函数进行退出这里前面已经写过了 4.3将房间中用户数量--这里不需要做前面调用的函数已经处理了。如果用户数量为0就销毁房间。 void delete_room_user(uint64_t uid) 15.session管理模块 15.1session是什么 在web开发中HTTP协议是⼀种⽆状态短链接的协议这就导致⼀个客户端连接到服务器上之后服务器不知道当前的连接对应的是哪个用户也不知道客户端是否登录成功这时候为客户端提所有服务是不合理的。 因此服务器为每个用户浏览器创建⼀个会话对象session对象注意⼀个浏览器独占⼀个session对象(默认情况下)。因此在需要保存用户数据时服务器程序可以把用户数据写到用户浏览器独占的session中当用户使⽤浏览器访问其它程序时其它程序可以从用户的session中取出该用户的数据识别该连接对应的用户并为用户提供服务 15.2session工作原理 第一次访问时服务器会生成ssid和session的映射关系并返回ssid当客户端再次访问时就会携带ssid这样服务器就可以通过哈希表快速找到session。 15.3session模块设计 session类成员的设计 1.我们需要一个可以标识session的字段ssid 2.session是用来记录用户是否登录的所以肯定是需要用户uid的才知道用户是谁。 3.session在http连接下是短连接但是在websocket下是长连接所以我们需要设置session的状态是长连接还是短连接。 4.因为在http短连接的情况下我们需要定时销毁连接所以我们需要一个定时器这里采用websocket中的time_ptr。 因此类成员如下 session类成员函数设计 这里主要是可以使用成员函数返回成员变量因为这个类是给后面的session管理类服务的所以这些变量需要外面获得的到下面直接给出代码以及实现方式 class session
{
private:uint64_t _ssid; // 标识uint64_t _uid; // 用户idss_statu _statu; // 状态wsserver_t::timer_ptr _tp; // 定时器
public:session(uint64_t ssid):_ssid(ssid) { DLOG(session %p 被创建!, _ssid); }~session() { DLOG(session %p 被销毁!, _ssid); }void set_user(uint64_t uid) { _uid uid; }void set_statu(ss_statu statu) { _statu statu; }void set_timer(const wsserver_t::timer_ptr tp) { _tp tp; }bool is_login() { return _statu LOGIN; }uint64_t get_ssid() { return _ssid; }uint64_t get_uid() { return _uid; }wsserver_t::timer_ptr get_timer() { return _tp; }
}; 15.4session管理模块设计 session管理类成员设计 1.和房间管理类相似我们也需要next_sid来维护我们的session的管理 2.我们需要建立ssid和session_ptr的联系因为我们需要通过ssid来查找session类中的变量 3.数据结构哈希表不是线程安全的所以我们需要锁来保证线程安全 4.我们需要设置定时任务来保证session的长短连接的维护所以我们需要websocketpp::serverwebsocketpp::config::asio的指针这里我已经将其重定义为wsserver_t; 所以类成员变量如下 session管理类成员函数设计以及实现步骤 1.创建session我们可以通过用户id和session的状态来建立session我们有短连接和长连接两种状态所以需要传入session的状态来确定是短连接还是长连接。 1.1创建session设置状态用户以及ssid和session的映射关系_next_sid; session_ptr session_create(uint64_t uid, ss_statu statu) 2.通过sid来获取session 2.1在哈希表中找到session并返回如果没找到就返回空的智能指针对象 session_ptr get_session_by_sid(uint64_t sid) 3.添加session以及删除session分别对应登录以及退出两种情况 3.1维护哈希表中的映射关系 void append_session(session_ptr sp)
void remove_session(uint64_t sid) 4.因为需要更新session信息即更新长连接和短连接所以我们需要封装一个函数就行更新通过sid以及传进来的秒数来更新。http通信时为短连接该通信是定时删除websocket通信是长连接session是永久存在的注册或者登陆的时候是短连接通信应该定时删除当用户退出游戏大厅或者游戏房间的时候应该设置定时删除 4.1在session永久存在的时候设置永久存在 也就是不需要做任何操作 4.2在session永久存在的情况下设置定制删除 4.3在定时删除的情况下设置永久存在 4.4在定时删除的情况下重新计算时间定时删除 其中我们需要注意的是websocketpp给我们提供的定制删除是删除后立即执行并不是删除了这个任务而是删除了时间变成立刻执行该任务所以我们在取消定制删除的之后应该将seession重新添加。并且需要设置定时添加在执行完上个任务之后0秒添加为了防止删除任务时可能会慢一点导致你刚添加的任务又删除了 void set_session_expire_time(uint64_t sid, int ms) 16.玩家匹配管理模块 16.1玩家匹配队列 匹配队列中的数据可能会改变所以我们这里使用模板类来保证其可拓展性。 类成员设计 1.我们需要一个匹配队列用来匹配这里我们不使用stl中的queue是因为我们有对指定元素的删除因为当一个玩家不想匹配的时候可以随时取消所以使用list来代替queue 2.既然有数据结构的使用那我们一定要保证线程安全所以需要使用到锁 3.当匹配队列中数量少于2的时候需要等待那么我们应该在数据大于等于2的时候将线程唤醒所以我们需要条件变量来维护 因此类成员如下 类成员函数的设计 1.判断队列是否为空获取队列的大小 int size()
bool empty() 2.插入或者移除数据 2.1从队列的尾端插入数据每次插入数据都需要唤醒线程删除数据就从队列最后删除每次插入和删除都必须加锁保护 void push(const T data)
bool pop(T data) 3.阻塞线程当队列数据少于2的时候就需要阻塞线程而阻塞线程直接调用条件变量中的wait接口即可 void Wait() 4.删除队列中的指定元素当用户取消匹配的时候就需要这个接口 4.1只需要调用list中remove接口即可 void remove(T data) 16.2玩家匹配管理模块 类成员设计 1.我们分了3个段位分别是普通高水平以及超高水平根据不同的分数在不同的匹配队列中匹配相同实力的对手。所以我们设计了3个队列 2.有3个队列那么就一定要有3个线程来维护 3.因为这里是在游戏大厅进行匹配所以我们需要知道该用户在不在游戏大厅中所以需要使用到在线管理模块的指针 4.匹配完成之后我们还需要为他们创建房间所以需要房间管理模块的指针来创建房间 5.我们需要查看该用户信息是否在数据库中所以需要数据库管理的指针 故类成员如下 类成员函数的设计 1.首先是构造函数初始化我们需要给3个线程添加线程入口函数线程执行函数我们把它们放到类内。 matcher(user_table* ut,online_user* ou,room_manager* rm):_ut(ut),_ou(ou),_rm(rm),_th_normal(std::thread(matcher::thread_normal_enter,this)),_th_high(std::thread(matcher::thread_high_enter,this)),_th_super(std::thread(matcher::thread_super_enter,this)){DLOG(matcher 创建完成!);} 2.因为3个线程执行的动作其实非常相似所以我们可以将3个线程执行函数分别调用一个hanlder_task函数来完成线程该执行的动作 2.1如果队列中的数量少于2就阻塞线程 2.2判断两个用户是否出队成功如果失败记得把另外一个出队的数据再放入队列中 2.3判断两个用户是否在线如果不在线记得把另外一个出队的数据再放入队列中 2.4为它们创建游戏房间如果房间创建失败那么就将这两个数据放回队列中 2.5组织响应序列化之后给两个用户应答 void handler_task(match_queueuint64_t mq)
//3个线程执行函数分别调用上面的接口即可完成任务
void thread_normal_enter() { return handler_task(_q_normal); }
void thread_high_enter() { return handler_task(_q_high); }
void thread_super_enter() { return handler_task(_q_super); } 3.通过uid将用户添加到队列中以及将用户从队列中移除 3.1添加从数据库中得到用户的分数信息然后根据分数来添加到不同的队列 3.2移除从数据库中得到用户的分数信息然后根据分数来移除指定队列中的数据 bool add(uint64_t uid)
bool del(uint64_t uid) 17.服务器模块 作用服务器模块是对当前所实现的所有模块的⼀个整合并进⾏服务器搭建的⼀个模块最终封装实现出⼀个gobang_server的服务器模块类向外提供搭建五⼦棋对战服务器的接⼝。通过实例化的对象可以简便的完成服务器的搭建。 17.1通信接口的设计协议定制 17.1.1静态资源的请求 1. 注册页面请求 请求GET /register.html HTTP/1.1 响应 HTTP/1.1 200 OK Content-Length: xxx Content-Type: text/html 2. 登录页面请求 请求GET /login.html HTTP/1.13. ⼤厅页面请求 请求GET /game_hall.html HTTP/1.14. 房间页面请求 请求GET /game_room.html HTTP/1.1 17.1.2注册用户 请求 POST /reg HTTP/1.1 Content-Type: application/json Content-Length: *** {username:xiaobai, password:123456} 成功时的响应 HTTP/1.1 200 OK Content-Type: application/json Content-Length: 15 {result:true} 失败时的响应 HTTP/1.1 400 Bad Request Content-Type: application/json Content-Length: 43 {result:false, reason: 用户名已经被占⽤} 17.1.3用户登录 请求 POST /login HTTP/1.1 Content-Type: application/json Content-Length: *** {username:xiaobai, password:123456} 成功时的响应 HTTP/1.1 200 OK Content-Type: application/json Content-Length: *** {result:true} 失败时的响应 HTTP/1.1 400 Bad Request Content-Type: application/json Content-Length: *** {result:false, reason: ⽤⼾名或密码错误} 17.1.4获取客户端信息 请求 GET /userinfo HTTP/1.1 Content-Type: application/json Content-Length: 0 成功时的响应 HTTP/1.1 200 OK Content-Type: application/json Content-Length: *** {id:1, username:xiaobai, score:1000, total_count:4, win_count:2} 失败时的响应 HTTP/1.1 401 Unauthorized Content-Type: application/json Content-Length: *** {result:false, reason: 用户还未登录} 17.1.5websocket长连接协议切换请求进入游戏大厅 请求 GET /match HTTP/1.1 Connection: Upgrade Upgrade: WebSocket ...... 响应 HTTP/1.1 101 Switching ...... WebSocket握手成功后的回复表示游戏大厅已经进入成功。 { optype: hall_ready, uid: 1 } 17.1.6开始对战匹配 客户端 { optype: match_start } 服务器响应 后台正确处理后回复 { optype: match_start, result: true } 后台处理出错回复 { optype: match_start result: false, reason: 具体原因.... } 匹配成功了给客户端的回复 { optype: match_success, //表⽰成匹配成功 result: true } 17.1.7停⽌匹配 客户端 { optype: match_stop } 服务器响应 后台正确处理后回复 { optype: match_stop result: true } 后台处理出错回复 { optype: match_stop result: false, reason: 具体原因.... } 17.1.8websocket长连接协议切换请求进入游戏房间 客户端 GET /game HTTP/1.1 Connection: Upgrade Upgrade: WebSocket ...... 服务器 HTTP/1.1 101 Switching WebSocket握⼿成功后的回复表⽰游戏房间已经进⼊成功 协议切换成功 房间已经建⽴ { optype: room_ready, room_id: 1, //房间ID self_id: 1, //⾃⾝ID white_id: 1, //⽩棋ID black_id: 2, //⿊棋ID } 17.1.9走棋 客户端 { optype: put_chess, // put_chess表⽰当前请求是下棋操作 room_id: 222, // room_id 表⽰当前动作属于哪个房间 uid: 1, // 当前的下棋操作是哪个用户发起的 row: 3, // 当前下棋位置的⾏号 col: 2 // 当前下棋位置的列号 } 服务器 失败响应 { optype: put_chess, result: false reason: ⾛棋失败具体原因 } 获胜的响应 { optype: put_chess, result: true, reason: 对⽅掉线不战⽽胜/对方获得胜利, room_id: 1, uid: 1, row: 3, col: 2, winner: 0 // 0-未分胜负 !0-已分胜负 (uid是谁谁就赢了) } 17.1.9聊天 客户端 { optype: chat, room_id: 1, uid: 1, message: 赶紧点 } 服务器 { optype: chat, result: false reason: 聊天失败具体原因....⽐如有敏感词... } 或者 { optype: chat, result: true, room_id: 1, uid: 1, message: 赶紧点 } 17.2服务器模块实现 类成员设计 1.因为这里我们需要使用到所有的模块所以肯定是需要所有模块的指针的 2.因为我们还需要对接前端的资源文件所以我们需要设置一个web根目录 类成员如下 类成员函数设计 1.我们初始化服务以及启动服务器这两个接口在我们练习使用websocket通信的时候就已经完成了相关操作这里不重复赘述 gobang_server(const std::string host, const std::string username, const std::string password,const std::string dbname, uint16_t port, const std::string webroot WWWROOT): _ut(host, username, password, dbname, port), _rm(_ou, _ut), _sm(_wssvr), _match(_ut, _ou, _rm), _webroot(webroot)
void start(int port 3389)2.在前端需要静态资源的时候我们要返回静态资源页面 2.1获取请求 2.2获取静态资源路径如果是根目录那么我们就返回登录页面 2.3读取文件如果请求的资源不存在就返回404 2.4设置响应状态码以及响应正文 void file_handler(wsserver_t::connection_ptr conn) 3.我们需要http_callback来处理注册请、登录请求以及信息获取我们可以根据不同的uri来处理不同的场景uri从通信连接中得到 void http_callback(websocketpp::connection_hdl hdl) 3.1注册请求 3.1.1获取请求 3.1.2获取请求正文并反序列化然后向数据库中插入如果失败那就返回错误的响应 3.1.3返回成功的响应 void reg(wsserver_t::connection_ptr conn) 3.2登录请求 3.2.1获取请求正文并反序列化 3.2.2校验用户信息的完整性用户名和密码不能为空 3.2.3建立session并设置定制删除 3.2.4添加cookie头部字段并进行响应 void login(wsserver_t::connection_ptr conn) 3.3获取用户信息 3.3.1获取cookie字段然后在cookie中获得ssid 3.3.2根据ssid来获取会话信息 3.3.3在数据库中找到用户信息并进行序列化 3.3.4设置响应正文同时刷新session删除时间 void info(wsserver_t::connection_ptr conn) 4.我们需要通过cookie字段获取session信息这个函数在多个地方使用到所以我们单独写出来 4.1.获取请求正文中的Cookie信息并通过cookie获得ssid 4.2通过ssid获得会话信息 session_ptr get_session_by_cookie(wsserver_t::connection_ptr conn) 5.websocket通信模块其中我们需要设置3个回调函数为了初始化服务器。 5.1连接建立时调用的回调函数通过判断uri是游戏大厅或者游戏房间来分别处理 void open_callback(websocketpp::connection_hdl hdl) 5.1.1游戏大厅处理 1.通过session查看客户端是否登录2.查看用户是否重新登陆3.将用户添加到在线用户管理模块中4.给客户端进行响应进入游戏大厅5.将短连接改成长连接 void ws_game_hall(wsserver_t::connection_ptr conn) 5.1.2游戏房间的处理 1.获取用户的会话信息2.查看用户是否重新登陆3.查看是否给用户创建好房间4.将用户添加到在线用户管理中的房间管理模块5.将连接设置成长连接6.给客户端发送响应 void ws_game_room(wsserver_t::connection_ptr conn) 5.2连接关闭调用的回调函数通过判断uri是游戏大厅或者游戏房间来分别处理 void close_callback(websocketpp::connection_hdl hdl) 5.2.1游戏大厅处理 1.获取session信息2.将用户移除游戏大厅3.将连接改成短连接 void wsclose_game_hall(wsserver_t::connection_ptr conn) 5.2.2游戏房间处理 1.获取会话信息2.将玩家从在线用户管理房间模块中移除3.将session信息设置为定时删除4.将该用户移除房间 void wsclose_game_room(wsserver_t::connection_ptr conn) 5.3连接时的信息处理的回调函数通过判断uri是游戏大厅或者游戏房间来分别处理 void message_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg) 5.3.1游戏大厅处理通过连接和发送过来的信息完成处理之后发送给客户端 1.获取用户信息2.将信息反序列化3.分别处理开始匹配和停止匹配的动作4.给客户端响应 void wsmsg_game_hall(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg) 5.3.2游戏大厅处理通过连接和发送过来的信息完成处理之后发送给客户端 1.获取用户session信息2.获取用户房间信息3.将信息反序列化4.通过房间管理模块来对信息进行处理并返回给客户端 void wsmsg_game_room(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg) 18.前端模块 在这个模块中我们主要说明的是使用js语言来对接前端 18.1注册页面 当客户端输入完用户名和密码之后我们需要做的工作如下 1.给提交按钮添加点击事件2.获取输入框中的信息通过ajax来组织一个响应发送给服务器如果成功那就跳转登录页面如果失败就打印提示原因并清空输入框中的内容 18.2登录页面 当客户端输入完用户名和密码之后我们需要做的工作如下 1.给提交按钮添加点击事件2.获取输入框中的信息通过ajax来组织一个响应发送给服务器如果成功那就跳转游戏大厅页面如果失败就打印提示原因并清空输入框中的内容 18.3游戏大厅页面 1.我们在进入游戏大厅页面之后要加载用户的信息并将websocket通信连接建立好设置好各个回调函数如果发生错误就返回登录页面 2.我们要给开始匹配按钮添加点击事件当收到客户端要开始匹配的时候就向后台发送信息当用户停止匹配的时候也是如此 3.在信息处理的时候我们需要根据不同的请求字段来分别对消息进行处理例如开始匹配、匹配成功、停止匹配等 18.4游戏房间页面 首先进入这个页面就需要建立好websocket长连接设置好4个回调函数对于消息处理函数我们最后整体讲解 1.处理下棋动作 1.1首先给棋盘添加点击事件1.2查看是否轮到我方走棋1.3查看下棋的位置是否被占用 1.4组织一个请求发送给服务器让服务器查看当次请求是否合理。 2.处理聊天动作 2.1给发送按钮添加点击事件2.2发送给服务器后经过敏感词的排查如果没有问题就在聊天板中添加一个控件 3.消息处理的回调函数 3.1如果是刚刚进入游戏房间那么我们就要保存好房间用户的信息以便后面在下棋或者聊天中使用 3.2下棋操作绘制棋盘然后判断是否有胜利者没有胜利者就继续有胜利者就将下面的控件改成获胜或者失败的信息然后添加返回游戏大厅的控件 3.3聊天处理在聊天板中添加发送的信息并清空发送区域的聊天信息 19.项目扩展 19.1 实现局时/步时 局时:⼀局游戏中玩家能思考的总时间 步时:⼀步落子过程中玩家能思考的时间 19.2 保存棋谱录像回放 服务器可以把每⼀局对局、玩家轮流落子的位置都记录下来 玩家可以在游戏大厅页面选定某个曾经的比赛在页面上回放出对局的过程 19.3观战功能• 在游戏大厅显示当前所有的对局房间 玩家可以选中某个房间以观众的形式加入到房间中实时的看到选手的对局情况 19.4 虚拟对手人机对战 如果当前长时间匹配不到选手则自动分配⼀个AI对手实现⼈机对战 20.项目源码 Gitee:网页版五子棋源码