【C++】在线五子棋对战项目网页版
目录
1.Websocket
1.1.Websocket的简单认识
1.2.什么是轮询呢?
1.3.websocket协议切换过程
1.4.websocketpp库常用接口认识
1.5.websocketpp库搭建服务器流程
1.6.websocketpp库搭建服务器
2.mysqlclient库-接口认识
3.项目模块的划分:
4.项目流程图:
4.1.玩家用户角度流程图:
4.2.服务器流程结构图:
5.实用的工具模块:编辑
5.1.日志的实现
代码:
5.2.mysql工具类编译与测试
代码:
5.3.json工具类编译与测试:
代码:
5.4.string工具类编译与测试
代码:
5.5.file工具类编译与测试
代码:
6.用户信息表的设计:
代码:
7.数据管理模块类的设计
整体思路:
代码:
数据管理模块类的编译测试
8.在线用户管理设计思想
代码的整体框架:
具体实现:
在线用户管理类的编译与测试
9.游戏房间的设计思想:
代码整体框架:
具体代码实现:
10.房间管理的设计思想:
10.1.房间管理类的设计
具体代码:
房间管理类的编译纠错
11.session的基础了解
12.session类的设计:
总体思想:编辑
框架的搭建:编辑
具体代码的实现:
13.session管理类的设计
框架的搭建:
具体代码:
14.游戏对战匹配设计思想
游戏对战匹配队列设计
代码整体框架:编辑
具体代码的实现:
15.游戏对战匹配管理设计
代码框架:
具体代码实现:
16.服务器业务请求流程
17.服务器网络通信接口设计
18.服务器类设计
19.服务器业务请求处理分类
20.服务器业务静态资源请求处理
服务器业务静态资源请求测试
21.服务器业务用户注册后台处理
22.服务器业务用户注册前端处理
23.服务器业务用户注册联调测试
问题:
24.服务器业务用户登录后台处理
25.服务器业务用户登录前端处理:
26.服务器业务用户信息获取后台处理
27.服务器业务用户信息获取前端处理
28.服务器业务用户登录联调测试
29.用户信息获取功能请求的处理
30.服务器业务用户信息获取前端处理
31.服务器业务用户信息获取前后端联调
32.服务器业务长连接分类
33.服务器业务游戏大厅长连接建立成功处理
服务器业务游戏大厅和游戏房间的长连接关闭处理
服务器业务游戏大厅消息请求处理
34.服务器业务游戏大厅前端事件处理
35.服务器业务游戏大厅所有功能联调测试
36.服务器业务游戏房间前端长连接建立
37.走棋操作:
项目总结:
问题:
1.Websocket
1.1.Websocket的简单认识
WebSocket 是从 HTML5 开始支持的一种网页端和服务端保持长连接的消息推送机制。
- 传统的 web 程序都是属于 "⼀问⼀答" 的形式,即客户端给服务器发送了⼀个 HTTP 请求,服务器 给客⼾端返回⼀个 HTTP 响应。这种情况下服务器是属于被动的一方,在HTTP协议中,如果客⼾端不主动发起请求 ,服务器就⽆法主动给客⼾端响应
- 像网页即时聊天或者我们做的五子棋游戏这样的程序都是非常依赖 "消息推送" 的, 即需要服务器 主动推动消息到客户端。如果只是使用原生的 HTTP 协议,要想实现消息推送⼀般需要通过 "轮 询" 的方式实现, ⽽轮询的成本⽐较⾼并且也不能及时的获取到消息的响应。
1.2.什么是轮询呢?
比如上图,服务端是无法向主机B主动发送信息,必须让主机B先主动发送信息请求信息到服务端,问服务端有信息了吗,对于主机A也是同样如此,要想获取主机B从服务端发送的信息,服务端是不能主动发送信息到主机A,必须由主机A自己多次申请,这就是“轮询”!
基于上述两个问题, 就产⽣了WebSocket协议。WebSocket 更接近于 TCP 这种级别的通信⽅式,⼀ 旦连接建⽴完成客户端或者服务器都可以主动的向对⽅发送数据。
1.3.websocket协议切换过程
websocketpp协议是由HTTP协议切换过来的,客户端发送协议切换的请求,服务端进行响应,同意切换,这样就变成了websocket的通信连接了
1.4.websocketpp库常用接口认识
1.5.websocketpp库搭建服务器流程
- 实例化server对象
- 设置日志输出等级
- 初始化asio框架种的调度器
- 设置业务处理回调函数(具体业务处理的函数由我们自己实现)
- 设置服务器监听端口
- 开始获取新建连接
- 启动服务器
1.6.websocketpp库搭建服务器
代码:
#include <iostream>
#include <string>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>typedef websocketpp::server<websocketpp::config::asio> wsserver_t;void print(const std::string &str)
{std::cout << str << std::endl;
}void http_callback(wsserver_t *srv, websocketpp::connection_hdl hdl) {//给客户端返回一个hello world的页面wsserver_t::connection_ptr conn = srv->get_con_from_hdl(hdl);std::cout << "body: " << conn->get_request_body() << std::endl; websocketpp::http::parser::request req = conn->get_request();std::cout << "method: " << req.get_method() << std::endl;std::cout << "uri: " << req.get_uri() << std::endl;std::string body = "<html><body><h1>Hello World</h1></body></html>";conn->set_body(body);conn->append_header("Content-Type", "text/html");//conn->set_body(conn->get_request_body());conn->set_status(websocketpp::http::status_code::ok);//wsserver_t::timer_ptr tp = srv->set_timer(5000, std::bind(print, "bitejiuyeke"));//tp->cancel();//定时任务的取消,会导致定时任务立即被执行
}
void wsopen_callback(wsserver_t *srv, websocketpp::connection_hdl hdl) {std::cout << "websocket握手成功!!\n";
}
void wsclose_callback(wsserver_t *srv, websocketpp::connection_hdl hdl) {std::cout << "websocket连接断开!!\n";
}
void wsmsg_callback(wsserver_t *srv, websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg) {wsserver_t::connection_ptr conn = srv->get_con_from_hdl(hdl);std::cout << "wsmsg: " << msg->get_payload() << std::endl;std::string rsp = "client say: " + msg->get_payload();conn->send(rsp, websocketpp::frame::opcode::text);
}
int main()
{//1. 实例化server对象wsserver_t wssrv;//2. 设置日志等级wssrv.set_access_channels(websocketpp::log::alevel::none);//3. 初始化asio调度器wssrv.init_asio();wssrv.set_reuse_addr(true);//4. 设置回调函数wssrv.set_http_handler(std::bind(http_callback, &wssrv, std::placeholders::_1));wssrv.set_open_handler(std::bind(wsopen_callback, &wssrv, std::placeholders::_1));wssrv.set_close_handler(std::bind(wsclose_callback, &wssrv, std::placeholders::_1));wssrv.set_message_handler(std::bind(wsmsg_callback, &wssrv, std::placeholders::_1, std::placeholders::_2));//5. 设置监听端口wssrv.listen(8085);//6. 开始获取新连接wssrv.start_accept();//7. 启动服务器wssrv.run();return 0;
}
我们想要在页面生成一个Hello World
成功生成!我们使用websocketpp协议搭建网络通信平台成功!
2.mysqlclient库-接口认识
mysql数据库的访问操作流程:
客户端初始化过程:
1.初始化mysql操作句柄
2.连接mysql服务器
3.设置客户端的字符集
4.选择想要操作的数据库
客户端对数据库中数据的操作:
新增,修改,删除,都是只要sql语句执行成功了,数据库中的数据就发生了改变,没有反馈结果
查询是不一样的,因为需要首先执行语句成功,需要将查询结果保存到本地进行访问
5.执行sql语句
一旦成功,如果是增删改,则就完毕了,
查询:
6.将查询结果保存到本地
7.获取查询结果集中的结果条数
8.遍历获取结果集中的每一条数据进行处理
9.释放结果集,避免内存泄露
10.释放MySQL操作句柄
用例代码:
#include <stdio.h>
#include <string.h>
#include <mysql/mysql.h>#define HOST "127.0.0.1"
#define PORT 3306
#define USER "root"
#define PASS "qwer@wu.888"
#define DBNAME "gobang"
int main()
{//1. 初始化mysql句柄// MYSQL *mysql_init(MYSQL *mysql);MYSQL *mysql = mysql_init(NULL);if (mysql == NULL) {printf("mysql init failed!\n");return -1;}//2. 连接服务器// MYSQL *mysql_real_connect(mysql, host, user, pass, dbname, port, unix_socket, flag);if (mysql_real_connect(mysql, HOST, USER, PASS, DBNAME, PORT, NULL, 0) == NULL) {printf("connect mysql server failed : %s\n", mysql_error(mysql));mysql_close(mysql);return -1;}//3. 设置客户端字符集// int mysql_set_character_set(mysql, "utf8");if (mysql_set_character_set(mysql, "utf8") != 0) {printf("set client character failed : %s\n", mysql_error(mysql));mysql_close(mysql);return -1;}//4. 选择要操作的数据库// int mysql_select_db(mysql, dbname)// mysql_select_db(mysql, DBNAME);//5. 执行sql语句// int mysql_query(MYSQL *mysql, char *sql);//char *sql = "insert stu values(null, '小明', 18, 53, 68, 87);";//char *sql = "update stu set ch=ch+40 where sn=1;";//char *sql = "delete from stu where sn=1;";char *sql = "select * from stu;";int ret = mysql_query(mysql, sql);if (ret != 0) {printf("%s\n", sql);printf("mysql query failed : %s\n", mysql_error(mysql));mysql_close(mysql);return -1;}//6. 如果sql语句是查询语句,则需要保存结果到本地// MYSQL_RES *mysql_store_result(MYSQL *mysql)MYSQL_RES *res = mysql_store_result(mysql);if (res == NULL) {mysql_close(mysql);return -1;}//7. 获取结果集中的结果条数// int mysql_num_rows(MYSQL_RES *res);// int mysql_num_fields(MYSQL_RES *res);int num_row = mysql_num_rows(res);int num_col = mysql_num_fields(res);//8. 遍历保存到本地的结果集for (int i = 0; i < num_row; i++) {// MYSQL_ROW mysql_fetch_row(MYSQL *res);MYSQL_ROW row = mysql_fetch_row(res);for (int i = 0; i < num_col; i++) {printf("%s\t", row[i]);}printf("\n");}//9. 释放结果集mysql_free_result(res);//10. 关闭连接,释放句柄mysql_close(mysql);return 0;
}
试着插入新数据,查询结果,成功!
3.项目模块的划分:
4.项目流程图:
4.1.玩家用户角度流程图:
4.2.服务器流程结构图:
5.实用的工具模块:

5.1.日志的实现
日志宏的实现的一些前置知识:
代码:
#ifndef __M_LOGGER_H__
#define __M_LOGGER_H__
#include <stdio.h>
#include <time.h>#define INF 0
#define DBG 1
#define ERR 2
#define DEFAULT_LOG_LEVEL INF
#define LOG(level, format, ...) do{\if (DEFAULT_LOG_LEVEL > level) break;\time_t t = time(NULL);\struct tm *lt = localtime(&t);\char buf[32] = {0};\strftime(buf, 31, "%H:%M:%S", lt);\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(DBG, format, ##__VA_ARGS__)
#define ELOG(format, ...) LOG(ERR, format, ##__VA_ARGS__)#endif
5.2.mysql工具类编译与测试
这个报错是为什么?
因为我们把第一步忘记了,第一步需要创建表结构
添加表结构之后,我们再执行一遍,成功添加小黑的信息!
代码:
class mysql_util {public:static MYSQL *mysql_create(const std::string &host,const std::string &username,const std::string &password,const std::string &dbname,uint16_t port = 3306) {MYSQL *mysql = mysql_init(NULL);if (mysql == NULL) {ELOG("mysql init failed!");return NULL;}//2. 连接服务器if (mysql_real_connect(mysql, host.c_str(), username.c_str(), password.c_str(), dbname.c_str(), port, NULL, 0) == NULL) {ELOG("connect mysql server failed : %s", mysql_error(mysql));mysql_close(mysql);return NULL;}//3. 设置客户端字符集if (mysql_set_character_set(mysql, "utf8") != 0) {ELOG("set client character failed : %s", mysql_error(mysql));mysql_close(mysql);return NULL;}return mysql;}static bool mysql_exec(MYSQL *mysql, const std::string &sql) {int ret = mysql_query(mysql, sql.c_str());if (ret != 0) {ELOG("%s\n", sql.c_str());ELOG("mysql query failed : %s\n", mysql_error(mysql));return false;}return true;}static void mysql_destroy(MYSQL *mysql) {if (mysql != NULL) {mysql_close(mysql);}return ;}
};
5.3.json工具类编译与测试:
这个工具类是用来干嘛的?序列化和反序列化成功!
编译成功!
代码:
class json_util{public:static bool serialize(const Json::Value &root, std::string &str) {Json::StreamWriterBuilder swb;std::unique_ptr<Json::StreamWriter>sw(swb.newStreamWriter());std::stringstream ss;int ret = sw->write(root, &ss);if (ret != 0) {ELOG("json serialize failed!!");return false;}str = ss.str();return true;}static bool unserialize(const std::string &str, Json::Value &root) {Json::CharReaderBuilder crb;std::unique_ptr<Json::CharReader> cr(crb.newCharReader());std::string err;bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), &root, &err);if (ret == false) {ELOG("json unserialize failed: %s", err.c_str());return false;}return true;}
};
5.4.string工具类编译与测试
该工具类是为了分割字符串来提取内容
成功分割,提取到内容!
代码:
class string_util{public:static int split(const std::string &src, const std::string &sep, std::vector<std::string> &res) {// 123,234,,,,345size_t pos, idx = 0;while(idx < src.size()) {pos = src.find(sep, idx);if (pos == std::string::npos) {//没有找到,字符串中没有间隔字符了,则跳出循环res.push_back(src.substr(idx));break;}if (pos == idx) {idx += sep.size();continue;}res.push_back(src.substr(idx, pos - idx));idx = pos + sep.size();}return res.size();}
};
5.5.file工具类编译与测试
该工具是为了提取文件内容
- 定义一个
size_t
类型的变量fsize
,用于存储文件的大小。 - 将文件读取指针移动到文件的末尾,以获取文件的大小。
- 使用
tellg
函数获取当前文件指针的位置,即文件的大小,并将其存储在fsize
中。
成功将makefile文件里面的内容进行读取和打印!
代码:
class file_util {public:static bool read(const std::string &filename, std::string &body) {//打开文件std::ifstream ifs(filename, std::ios::binary);if (ifs.is_open() == false) {ELOG("%s file open failed!!", filename.c_str());return false;}//获取文件大小size_t fsize = 0;ifs.seekg(0, std::ios::end);fsize = ifs.tellg();ifs.seekg(0, std::ios::beg);body.resize(fsize);//将文件所有数据读取出来ifs.read(&body[0], fsize);if (ifs.good() == false) {ELOG("read %s file content failed!", filename.c_str());ifs.close();return false;}//关闭文件ifs.close();return true;}
};
6.用户信息表的设计:
运行结果与我们创建表的效果一致!
代码:
drop database if exists gobang;
create database if not exists gobang;
use gobang;
create table if not exists user(id int primary key auto_increment,username varchar(32) unique key not null,password varchar(128) not null,score int,total_count int,win_count int
);
7.数据管理模块类的设计
整体思路:
代码的整体框架:
代码:
#ifndef __M_DB_H__
#define __M_DB_H__
#include "util.hpp"
#include <mutex>
#include <cassert>class user_table{private:MYSQL *_mysql; //mysql操作句柄std::mutex _mutex;//互斥锁保护数据库的访问操作public:user_table(const std::string &host,const std::string &username,const std::string &password,const std::string &dbname,uint16_t port = 3306) {_mysql = mysql_util::mysql_create(host, username, password, dbname, port);assert(_mysql != NULL);} ~user_table() {mysql_util::mysql_destroy(_mysql);_mysql = NULL;}//注册时新增用户bool insert(Json::Value &user) {
#define INSERT_USER "insert user values(null, '%s', password('%s'), 1000, 0, 0);"// sprintf(void *buf, char *format, ...)if (user["password"].isNull() || user["username"].isNull()) {DLOG("INPUT PASSWORD OR USERNAME");return false;}char sql[4096] = {0};sprintf(sql, INSERT_USER, user["username"].asCString(), user["password"].asCString());bool ret = mysql_util::mysql_exec(_mysql, sql);if (ret == false) {DLOG("insert user info failed!!\n");return false;}return true;}//登录验证,并返回详细的用户信息bool login(Json::Value &user) {if (user["password"].isNull() || user["username"].isNull()) {DLOG("INPUT PASSWORD OR USERNAME");return false;}//以用户名和密码共同作为查询过滤条件,查询到数据则表示用户名密码一致,没有信息则用户名密码错误
#define LOGIN_USER "select id, score, total_count, win_count from user where username='%s' and password=password('%s');"char sql[4096] = {0};sprintf(sql, LOGIN_USER, user["username"].asCString(), user["password"].asCString());MYSQL_RES *res = NULL;{std::unique_lock<std::mutex> lock(_mutex);bool ret = mysql_util::mysql_exec(_mysql, sql);if (ret == false) {DLOG("user login failed!!\n");return false;}//按理说要么有数据,要么没有数据,就算有数据也只能有一条数据res = mysql_store_result(_mysql);if (res == NULL) {DLOG("have no login user info!!");return false;}}int row_num = mysql_num_rows(res);if (row_num != 1) {DLOG("the user information queried is not unique!!");return false;}MYSQL_ROW row = mysql_fetch_row(res);user["id"] = (Json::UInt64)std::stol(row[0]);user["score"] = (Json::UInt64)std::stol(row[1]);user["total_count"] = std::stoi(row[2]);user["win_count"] = std::stoi(row[3]);mysql_free_result(res);return true;}// 通过用户名获取用户信息bool select_by_name(const std::string &name, Json::Value &user) {
#define USER_BY_NAME "select id, score, total_count, win_count from user where username='%s';"char sql[4096] = {0};sprintf(sql, USER_BY_NAME, name.c_str());MYSQL_RES *res = NULL;{std::unique_lock<std::mutex> lock(_mutex);bool ret = mysql_util::mysql_exec(_mysql, sql);if (ret == false) {DLOG("get user by name failed!!\n");return false;}//按理说要么有数据,要么没有数据,就算有数据也只能有一条数据res = mysql_store_result(_mysql);if (res == NULL) {DLOG("have no user info!!");return false;}}int row_num = mysql_num_rows(res);if (row_num != 1) {DLOG("the user information queried is not unique!!");return false;}MYSQL_ROW row = mysql_fetch_row(res);user["id"] = (Json::UInt64)std::stol(row[0]);user["username"] = name;user["score"] = (Json::UInt64)std::stol(row[1]);user["total_count"] = std::stoi(row[2]);user["win_count"] = std::stoi(row[3]);mysql_free_result(res);return true;}// 通过用户名获取用户信息bool select_by_id(uint64_t id, Json::Value &user) {
#define USER_BY_ID "select username, score, total_count, win_count from user where id=%ld;"char sql[4096] = {0};sprintf(sql, USER_BY_ID, id);MYSQL_RES *res = NULL;{std::unique_lock<std::mutex> lock(_mutex);bool ret = mysql_util::mysql_exec(_mysql, sql);if (ret == false) {DLOG("get user by id failed!!\n");return false;}//按理说要么有数据,要么没有数据,就算有数据也只能有一条数据res = mysql_store_result(_mysql);if (res == NULL) {DLOG("have no user info!!");return false;}}int row_num = mysql_num_rows(res);if (row_num != 1) {DLOG("the user information queried is not unique!!");return false;}MYSQL_ROW row = mysql_fetch_row(res);user["id"] = (Json::UInt64)id;user["username"] = row[0];user["score"] = (Json::UInt64)std::stol(row[1]);user["total_count"] = std::stoi(row[2]);user["win_count"] = std::stoi(row[3]);mysql_free_result(res);return true;}//胜利时天梯分数增加30分,战斗场次增加1,胜利场次增加1bool win(uint64_t id) {
#define USER_WIN "update user set score=score+30, total_count=total_count+1, win_count=win_count+1 where id=%ld;"char sql[4096] = {0};sprintf(sql, USER_WIN, id);bool ret = mysql_util::mysql_exec(_mysql, sql);if (ret == false) {DLOG("update win user info failed!!\n");return false;}return true;}//失败时天梯分数减少30,战斗场次增加1,其他不变bool lose(uint64_t id) {
#define USER_LOSE "update user set score=score-30, total_count=total_count+1 where id=%ld;"char sql[4096] = {0};sprintf(sql, USER_LOSE, id);bool ret = mysql_util::mysql_exec(_mysql, sql);if (ret == false) {DLOG("update lose user info failed!!\n");return false;}return true;}
};
#endif
数据管理模块类的编译测试
验证登录功能和登录验证功能:
我们以xiaoming,但是输入不同的密码
程序会报错!
测试通过用户名获得用户信息
成功!
8.在线用户管理设计思想
在线用户管理类的设计
代码的整体框架:
具体实现:
#ifndef __M_ONLINE_H__
#define __M_ONLINE_H__
#include "util.hpp"
#include <mutex>
#include <unordered_map>class online_manager{private:std::mutex _mutex;//用于建立游戏大厅用户的用户ID与通信连接的关系std::unordered_map<uint64_t, wsserver_t::connection_ptr> _hall_user;//用于建立游戏房间用户的用户ID与通信连接的关系std::unordered_map<uint64_t, wsserver_t::connection_ptr> _room_user;public://websocket连接建立的时候才会加入游戏大厅&游戏房间在线用户管理void enter_game_hall(uint64_t uid, wsserver_t::connection_ptr &conn) {std::unique_lock<std::mutex> lock(_mutex);_hall_user.insert(std::make_pair(uid, conn));}void enter_game_room(uint64_t uid, wsserver_t::connection_ptr &conn) {std::unique_lock<std::mutex> lock(_mutex);_room_user.insert(std::make_pair(uid, conn));}//websocket连接断开的时候,才会移除游戏大厅&游戏房间在线用户管理void exit_game_hall(uint64_t uid) {std::unique_lock<std::mutex> lock(_mutex);_hall_user.erase(uid);}void exit_game_room(uint64_t uid) {std::unique_lock<std::mutex> lock(_mutex);_room_user.erase(uid);}//判断当前指定用户是否在游戏大厅/游戏房间bool is_in_game_hall(uint64_t uid) {std::unique_lock<std::mutex> lock(_mutex);auto it = _hall_user.find(uid);if (it == _hall_user.end()) {return false;}return true;}bool is_in_game_room(uint64_t uid) {std::unique_lock<std::mutex> lock(_mutex);auto it = _room_user.find(uid);if (it == _room_user.end()) {return false;}return true;}//通过用户ID在游戏大厅/游戏房间用户管理中获取对应的通信连接wsserver_t::connection_ptr get_conn_from_hall(uint64_t uid) {std::unique_lock<std::mutex> lock(_mutex);auto it = _hall_user.find(uid);if (it == _hall_user.end()) {return wsserver_t::connection_ptr();}return it->second;}wsserver_t::connection_ptr get_conn_from_room(uint64_t uid) {std::unique_lock<std::mutex> lock(_mutex);auto it = _room_user.find(uid);if (it == _room_user.end()) {return wsserver_t::connection_ptr();}return it->second;}
};#endif
在线用户管理类的编译与测试
因为现阶段没有对应的业务,所以只能简单的进行测试是否在房间中,当退出的时候看是否成功退出房间
测试代码:
测试结果如下图,成功!
9.游戏房间的设计思想:
代码整体框架:
具体代码实现:
#ifndef __M_ROOM_H__
#define __M_ROOM_H__
#include "util.hpp"
#include "logger.hpp"
#include "online.hpp"
#include "db.hpp"
#define BOARD_ROW 15
#define BOARD_COL 15
#define CHESS_WHITE 1
#define CHESS_BLACK 2
typedef enum { GAME_START, GAME_OVER }room_statu;
class room {private:uint64_t _room_id;room_statu _statu;int _player_count;uint64_t _white_id;uint64_t _black_id;user_table *_tb_user;online_manager *_online_user;std::vector<std::vector<int>> _board;private:bool five(int row, int col, int row_off, int col_off, int color) {//row和col是下棋位置, row_off和col_off是偏移量,也是方向int count = 1;int search_row = row + row_off;int search_col = col + col_off;while(search_row >= 0 && search_row < BOARD_ROW &&search_col >= 0 && search_col < BOARD_COL &&_board[search_row][search_col] == color) {//同色棋子数量++count++;//检索位置继续向后偏移search_row += row_off;search_col += col_off;}search_row = row - row_off;search_col = col - col_off;while(search_row >= 0 && search_row < BOARD_ROW &&search_col >= 0 && search_col < BOARD_COL &&_board[search_row][search_col] == color) {//同色棋子数量++count++;//检索位置继续向后偏移search_row -= row_off;search_col -= col_off;}return (count >= 5);}uint64_t check_win(int row, int col, int color) {// 从下棋位置的四个不同方向上检测是否出现了5个及以上相同颜色的棋子(横行,纵列,正斜,反斜)if (five(row, col, 0, 1, color) || five(row, col, 1, 0, color) ||five(row, col, -1, 1, color)||five(row, col, -1, -1, color)) {//任意一个方向上出现了true也就是五星连珠,则设置返回值return color == CHESS_WHITE ? _white_id : _black_id;}return 0;}public:room(uint64_t room_id, user_table *tb_user, online_manager *online_user):_room_id(room_id), _statu(GAME_START), _player_count(0),_tb_user(tb_user), _online_user(online_user),_board(BOARD_ROW, std::vector<int>(BOARD_COL, 0)){DLOG("%lu 房间创建成功!!", _room_id);}~room() {DLOG("%lu 房间销毁成功!!", _room_id);}uint64_t id() { return _room_id; }room_statu statu() { return _statu; }int player_count() { return _player_count; }void add_white_user(uint64_t uid) { _white_id = uid; _player_count++; }void add_black_user(uint64_t uid) { _black_id = uid; _player_count++; }uint64_t get_white_user() { return _white_id; }uint64_t get_black_user() { return _black_id; }/*处理下棋动作*/Json::Value handle_chess(Json::Value &req) {Json::Value json_resp = req;// 2. 判断房间中两个玩家是否都在线,任意一个不在线,就是另一方胜利。int chess_row = req["row"].asInt();int chess_col = req["col"].asInt();uint64_t cur_uid = req["uid"].asUInt64();if (_online_user->is_in_game_room(_white_id) == false) {json_resp["result"] = true;json_resp["reason"] = "运气真好!对方掉线,不战而胜!";json_resp["winner"] = (Json::UInt64)_black_id;return json_resp;}if (_online_user->is_in_game_room(_black_id) == false) {json_resp["result"] = true;json_resp["reason"] = "运气真好!对方掉线,不战而胜!";json_resp["winner"] = (Json::UInt64)_white_id;return json_resp;}// 3. 获取走棋位置,判断当前走棋是否合理(位置是否已经被占用)if (_board[chess_row][chess_col] != 0) {json_resp["result"] = false;json_resp["reason"] = "当前位置已经有了其他棋子!";return json_resp;}int cur_color = cur_uid == _white_id ? CHESS_WHITE : CHESS_BLACK;_board[chess_row][chess_col] = cur_color;// 4. 判断是否有玩家胜利(从当前走棋位置开始判断是否存在五星连珠)uint64_t winner_id = check_win(chess_row, chess_col, cur_color);if (winner_id != 0) {json_resp["reason"] = "五星连珠,战无敌!";}json_resp["result"] = true;json_resp["winner"] = (Json::UInt64)winner_id;return json_resp;}/*处理聊天动作*/Json::Value handle_chat(Json::Value &req) {Json::Value json_resp = req;//检测消息中是否包含敏感词std::string msg = req["message"].asString();size_t pos = msg.find("垃圾");if (pos != std::string::npos) {json_resp["result"] = false;json_resp["reason"] = "消息中包含敏感词,不能发送!";return json_resp;}//广播消息---返回消息json_resp["result"] = true;return json_resp;}/*处理玩家退出房间动作*/void handle_exit(uint64_t uid) {//如果是下棋中退出,则对方胜利,否则下棋结束了退出,则是正常退出Json::Value json_resp;if (_statu == GAME_START) {uint64_t winner_id = (Json::UInt64)(uid == _white_id ? _black_id : _white_id);json_resp["optype"] = "put_chess";json_resp["result"] = true;json_resp["reason"] = "对方掉线,不战而胜!";json_resp["room_id"] = (Json::UInt64)_room_id;json_resp["uid"] = (Json::UInt64)uid;json_resp["row"] = -1;json_resp["col"] = -1;json_resp["winner"] = (Json::UInt64)winner_id;uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;_tb_user->win(winner_id);_tb_user->lose(loser_id);_statu = GAME_OVER;broadcast(json_resp);}//房间中玩家数量--_player_count--;return;}/*总的请求处理函数,在函数内部,区分请求类型,根据不同的请求调用不同的处理函数,得到响应进行广播*/void handle_request(Json::Value &req) {//1. 校验房间号是否匹配Json::Value json_resp;uint64_t room_id = req["room_id"].asUInt64();if (room_id != _room_id) {json_resp["optype"] = req["optype"].asString();json_resp["result"] = false;json_resp["reason"] = "房间号不匹配!";return broadcast(json_resp);}//2. 根据不同的请求类型调用不同的处理函数if (req["optype"].asString() == "put_chess") {json_resp = handle_chess(req);if (json_resp["winner"].asUInt64() != 0) {uint64_t winner_id = json_resp["winner"].asUInt64();uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;_tb_user->win(winner_id);_tb_user->lose(loser_id);_statu = GAME_OVER;}}else if (req["optype"].asString() == "chat") {json_resp = handle_chat(req);}else {json_resp["optype"] = req["optype"].asString();json_resp["result"] = false;json_resp["reason"] = "未知请求类型";}std::string body;json_util::serialize(json_resp, body);DLOG("房间-广播动作: %s", body.c_str());return broadcast(json_resp);}/*将指定的信息广播给房间中所有玩家*/void broadcast(Json::Value &rsp) {//1. 对要响应的信息进行序列化,将Json::Value中的数据序列化成为json格式字符串std::string body;json_util::serialize(rsp, body);//2. 获取房间中所有用户的通信连接//3. 发送响应信息wsserver_t::connection_ptr wconn = _online_user->get_conn_from_room(_white_id);if (wconn.get() != nullptr) {wconn->send(body);}else {DLOG("房间-白棋玩家连接获取失败");}wsserver_t::connection_ptr bconn = _online_user->get_conn_from_room(_black_id);if (bconn.get() != nullptr) {bconn->send(body);}else {DLOG("房间-黑棋玩家连接获取失败");}return;}
};
游戏房间类的编译纠错
测试代码:
这里只需要代码的编译成功即可
10.房间管理的设计思想:
10.1.房间管理类的设计
具体代码:
using room_ptr = std::shared_ptr<room>;class room_manager{private:uint64_t _next_rid;std::mutex _mutex;user_table *_tb_user;online_manager *_online_user;std::unordered_map<uint64_t, room_ptr> _rooms;std::unordered_map<uint64_t, uint64_t> _users;public:/*初始化房间ID计数器*/room_manager(user_table *ut, online_manager *om):_next_rid(1), _tb_user(ut), _online_user(om) {DLOG("房间管理模块初始化完毕!");}~room_manager() { DLOG("房间管理模块即将销毁!"); }//为两个用户创建房间,并返回房间的智能指针管理对象room_ptr create_room(uint64_t uid1, uint64_t uid2) {//两个用户在游戏大厅中进行对战匹配,匹配成功后创建房间//1. 校验两个用户是否都还在游戏大厅中,只有都在才需要创建房间。if (_online_user->is_in_game_hall(uid1) == false) {DLOG("用户:%lu 不在大厅中,创建房间失败!", uid1);return room_ptr();}if (_online_user->is_in_game_hall(uid2) == false) {DLOG("用户:%lu 不在大厅中,创建房间失败!", uid2);return room_ptr();}//2. 创建房间,将用户信息添加到房间中std::unique_lock<std::mutex> lock(_mutex);room_ptr rp(new room(_next_rid, _tb_user, _online_user));rp->add_white_user(uid1);rp->add_black_user(uid2);//3. 将房间信息管理起来_rooms.insert(std::make_pair(_next_rid, rp));_users.insert(std::make_pair(uid1, _next_rid));_users.insert(std::make_pair(uid2, _next_rid));_next_rid++;//4. 返回房间信息return rp;}/*通过房间ID获取房间信息*/room_ptr get_room_by_rid(uint64_t rid) {std::unique_lock<std::mutex> lock(_mutex);auto it = _rooms.find(rid);if (it == _rooms.end()) {return room_ptr();}return it->second;}/*通过用户ID获取房间信息*/room_ptr get_room_by_uid(uint64_t uid) {std::unique_lock<std::mutex> lock(_mutex);//1. 通过用户ID获取房间IDauto uit = _users.find(uid);if (uit == _users.end()) {return room_ptr();}uint64_t rid = uit->second;//2. 通过房间ID获取房间信息auto rit = _rooms.find(rid);if (rit == _rooms.end()) {return room_ptr();}return rit->second;}/*通过房间ID销毁房间*/void remove_room(uint64_t rid) {//因为房间信息,是通过shared_ptr在_rooms中进行管理,因此只要将shared_ptr从_rooms中移除//则shared_ptr计数器==0,外界没有对房间信息进行操作保存的情况下就会释放//1. 通过房间ID,获取房间信息room_ptr rp = get_room_by_rid(rid);if (rp.get() == nullptr) {return;}//2. 通过房间信息,获取房间中所有用户的IDuint64_t uid1 = rp->get_white_user();uint64_t uid2 = rp->get_black_user();//3. 移除房间管理中的用户信息std::unique_lock<std::mutex> lock(_mutex);_users.erase(uid1);_users.erase(uid2);//4. 移除房间管理信息_rooms.erase(rid);}/*删除房间中指定用户,如果房间中没有用户了,则销毁房间,用户连接断开时被调用*/void remove_room_user(uint64_t uid) {room_ptr rp = get_room_by_uid(uid);if (rp.get() == nullptr) {return;}//处理房间中玩家退出动作rp->handle_exit(uid);//房间中没有玩家了,则销毁房间if (rp->player_count() == 0) {remove_room(rp->id());}return ;}
};
房间管理类的编译纠错
测试代码:
编译成功!
11.session的基础了解
websocketpp中定时器的基础使用和特殊的细节
这两个任务没有直接被执行,而是等了5秒钟之后才会被执行。
特殊的细节:tp->cancel();//定时任务的取消,会导致定时任务立即被执行
12.session类的设计:
总体思想:

框架的搭建:

具体代码的实现:
typedef enum {UNLOGIN, LOGIN} ss_statu;
class session {private:uint64_t _ssid;//标识符uint64_t _uid;//session对应的用户IDss_statu _statu;//用户状态:未登录,已登录wsserver_t::timer_ptr _tp;//session关联的定时器public:session(uint64_t ssid): _ssid(ssid){ DLOG("SESSION %p 被创建!!", this); }~session() { DLOG("SESSION %p 被释放!!", this); }uint64_t ssid() { return _ssid; }void set_statu(ss_statu statu) { _statu = statu; }void set_user(uint64_t uid) { _uid = uid; }uint64_t get_user() { return _uid; }bool is_login() { return (_statu == LOGIN); }void set_timer(const wsserver_t::timer_ptr &tp) { _tp = tp;}wsserver_t::timer_ptr& get_timer() { return _tp; }
};
13.session管理类的设计
框架的搭建:
具体代码:
#define SESSION_TIMEOUT 30000
#define SESSION_FOREVER -1
using session_ptr = std::shared_ptr<session>;
class session_manager {private:uint64_t _next_ssid;std::mutex _mutex;std::unordered_map<uint64_t, session_ptr> _session;wsserver_t *_server;public:session_manager(wsserver_t *srv): _next_ssid(1), _server(srv){DLOG("session管理器初始化完毕!");}~session_manager() { DLOG("session管理器即将销毁!"); }session_ptr create_session(uint64_t uid, ss_statu statu) {std::unique_lock<std::mutex> lock(_mutex);session_ptr ssp(new session(_next_ssid));ssp->set_statu(statu);ssp->set_user(uid);_session.insert(std::make_pair(_next_ssid, ssp));_next_ssid++;return ssp;}void append_session(const session_ptr &ssp) {std::unique_lock<std::mutex> lock(_mutex);_session.insert(std::make_pair(ssp->ssid(), ssp));}session_ptr get_session_by_ssid(uint64_t ssid) {std::unique_lock<std::mutex> lock(_mutex);auto it = _session.find(ssid);if (it == _session.end()) {return session_ptr();}return it->second;}void remove_session(uint64_t ssid) {std::unique_lock<std::mutex> lock(_mutex);_session.erase(ssid);}void set_session_expire_time(uint64_t ssid, int ms) {//依赖于websocketpp的定时器来完成session生命周期的管理。// 登录之后,创建session,session需要在指定时间无通信后删除// 但是进入游戏大厅,或者游戏房间,这个session就应该永久存在// 等到退出游戏大厅,或者游戏房间,这个session应该被重新设置为临时,在长时间无通信后被删除session_ptr ssp = get_session_by_ssid(ssid);if (ssp.get() == nullptr) {return;}wsserver_t::timer_ptr tp = ssp->get_timer();if (tp.get() == nullptr && ms == SESSION_FOREVER) {// 1. 在session永久存在的情况下,设置永久存在return ;}else if (tp.get() == nullptr && ms != SESSION_FOREVER) {// 2. 在session永久存在的情况下,设置指定时间之后被删除的定时任务wsserver_t::timer_ptr tmp_tp = _server->set_timer(ms, std::bind(&session_manager::remove_session, this, ssid));ssp->set_timer(tmp_tp);}else if (tp.get() != nullptr && ms == SESSION_FOREVER) {// 3. 在session设置了定时删除的情况下,将session设置为永久存在// 删除定时任务--- stready_timer删除定时任务会导致任务直接被执行tp->cancel();//因为这个取消定时任务并不是立即取消的//因此重新给session管理器中,添加一个session信息, 且添加的时候需要使用定时器,而不是立即添加ssp->set_timer(wsserver_t::timer_ptr());//将session关联的定时器设置为空_server->set_timer(0, std::bind(&session_manager::append_session, this, ssp));}else if (tp.get() != nullptr && ms != SESSION_FOREVER) {// 4. 在session设置了定时删除的情况下,将session重置删除时间。tp->cancel();//因为这个取消定时任务并不是立即取消的ssp->set_timer(wsserver_t::timer_ptr());_server->set_timer(0, std::bind(&session_manager::append_session, this, ssp));//重新给session添加定时销毁任务wsserver_t::timer_ptr tmp_tp = _server->set_timer(ms, std::bind(&session_manager::remove_session, this, ssp->ssid()));//重新设置session关联的定时器ssp->set_timer(tmp_tp);}}
};
直接编译成功即可!
14.游戏对战匹配设计思想
游戏对战匹配队列设计
代码整体框架:

具体代码的实现:
template <class T>
class match_queue {private:/*用链表而不直接使用queue是因为我们有中间删除数据的需要*/std::list<T> _list;/*实现线程安全*/std::mutex _mutex;/*这个条件变量主要为了阻塞消费者,后边使用的时候:队列中元素个数<2则阻塞*/std::condition_variable _cond;public:/*获取元素个数*/int size() { std::unique_lock<std::mutex> lock(_mutex);return _list.size(); }/*判断是否为空*/bool empty() {std::unique_lock<std::mutex> lock(_mutex);return _list.empty();}/*阻塞线程*/void wait() {std::unique_lock<std::mutex> lock(_mutex);_cond.wait(lock);}/*入队数据,并唤醒线程*/void push(const T &data) {std::unique_lock<std::mutex> lock(_mutex);_list.push_back(data);_cond.notify_all();}/*出队数据*/bool pop(T &data) {std::unique_lock<std::mutex> lock(_mutex);if (_list.empty() == true) {return false;}data = _list.front();_list.pop_front();return true;}/*移除指定的数据*/void remove(T &data) {std::unique_lock<std::mutex> lock(_mutex);_list.remove(data);}
};
测试编译:
语法没有问题!
15.游戏对战匹配管理设计
代码框架:
具体代码实现:
class matcher {private:/*普通选手匹配队列*/match_queue<uint64_t> _q_normal;/*高手匹配队列*/match_queue<uint64_t> _q_high;/*大神匹配队列*/match_queue<uint64_t> _q_super;/*对应三个匹配队列的处理线程*/std::thread _th_normal;std::thread _th_high;std::thread _th_super;room_manager *_rm;user_table *_ut;online_manager *_om;private:void handle_match(match_queue<uint64_t> &mq) {while(1) {//1. 判断队列人数是否大于2,<2则阻塞等待while (mq.size() < 2) {mq.wait();} //2. 走下来代表人数够了,出队两个玩家uint64_t uid1, uid2;bool ret = mq.pop(uid1);if (ret == false) { continue; }ret = mq.pop(uid2);if (ret == false) { this->add(uid1); continue; }//3. 校验两个玩家是否在线,如果有人掉线,则要吧另一个人重新添加入队列wsserver_t::connection_ptr conn1 = _om->get_conn_from_hall(uid1);if (conn1.get() == nullptr) {this->add(uid2); continue;}wsserver_t::connection_ptr conn2 = _om->get_conn_from_hall(uid2);if (conn2.get() == nullptr) {this->add(uid1); continue;}//4. 为两个玩家创建房间,并将玩家加入房间中room_ptr rp = _rm->create_room(uid1, uid2);if (rp.get() == nullptr) {this->add(uid1);this->add(uid2);continue;}//5. 对两个玩家进行响应Json::Value resp;resp["optype"] = "match_success";resp["result"] = true;std::string body;json_util::serialize(resp, body);conn1->send(body);conn2->send(body);}}void th_normal_entry() { return handle_match(_q_normal); }void th_high_entry() { return handle_match(_q_high); }void th_super_entry() { return handle_match(_q_super); }public:matcher(room_manager *rm, user_table *ut, online_manager *om): _rm(rm), _ut(ut), _om(om),_th_normal(std::thread(&matcher::th_normal_entry, this)),_th_high(std::thread(&matcher::th_high_entry, this)),_th_super(std::thread(&matcher::th_super_entry, this)){DLOG("游戏匹配模块初始化完毕....");}bool add(uint64_t uid) {//根据玩家的天梯分数,来判定玩家档次,添加到不同的匹配队列// 1. 根据用户ID,获取玩家信息Json::Value user;bool ret = _ut->select_by_id(uid, user);if (ret == false) {DLOG("获取玩家:%d 信息失败!!", uid);return false;}int score = user["score"].asInt();// 2. 添加到指定的队列中if (score < 2000) {_q_normal.push(uid);}else if (score >= 2000 && score < 3000) {_q_high.push(uid);}else {_q_super.push(uid);}return true;}bool del(uint64_t uid) {Json::Value user;bool ret = _ut->select_by_id(uid, user);if (ret == false) {DLOG("获取玩家:%d 信息失败!!", uid);return false;}int score = user["score"].asInt();// 2. 添加到指定的队列中if (score < 2000) {_q_normal.remove(uid);}else if (score >= 2000 && score < 3000) {_q_high.remove(uid);}else {_q_super.remove(uid);}return true;}
};
语法没有基本问题
16.服务器业务请求流程
17.服务器网络通信接口设计
数据格式:
静态资源请求后面的内容就是动态资源请求
18.服务器类设计
整体框架:
服务器类网络通信代码编写
void http_callback(websocketpp::connection_hdl hdl){wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);websocketpp::http::parser::request req = conn->get_request();std::string method = req.get_method();std::string uri = req.get_uri();std::string pathname = _web_root + uri;std::string body;file_util::read(pathname,body);//read函数在util头文件里面实现过了conn->set_status(websocketpp::http::status_code::ok);conn->set_body(body);}public:/*进行成员初始化,以及服务器回调函数的设置*/gobang_server(const std::string &host,const std::string &user,const std::string &pass,const std::string &dbname,uint16_t port = 3306,const std::string &wwwroot = WWWROOT):_web_root(wwwroot), _ut(host, user, pass, dbname, port),_rm(&_ut, &_om), _sm(&_wssrv), _mm(&_rm, &_ut, &_om) {_wssrv.set_access_channels(websocketpp::log::alevel::none);_wssrv.init_asio();_wssrv.set_reuse_addr(true);//类的成员函数在绑定的时候 必须要取地址并且使用类名_wssrv.set_http_handler(std::bind(&gobang_server::http_callback, this, std::placeholders::_1));_wssrv.set_open_handler(std::bind(&gobang_server::wsopen_callback, this, std::placeholders::_1));_wssrv.set_close_handler(std::bind(&gobang_server::wsclose_callback, this, std::placeholders::_1));_wssrv.set_message_handler(std::bind(&gobang_server::wsmsg_callback, this, std::placeholders::_1, std::placeholders::_2));}/*启动服务器*/void start(int port) {_wssrv.listen(port);_wssrv.start_accept();_wssrv.run();}
};
服务器类网络通信编译测试
成功!
19.服务器业务请求处理分类
20.服务器业务静态资源请求处理
void file_handler(wsserver_t::connection_ptr &conn){//静态资源请求的处理//1.获取到请求uri-资源路径,了解客户端请求的页面文件名称websocketpp::http::parser::request req = conn->get_request();//获取请求信息std::string uri = req.get_uri();//获取uri//2.组合出文件的实际路径 相对根目录 + uristd::string realpath = _web_root + uri;//3.如果请求的是一个目录,增加一个后缀 login.html// 如何判断是否是目录呢?看最后一个字符如果是/,就代表是目录if(realpath.back() == '/'){realpath += "login.html";}//4.读取文件内容Json::Value resp_json;std::string body;bool ret = file_util::read(realpath, body);// 4.1.文件如果不存在,读取文件内容失败,返回404if(ret == false){body += "<html>";body += "<head>";body += "<meta charset='UTF-8'/>";body += "</head>";body += "<body>";body += "<h1> Not Found </h1>";body += "</body>";//需要自己去设置相应状态码conn->set_status(websocketpp::http::status_code::not_found);conn->set_body(body);return;}//5.设置相应正文conn->set_body(body);conn->set_status(websocketpp::http::status_code::ok);}
服务器业务静态资源请求测试
成功!
21.服务器业务用户注册后台处理
后端框架
具体代码:
void http_resp(wsserver_t::connection_ptr &conn, bool result, websocketpp::http::status_code::value code, const std::string &reason) {Json::Value resp_json;resp_json["result"] = result;resp_json["reason"] = reason;std::string resp_body;json_util::serialize(resp_json, resp_body);conn->set_status(code);conn->set_body(resp_body);conn->append_header("Content-Type", "application/json");return;}void reg(wsserver_t::connection_ptr &conn) {//用户注册功能请求的处理websocketpp::http::parser::request req = conn->get_request();//1. 获取到请求正文std::string req_body = conn->get_request_body();//2. 对正文进行json反序列化,得到用户名和密码Json::Value login_info;bool ret = json_util::unserialize(req_body, login_info);if (ret == false) {DLOG("反序列化注册信息失败");return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请求的正文格式错误");}//3. 进行数据库的用户新增操作if (login_info["username"].isNull() || login_info["password"].isNull()) {DLOG("用户名密码不完整");return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名/密码");}ret = _ut.insert(login_info);if (ret == false) {DLOG("向数据库插入数据失败");return http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名已经被占用!");}// 如果成功了,则返回200return http_resp(conn, true, websocketpp::http::status_code::ok, "注册用户成功");}
22.服务器业务用户注册前端处理
具体逻辑:
具体代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>注册</title><link rel="stylesheet" href="./css/common.css"><link rel="stylesheet" href="./css/login.css">
</head>
<body><div class="nav">网络五子棋对战游戏</div><div class="login-container"><!-- 登录界面的对话框 --><div class="login-dialog"><!-- 提示信息 --><h3>注册</h3><!-- 这个表示一行 --><div class="row"><span>用户名</span><input type="text" id="user_name" name="username"></div><!-- 这是另一行 --><div class="row"><span>密码</span><input type="password" id="password" name="password"></div><!-- 提交按钮 --><div class="row"><button id="submit" onclick="reg()">提交</button></div></div></div> <script src="js/jquery.min.js"></script><script>//1. 给按钮添加点击事件,调用注册函数//2. 封装实现注册函数function reg() {// 1. 获取两个输入框空间中的数据,组织成为一个json串var reg_info = {username: document.getElementById("user_name").value,password: document.getElementById("password").value};console.log(JSON.stringify(reg_info));// 2. 通过ajax向后台发送用户注册请求$.ajax({url : "/reg",type : "post",data : JSON.stringify(reg_info),success : function(res) {if (res.result == false) {// 4. 如果请求失败,则清空两个输入框内容,并提示错误原因document.getElementById("user_name").value = "";document.getElementById("password").value = "";alert(res.reason);}else {// 3. 如果请求成功,则跳转的登录页面alert(res.reason);window.location.assign("/login.html");}},error : function(xhr) {document.getElementById("user_name").value = "";document.getElementById("password").value = "";alert(JSON.stringify(xhr));}})}</script>
</body>
</html>
23.服务器业务用户注册联调测试
有bug
问题:
注册的时候,往数据库中插入数据失败了
注册的问题是,你的mysql版本是8.0,password这个函数在mysql8.0已经被移除了,简单说就没有这个函数了,所以插入失败了,这里我直接换成了md5这个相对简单的哈希算法
24.服务器业务用户登录后台处理
代码整体框架:
具体代码:
void login(wsserver_t::connection_ptr &conn) {//用户登录功能请求的处理//1. 获取请求正文,并进行json反序列化,得到用户名和密码std::string req_body = conn->get_request_body();Json::Value login_info;bool ret = json_util::unserialize(req_body, login_info);if (ret == false) {DLOG("反序列化登录信息失败");return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请求的正文格式错误");}//2. 校验正文完整性,进行数据库的用户信息验证if (login_info["username"].isNull() || login_info["password"].isNull()) {DLOG("用户名密码不完整");return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名/密码");}ret = _ut.login(login_info);if (ret == false) {// 1. 如果验证失败,则返回400DLOG("用户名密码错误");return http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名密码错误");}//3. 如果验证成功,给客户端创建sessionuint64_t uid = login_info["id"].asUInt64();session_ptr ssp = _sm.create_session(uid, LOGIN);if (ssp.get() == nullptr) {DLOG("创建会话失败");return http_resp(conn, false, websocketpp::http::status_code::internal_server_error , "创建会话失败");}_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);//4. 设置响应头部:Set-Cookie,将sessionid通过cookie返回std::string cookie_ssid = "SSID=" + std::to_string(ssp->ssid());conn->append_header("Set-Cookie", cookie_ssid);return http_resp(conn, true, websocketpp::http::status_code::ok , "登录成功");}
25.服务器业务用户登录前端处理:
整体框架:
具体代码的实现:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>登录</title><link rel="stylesheet" href="./css/common.css"><link rel="stylesheet" href="./css/login.css">
</head>
<body><div class="nav">网络五子棋对战游戏</div><div class="login-container"><!-- 登录界面的对话框 --><div class="login-dialog"><!-- 提示信息 --><h3>登录</h3><!-- 这个表示一行 --><div class="row"><span>用户名</span><input type="text" id="user_name"></div><!-- 这是另一行 --><div class="row"><span>密码</span><input type="password" id="password"></div><!-- 提交按钮 --><div class="row"><button id="submit" onclick="login()">提交</button></div></div></div><script src="./js/jquery.min.js"></script><script>//1. 给按钮添加点击事件,调用登录请求函数//2. 封装登录请求函数function login() {// 1. 获取输入框中的用户名和密码,并组织json对象var login_info = {username: document.getElementById("user_name").value,password: document.getElementById("password").value};// 2. 通过ajax向后台发送登录验证请求$.ajax({url: "/login",type: "post",data: JSON.stringify(login_info),success: function(result) {// 3. 如果验证通过,则跳转游戏大厅页面alert("登录成功");window.location.assign("/game_hall.html");},error: function(xhr) {// 4. 如果验证失败,则提示错误信息,并清空输入框alert(JSON.stringify(xhr));document.getElementById("user_name").value = "";document.getElementById("password").value = "";}})}</script>
</body>
</html>
26.服务器业务用户信息获取后台处理
代码整体框架:
具体代码:
bool get_cookie_val(const std::string &cookie_str, const std::string &key, std::string &val) {// Cookie: SSID=XXX; path=/; //1. 以 ; 作为间隔,对字符串进行分割,得到各个单个的cookie信息std::string sep = "; ";std::vector<std::string> cookie_arr;string_util::split(cookie_str, sep, cookie_arr);for (auto str : cookie_arr) {//2. 对单个cookie字符串,以 = 为间隔进行分割,得到key和valstd::vector<std::string> tmp_arr;string_util::split(str, "=", tmp_arr);if (tmp_arr.size() != 2) { continue; }if (tmp_arr[0] == key) {val = tmp_arr[1];return true;}}return false;}void info(wsserver_t::connection_ptr &conn) {//用户信息获取功能请求的处理Json::Value err_resp;// 1. 获取请求信息中的Cookie,从Cookie中获取ssidstd::string cookie_str = conn->get_request_header("Cookie");if (cookie_str.empty()) {//如果没有cookie,返回错误:没有cookie信息,让客户端重新登录return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到cookie信息,请重新登录");}// 1.5. 从cookie中取出ssidstd::string ssid_str;bool ret = get_cookie_val(cookie_str, "SSID", ssid_str);if (ret == false) {//cookie中没有ssid,返回错误:没有ssid信息,让客户端重新登录return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到ssid信息,请重新登录");}// 2. 在session管理中查找对应的会话信息session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));if (ssp.get() == nullptr) {//没有找到session,则认为登录已经过期,需要重新登录return http_resp(conn, true, websocketpp::http::status_code::bad_request, "登录过期,请重新登录");}// 3. 从数据库中取出用户信息,进行序列化发送给客户端uint64_t uid = ssp->get_user();Json::Value user_info;ret = _ut.select_by_id(uid, user_info);if (ret == false) {//获取用户信息失败,返回错误:找不到用户信息return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到用户信息,请重新登录");}std::string body;json_util::serialize(user_info, body);conn->set_body(body);conn->append_header("Content-Type", "application/json");conn->set_status(websocketpp::http::status_code::ok);// 4. 刷新session的过期时间_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);}
27.服务器业务用户信息获取前端处理
实现逻辑:
原码:
login.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>登录</title><link rel="stylesheet" href="./css/common.css"><link rel="stylesheet" href="./css/login.css">
</head>
<body><div class="nav">网络五子棋对战游戏</div><div class="login-container"><!-- 登录界面的对话框 --><div class="login-dialog"><!-- 提示信息 --><h3>登录</h3><!-- 这个表示一行 --><div class="row"><span>用户名</span><input type="text" id="user_name"></div><!-- 这是另一行 --><div class="row"><span>密码</span><input type="password" id="password"></div><!-- 提交按钮 --><div class="row"><button id="submit" onclick="login()">提交</button></div></div></div><script src="./js/jquery.min.js"></script><script>//1. 给按钮添加点击事件,调用登录请求函数//2. 封装登录请求函数function login() {// 1. 获取输入框中的用户名和密码,并组织json对象var login_info = {username: document.getElementById("user_name").value,password: document.getElementById("password").value};// 2. 通过ajax向后台发送登录验证请求$.ajax({url: "/login",type: "post",data: JSON.stringify(login_info),success: function(result) {// 3. 如果验证通过,则跳转游戏大厅页面alert("登录成功");window.location.assign("/game_hall.html");},error: function(xhr) {// 4. 如果验证失败,则提示错误信息,并清空输入框alert(JSON.stringify(xhr));document.getElementById("user_name").value = "";document.getElementById("password").value = "";}})}</script>
</body>
</html>
28.服务器业务用户登录联调测试
session的自动创建和自动销毁
从登录页面跳转到游戏页面的成功!
服务器业务用户信息获取后台处理
从上一个图可以看到,中间框框是黑色的,并没有显示出来用户的信息
29.用户信息获取功能请求的处理
代码整体逻辑
原码:
bool get_cookie_val(const std::string &cookie_str, const std::string &key, std::string &val) {// Cookie: SSID=XXX; path=/; //1. 以 ; 作为间隔,对字符串进行分割,得到各个单个的cookie信息std::string sep = "; ";std::vector<std::string> cookie_arr;string_util::split(cookie_str, sep, cookie_arr);for (auto str : cookie_arr) {//2. 对单个cookie字符串,以 = 为间隔进行分割,得到key和valstd::vector<std::string> tmp_arr;string_util::split(str, "=", tmp_arr);if (tmp_arr.size() != 2) { continue; }if (tmp_arr[0] == key) {val = tmp_arr[1];return true;}}return false;}void info(wsserver_t::connection_ptr &conn) {//用户信息获取功能请求的处理Json::Value err_resp;// 1. 获取请求信息中的Cookie,从Cookie中获取ssidstd::string cookie_str = conn->get_request_header("Cookie");if (cookie_str.empty()) {//如果没有cookie,返回错误:没有cookie信息,让客户端重新登录return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到cookie信息,请重新登录");}// 1.5. 从cookie中取出ssidstd::string ssid_str;bool ret = get_cookie_val(cookie_str, "SSID", ssid_str);if (ret == false) {//cookie中没有ssid,返回错误:没有ssid信息,让客户端重新登录return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到ssid信息,请重新登录");}// 2. 在session管理中查找对应的会话信息session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));if (ssp.get() == nullptr) {//没有找到session,则认为登录已经过期,需要重新登录return http_resp(conn, true, websocketpp::http::status_code::bad_request, "登录过期,请重新登录");}// 3. 从数据库中取出用户信息,进行序列化发送给客户端uint64_t uid = ssp->get_user();Json::Value user_info;ret = _ut.select_by_id(uid, user_info);if (ret == false) {//获取用户信息失败,返回错误:找不到用户信息return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到用户信息,请重新登录");}std::string body;json_util::serialize(user_info, body);conn->set_body(body);conn->append_header("Content-Type", "application/json");conn->set_status(websocketpp::http::status_code::ok);// 4. 刷新session的过期时间_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);}
30.服务器业务用户信息获取前端处理
game_hall.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>游戏大厅</title><link rel="stylesheet" href="./css/common.css"><link rel="stylesheet" href="./css/game_hall.css">
</head>
<body><div class="nav">网络五子棋对战游戏</div><!-- 整个页面的容器元素 --><div class="container"><!-- 这个 div 在 container 中是处于垂直水平居中这样的位置的 --><div><!-- 展示用户信息 --><div id="screen"></div><!-- 匹配按钮 --><div id="match-button">开始匹配</div></div></div><script src="./js/jquery.min.js"></script><script>var ws_url = "ws://" + location.host + "/hall";var ws_hdl = null;window.onbeforeunload = function() {ws_hdl.close();}//按钮有两个状态:没有进行匹配的状态,正在匹配中的状态var button_flag = "stop";//点击按钮的事件处理:var be = document.getElementById("match-button");be.onclick = function() {if (button_flag == "stop") {//1. 没有进行匹配的状态下点击按钮,发送对战匹配请求var req_json = {optype: "match_start"}ws_hdl.send(JSON.stringify(req_json));}else {//2. 正在匹配中的状态下点击按钮,发送停止对战匹配请求var req_json = {optype: "match_stop"}ws_hdl.send(JSON.stringify(req_json));}}function get_user_info() {$.ajax({url: "/info",type: "get",success: function(res) {var info_html = "<p>" + "用户:" + res.username + " 积分:" + res.score + "</br>" + "比赛场次:" + res.total_count + " 获胜场次:" + res.win_count + "</p>";var screen_div = document.getElementById("screen");screen_div.innerHTML = info_html;ws_hdl = new WebSocket(ws_url);ws_hdl.onopen = ws_onopen;ws_hdl.onclose = ws_onclose;ws_hdl.onerror = ws_onerror;ws_hdl.onmessage = ws_onmessage;},error: function(xhr) {alert(JSON.stringify(xhr));location.replace("/login.html");}})}function ws_onopen() {console.log("websocket onopen");}function ws_onclose() {console.log("websocket onopen");}function ws_onerror() {console.log("websocket onopen");}function ws_onmessage(evt) {var rsp_json = JSON.parse(evt.data);if (rsp_json.result == false) {alert(evt.data);location.replace("/login.html");return;}if (rsp_json["optype"] == "hall_ready") {alert("游戏大厅连接建立成功!");}else if (rsp_json["optype"] == "match_success") {//对战匹配成功alert("对战匹配成功,进入游戏房间!");location.replace("/game_room.html");}else if (rsp_json["optype"] == "match_start") {console.log("玩家已经加入匹配队列");button_flag = "start";be.innerHTML = "匹配中....点击按钮停止匹配!";return;}else if (rsp_json["optype"] == "match_stop"){console.log("玩家已经移除匹配队列");button_flag = "stop";be.innerHTML = "开始匹配";return;}else {alert(evt.data);location.replace("/login.html");//页面的转换return;}}get_user_info();</script>
</body>
</html>
31.服务器业务用户信息获取前后端联调
成功显示用户信息!
32.服务器业务长连接分类
分为两种长连接:(根据uri资源判定来区分)
- 游戏大厅的长连接
- 游戏房间的长连接
33.服务器业务游戏大厅长连接建立成功处理
大厅长连接建立成功后的思路:
代码:
void wsopen_game_hall(wsserver_t::connection_ptr conn) {//游戏大厅长连接建立成功Json::Value resp_json;//1. 登录验证--判断当前客户端是否已经成功登录session_ptr ssp = get_session_by_cookie(conn);if (ssp.get() == nullptr) {return;}//2. 判断当前客户端是否是重复登录if (_om.is_in_game_hall(ssp->get_user()) || _om.is_in_game_room(ssp->get_user())) {resp_json["optype"] = "hall_ready";resp_json["reason"] = "玩家重复登录!";resp_json["result"] = false;return ws_resp(conn, resp_json);}//3. 将当前客户端以及连接加入到游戏大厅_om.enter_game_hall(ssp->get_user(), conn);//4. 给客户端响应游戏大厅连接建立成功resp_json["optype"] = "hall_ready";resp_json["result"] = true;ws_resp(conn, resp_json);//5. 记得将session设置为永久存在_sm.set_session_expire_time(ssp->ssid(), SESSION_FOREVER);}void wsopen_game_room(wsserver_t::connection_ptr conn) {Json::Value resp_json;//1. 获取当前客户端的sessionsession_ptr ssp = get_session_by_cookie(conn);if (ssp.get() == nullptr) {return;}//2. 当前用户是否已经在在线用户管理的游戏房间或者游戏大厅中---在线用户管理if (_om.is_in_game_hall(ssp->get_user()) || _om.is_in_game_room(ssp->get_user())) {resp_json["optype"] = "room_ready";resp_json["reason"] = "玩家重复登录!";resp_json["result"] = false;return ws_resp(conn, resp_json);}//3. 判断当前用户是否已经创建好了房间 --- 房间管理room_ptr rp = _rm.get_room_by_uid(ssp->get_user());if (rp.get() == nullptr) {resp_json["optype"] = "room_ready";resp_json["reason"] = "没有找到玩家的房间信息";resp_json["result"] = false;return ws_resp(conn, resp_json);}//4. 将当前用户添加到在线用户管理的游戏房间中_om.enter_game_room(ssp->get_user(), conn);//5. 将session重新设置为永久存在_sm.set_session_expire_time(ssp->ssid(), SESSION_FOREVER);//6. 回复房间准备完毕resp_json["optype"] = "room_ready";resp_json["result"] = true;resp_json["room_id"] = (Json::UInt64)rp->id();resp_json["uid"] = (Json::UInt64)ssp->get_user();resp_json["white_id"] = (Json::UInt64)rp->get_white_user();resp_json["black_id"] = (Json::UInt64)rp->get_black_user();return ws_resp(conn, resp_json);} void wsopen_callback(websocketpp::connection_hdl hdl){//websocket长连接建立成功之后的处理函数wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);websocketpp::http::parser::request req = conn->get_request();std::string uri = req.get_uri();if (uri == "/hall") {//建立了游戏大厅的长连接return wsopen_game_hall(conn);}else if (uri == "/room") {//建立了游戏房间的长连接return wsopen_game_room(conn);}}
服务器业务游戏大厅和游戏房间的长连接关闭处理
代码:
void wsclose_game_hall(wsserver_t::connection_ptr conn) {//游戏大厅长连接断开的处理//1. 登录验证--判断当前客户端是否已经成功登录session_ptr ssp = get_session_by_cookie(conn);if (ssp.get() == nullptr) {return;}//1. 将玩家从游戏大厅中移除_om.exit_game_hall(ssp->get_user());//2. 将session恢复生命周期的管理,设置定时销毁_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);}void wsclose_game_room(wsserver_t::connection_ptr conn) {//获取会话信息,识别客户端session_ptr ssp = get_session_by_cookie(conn);if (ssp.get() == nullptr) {return;}//1. 将玩家从在线用户管理中移除_om.exit_game_room(ssp->get_user());//2. 将session回复生命周期的管理,设置定时销毁_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);//3. 将玩家从游戏房间中移除,房间中所有用户退出了就会销毁房间_rm.remove_room_user(ssp->get_user());}void wsclose_callback(websocketpp::connection_hdl hdl) {//websocket连接断开前的处理wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);websocketpp::http::parser::request req = conn->get_request();std::string uri = req.get_uri();if (uri == "/hall") {//建立了游戏大厅的长连接return wsclose_game_hall(conn);}else if (uri == "/room") {//建立了游戏房间的长连接return wsclose_game_room(conn);}}
服务器业务游戏大厅消息请求处理
代码:
void wsmsg_game_hall(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg) {Json::Value resp_json;std::string resp_body;//1. 身份验证,当前客户端到底是哪个玩家session_ptr ssp = get_session_by_cookie(conn);if (ssp.get() == nullptr) {return;}//2. 获取请求信息std::string req_body = msg->get_payload();Json::Value req_json;bool ret = json_util::unserialize(req_body, req_json);if (ret == false) {resp_json["result"] = false;resp_json["reason"] = "请求信息解析失败";return ws_resp(conn, resp_json);}//3. 对于请求进行处理:if (!req_json["optype"].isNull() && req_json["optype"].asString() == "match_start"){// 开始对战匹配:通过匹配模块,将用户添加到匹配队列中_mm.add(ssp->get_user());resp_json["optype"] = "match_start";resp_json["result"] = true;return ws_resp(conn, resp_json);}else if (!req_json["optype"].isNull() && req_json["optype"].asString() == "match_stop") {// 停止对战匹配:通过匹配模块,将用户从匹配队列中移除_mm.del(ssp->get_user());resp_json["optype"] = "match_stop";resp_json["result"] = true;return ws_resp(conn, resp_json);}resp_json["optype"] = "unknow";resp_json["reason"] = "请求类型未知";resp_json["result"] = false;return ws_resp(conn, resp_json);}void wsmsg_game_room(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg) {Json::Value resp_json;//1. 获取客户端session,识别客户端身份session_ptr ssp = get_session_by_cookie(conn);if (ssp.get() == nullptr) {DLOG("房间-没有找到会话信息");return;}//2. 获取客户端房间信息room_ptr rp = _rm.get_room_by_uid(ssp->get_user());if (rp.get() == nullptr) {resp_json["optype"] = "unknow";resp_json["reason"] = "没有找到玩家的房间信息";resp_json["result"] = false;DLOG("房间-没有找到玩家房间信息");return ws_resp(conn, resp_json);}//3. 对消息进行反序列化Json::Value req_json;std::string req_body = msg->get_payload();bool ret = json_util::unserialize(req_body, req_json);if (ret == false) {resp_json["optype"] = "unknow";resp_json["reason"] = "请求解析失败";resp_json["result"] = false;DLOG("房间-反序列化请求失败");return ws_resp(conn, resp_json);}DLOG("房间:收到房间请求,开始处理....");//4. 通过房间模块进行消息请求的处理return rp->handle_request(req_json);}void wsmsg_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg) {//websocket长连接通信处理wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);websocketpp::http::parser::request req = conn->get_request();std::string uri = req.get_uri();if (uri == "/hall") {//建立了游戏大厅的长连接return wsmsg_game_hall(conn, msg);}else if (uri == "/room") {//建立了游戏房间的长连接return wsmsg_game_room(conn, msg);}}
34.服务器业务游戏大厅前端事件处理
代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>游戏大厅</title><link rel="stylesheet" href="./css/common.css"><link rel="stylesheet" href="./css/game_hall.css">
</head>
<body><div class="nav">网络五子棋对战游戏</div><!-- 整个页面的容器元素 --><div class="container"><!-- 这个 div 在 container 中是处于垂直水平居中这样的位置的 --><div><!-- 展示用户信息 --><div id="screen"></div><!-- 匹配按钮 --><div id="match-button">开始匹配</div></div></div><script src="./js/jquery.min.js"></script><script>var ws_url = "ws://" + location.host + "/hall";var ws_hdl = null;window.onbeforeunload = function() {ws_hdl.close();}//按钮有两个状态:没有进行匹配的状态,正在匹配中的状态var button_flag = "stop";//点击按钮的事件处理:var be = document.getElementById("match-button");be.onclick = function() {if (button_flag == "stop") {//1. 没有进行匹配的状态下点击按钮,发送对战匹配请求var req_json = {optype: "match_start"}ws_hdl.send(JSON.stringify(req_json));}else {//2. 正在匹配中的状态下点击按钮,发送停止对战匹配请求var req_json = {optype: "match_stop"}ws_hdl.send(JSON.stringify(req_json));}}function get_user_info() {$.ajax({url: "/info",type: "get",success: function(res) {var info_html = "<p>" + "用户:" + res.username + " 积分:" + res.score + "</br>" + "比赛场次:" + res.total_count + " 获胜场次:" + res.win_count + "</p>";var screen_div = document.getElementById("screen");screen_div.innerHTML = info_html;ws_hdl = new WebSocket(ws_url);ws_hdl.onopen = ws_onopen;ws_hdl.onclose = ws_onclose;ws_hdl.onerror = ws_onerror;ws_hdl.onmessage = ws_onmessage;},error: function(xhr) {alert(JSON.stringify(xhr));location.replace("/login.html");}})}function ws_onopen() {console.log("websocket onopen");}function ws_onclose() {console.log("websocket onopen");}function ws_onerror() {console.log("websocket onopen");}function ws_onmessage(evt) {var rsp_json = JSON.parse(evt.data);if (rsp_json.result == false) {alert(evt.data);location.replace("/login.html");return;}if (rsp_json["optype"] == "hall_ready") {alert("游戏大厅连接建立成功!");}else if (rsp_json["optype"] == "match_success") {//对战匹配成功alert("对战匹配成功,进入游戏房间!");location.replace("/game_room.html");}else if (rsp_json["optype"] == "match_start") {console.log("玩家已经加入匹配队列");button_flag = "start";be.innerHTML = "匹配中....点击按钮停止匹配!";return;}else if (rsp_json["optype"] == "match_stop"){console.log("玩家已经移除匹配队列");button_flag = "stop";be.innerHTML = "开始匹配";return;}else {alert(evt.data);location.replace("/login.html");//页面的转换return;}}get_user_info();</script>
</body>
</html>
35.服务器业务游戏大厅所有功能联调测试
注意不能使用同一个浏览器进行测试,因为同一个浏览器的cookie和session会互相影响
成功相应!
服务器业务游戏房间长连接关闭处理
代码:
void wsclose_game_hall(wsserver_t::connection_ptr conn) {//游戏大厅长连接断开的处理//1. 登录验证--判断当前客户端是否已经成功登录session_ptr ssp = get_session_by_cookie(conn);if (ssp.get() == nullptr) {return;}//1. 将玩家从游戏大厅中移除_om.exit_game_hall(ssp->get_user());//2. 将session恢复生命周期的管理,设置定时销毁_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);}void wsclose_game_room(wsserver_t::connection_ptr conn) {//获取会话信息,识别客户端session_ptr ssp = get_session_by_cookie(conn);if (ssp.get() == nullptr) {return;}//1. 将玩家从在线用户管理中移除_om.exit_game_room(ssp->get_user());//2. 将session回复生命周期的管理,设置定时销毁_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);//3. 将玩家从游戏房间中移除,房间中所有用户退出了就会销毁房间_rm.remove_room_user(ssp->get_user());}void wsclose_callback(websocketpp::connection_hdl hdl) {//websocket连接断开前的处理wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);websocketpp::http::parser::request req = conn->get_request();std::string uri = req.get_uri();if (uri == "/hall") {//建立了游戏大厅的长连接return wsclose_game_hall(conn);}else if (uri == "/room") {//建立了游戏房间的长连接return wsclose_game_room(conn);}}
36.服务器业务游戏房间前端长连接建立
代码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>游戏房间</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/game_room.css">
</head>
<body><div class="nav">网络五子棋对战游戏</div><div class="container"><div id="chess_area"><!-- 棋盘区域, 需要基于 canvas 进行实现 --><canvas id="chess" width="450px" height="450px"></canvas><!-- 显示区域 --><div id="screen"> 等待玩家连接中... </div></div><div id="chat_area" width="400px" height="300px"><div id="chat_show"><p id="self_msg">你好!</p></br><p id="peer_msg">你好!</p></br><p id="peer_msg">leihoua~</p></br></div><div id="msg_show"><input type="text" id="chat_input"><button id="chat_button">发送</button></div></div></div><script>let chessBoard = [];let BOARD_ROW_AND_COL = 15;let chess = document.getElementById('chess');let context = chess.getContext('2d');//获取chess控件的2d画布var ws_url = "ws://" + location.host + "/room";var ws_hdl = new WebSocket(ws_url);var room_info = null;//用于保存房间信息 var is_me;function initGame() {initBoard();context.strokeStyle = "#BFBFBF";// 背景图片let logo = new Image();logo.src = "image/sky.jpeg";logo.onload = function () {// 绘制图片context.drawImage(logo, 0, 0, 450, 450);// 绘制棋盘drawChessBoard();}}function initBoard() {for (let i = 0; i < BOARD_ROW_AND_COL; i++) {chessBoard[i] = [];for (let j = 0; j < BOARD_ROW_AND_COL; j++) {chessBoard[i][j] = 0;}}}// 绘制棋盘网格线function drawChessBoard() {for (let i = 0; i < BOARD_ROW_AND_COL; i++) {context.moveTo(15 + i * 30, 15);context.lineTo(15 + i * 30, 430); //横向的线条context.stroke();context.moveTo(15, 15 + i * 30);context.lineTo(435, 15 + i * 30); //纵向的线条context.stroke();}}//绘制棋子function oneStep(i, j, isWhite) {if (i < 0 || j < 0) return;context.beginPath();context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI);context.closePath();var gradient = context.createRadialGradient(15 + i * 30 + 2, 15 + j * 30 - 2, 13, 15 + i * 30 + 2, 15 + j * 30 - 2, 0);// 区分黑白子if (!isWhite) {gradient.addColorStop(0, "#0A0A0A");gradient.addColorStop(1, "#636766");} else {gradient.addColorStop(0, "#D1D1D1");gradient.addColorStop(1, "#F9F9F9");}context.fillStyle = gradient;context.fill();}//棋盘区域的点击事件chess.onclick = function (e) {// 1. 获取下棋位置,判断当前下棋操作是否正常// 1. 当前是否轮到自己走棋了// 2. 当前位置是否已经被占用// 2. 向服务器发送走棋请求if (!is_me) {alert("等待对方走棋....");return;}let x = e.offsetX;let y = e.offsetY;// 注意, 横坐标是列, 纵坐标是行// 这里是为了让点击操作能够对应到网格线上let col = Math.floor(x / 30);let row = Math.floor(y / 30);if (chessBoard[row][col] != 0) {alert("当前位置已有棋子!");return;}//oneStep(col, row, true);//向服务器发送走棋请求,收到响应后,再绘制棋子send_chess(row, col);}function send_chess(r, c) {var chess_info = {optype : "put_chess",room_id: room_info.room_id,uid: room_info.uid,row: r,col: c};ws_hdl.send(JSON.stringify(chess_info));console.log("click:" + JSON.stringify(chess_info));}window.onbeforeunload = function() {ws_hdl.close();}ws_hdl.onopen = function() {console.log("房间长连接建立成功");}ws_hdl.onclose = function() {console.log("房间长连接断开");}ws_hdl.onerror = function() {console.log("房间长连接出错");}function set_screen(me) {var screen_div = document.getElementById("screen");if (me) {screen_div.innerHTML = "轮到己方走棋...";}else {screen_div.innerHTML = "轮到对方走棋...";}}ws_hdl.onmessage = function(evt) {//1. 在收到room_ready之后进行房间的初始化// 1. 将房间信息保存起来var info = JSON.parse(evt.data);console.log(JSON.stringify(info));if (info.optype == "room_ready") {room_info = info;is_me = room_info.uid == room_info.white_id ? true : false;set_screen(is_me);initGame();}else if (info.optype == "put_chess"){console.log("put_chess" + evt.data);//2. 走棋操作// 3. 收到走棋消息,进行棋子绘制if (info.result == false) {alert(info.reason);return;}//当前走棋的用户id,与我自己的用户id相同,就是我自己走棋,走棋之后,就轮到对方了is_me = info.uid == room_info.uid ? false : true;//绘制棋子的颜色,应该根据当前下棋角色的颜色确定isWhite = info.uid == room_info.white_id ? true : false;//绘制棋子if (info.row != -1 && info.col != -1){oneStep(info.col, info.row, isWhite);//设置棋盘信息chessBoard[info.row][info.col] = 1;}//是否有胜利者if (info.winner == 0) {return;}var screen_div = document.getElementById("screen");if (room_info.uid == info.winner) {screen_div.innerHTML = info.reason;}else {screen_div.innerHTML = "你输了";}var chess_area_div = document.getElementById("chess_area");var button_div = document.createElement("div");button_div.innerHTML = "返回大厅";button_div.onclick = function() {ws_hdl.close();location.replace("/game_hall.html");}chess_area_div.appendChild(button_div);} else if (info.optype == "chat") {//收到一条消息,判断result,如果为true则渲染一条消息到显示框中if(info.result == false) {alert(info.reason);return;}var msg_div = document.createElement("p");msg_div.innerHTML = info.message;if (info.uid == room_info.uid) {msg_div.setAttribute("id", "self_msg");}else {msg_div.setAttribute("id", "peer_msg");}var br_div = document.createElement("br");var msg_show_div = document.getElementById("chat_show");msg_show_div.appendChild(msg_div);msg_show_div.appendChild(br_div);document.getElementById("chat_input").value = "";}}//3. 聊天动作// 1. 捕捉聊天输入框消息// 2. 给发送按钮添加点击事件,点击俺就的时候,获取到输入框消息,发送给服务器var cb_div = document.getElementById("chat_button");cb_div.onclick = function() {var send_msg = {optype : "chat",room_id : room_info.room_id,uid : room_info.uid,message : document.getElementById("chat_input").value};ws_hdl.send(JSON.stringify(send_msg));}</script>
</body>
</html>
37.走棋操作:
思路
聊天动作
项目总结:
核心技术:
问题:
这句代码是什么意思?
这段代码是什么意思?
这句话什么意思
用户请求什么就返回什么,是什么意思?
上面这两部分代码什么含义?第二部分为什么样式代码没有返回回去?
相关文章:
【C++】在线五子棋对战项目网页版
目录 1.Websocket 1.1.Websocket的简单认识 1.2.什么是轮询呢? 1.3.websocket协议切换过程 1.4.websocketpp库常用接口认识 1.5.websocketpp库搭建服务器流程 1.6.websocketpp库搭建服务器 2.mysqlclient库-接口认识 3.项目模块的划分: 4.项目…...
【Docker】搭建一个功能强大的自托管虚拟浏览器 - n.eko
前言 本教程基于群晖的NAS设备DS423的docker功能进行搭建,DSM版本为 DSM 7.2.2-72806 Update 2。 n.eko 支持多种类型浏览器在其虚拟环境中运行,本次教程使用 Chromium 浏览器镜像进行演示,支持访问内网设备和公网地址。 简介 n.eko 是…...
学习ASP.NET Core的身份认证(基于JwtBearer的身份认证7)
本文验证基于请求头中传递token信息的认证方式,webapi项目的控制器类中新建如下函数,仅通过验证的客户端能调用,需要客户端请求在Header中添加’Authorization’: Bearer token’的键值对且通过token验证后才能调用。 [Authorize] [HttpGet]…...
Linux 内核自旋锁spinlock(一)
文章目录 前言一、自旋锁1.1 简介1.2 API1.2.1 spin_lock/spin_unlock1.2.2 spin_lock_irq/spin_unlock_irq1.2.3 spin_lock_irqsave/spin_unlock_irqstore1.2.4 spin_lock_bh/spin_unlock_bh1.2.5 补充 二、自选锁原理三、自旋锁在内核的使用3.1 struct file3.2 struct dentry…...
JAVA 使用反射比较对象属性的变化,记录修改日志。使用注解【策略模式】,来进行不同属性枚举值到中英文描述的切换,支持前端国际化。
1.首先定义一个接口,接口中有两个方法,分别是将属性转换成英文描述和中文描述。 其实就是将数据库中记录的 0 1 ,转换成后面的描述 这边定义了中文转换为默认方法,是因为有些属性不需要进行中文转换,或者该属性的枚举…...
利用免费GIS工具箱实现高斯泼溅切片,将 PLY 格式转换为 3dtiles
在地理信息系统(GIS)和三维数据处理领域,不同数据格式有其独特应用场景与优势。PLY(Polygon File Format)格式常用于存储多边形网格数据,而 3DTiles 格式在 Web 端三维场景展示等方面表现出色。将 PLY 格式…...
Mysql视图(学习自用)
一、概念 二、创建视图 三、查询视图 四、修改视图 五、删除视图 六、视图检查选项 七、视图的更新 八、视图的作用...
企业内训|基于华为昇腾910B算力卡的大模型部署和调优-上海某央企智算中心
近日上海,TsingtaoAI为某央企智算中心交付华为昇腾910B算力卡的大模型部署和调优课程。课程深入讲解如何在昇腾NPU上高效地训练、调优和部署PyTorch与Transformer模型,并结合实际应用场景,探索如何优化和迁移模型至昇腾NPU平台。课程涵盖从模…...
linux下一些参数的说明
/proc/sys/net/corebpf_jit_enable 作用:用于控制是否启用BPF(Berkeley Packet Filter)的即时编译(JIT)功能。当启用该功能时,JIT编译器会将eBPF(extended Berkeley Packet Filter)…...
CKS认证 | Day1 K8s集群部署与安全配置
一、K8s安全运维概述 Kubernetes(K8s) 是一个广泛使用的容器编排平台,用于自动化部署、扩展和管理容器化应用程序。随着 K8s 在生产环境中的普及,安全运维成为确保系统稳定性和数据安全的关键。 1.1 安全运维的重要性 万物互联&…...
新手上路:Anaconda虚拟环境创建和配置以使用PyTorch和DGL
文章目录 前言步骤 1: 安装 Anaconda步骤 2: 创建新的 Anaconda 环境步骤 3: 安装最新版本的 PyTorch步骤 4: 安装特定版本的 PyTorch步骤 5: 安装最新版本的 DGL步骤 6: 安装特定版本的 DGL步骤 7: Pycharm中使用虚拟环境解释器第一种情况:创建新项目第二种情况&am…...
什么是PCB的Mark点?如何进行设计Mark点?
一、什么是Mark点? PCB的Mark点,也被称为基准点或光学定位点,是在印刷电路板(Printed Circuit Board, PCB)上设置的一种特殊标记。 图1.1 PCB的Mark点 Mark点的主要作用是为表面贴装技术(Surface Mount…...
Spark SQL中的from_json函数详解
Spark SQL中的from_json函数详解 在Spark SQL中,from_json是一个用于解析JSON数据的函数,主要用于将JSON格式的字符串解析为结构化的数据(即StructType或其他Spark SQL数据类型)。这个函数在处理半结构化数据(如JSON日…...
Vue 引入及简单示例
Vue 渐进式JavaScript 框架 学习笔记 - Vue 引入及简单示例 目录 与jquery区别 Vue引入 两种方式引入 下载到本地 代码结构 简单示例 Style中引入vue.js 对vue语法进行解析 对三目运算符支持 设置变量(状态) 总结 与jquery区别 不需要手动操…...
JDK长期支持版本(LTS)
https://blogs.oracle.com/java/post/the-arrival-of-java-23 jdk长期支持版本(LTS):JDK 8、11、17、21:...
审计文件标识作为水印打印在pdf页面边角
目录 说明 说明 将审计文件的所需要贴的编码直接作为水印贴在页面四个角落,节省辨别时间 我曾经写过一个给pdf页面四个角落加上文件名水印的python脚本,现在需要加一个图形界面进一步加强其实用性。首先通过路径浏览指定文件路径,先检测该路…...
图片专栏——概念
欢迎来到图片世界,大家一起学习交流! 1. 像素(Pixel) 定义:像素是图像的最小单位,是“图像元素”的缩写。你可以把像素想象成拼图中的一个最小块,无数个像素组合在一起就形成了完整的图像。作用ÿ…...
江天科技主要产品销售单价下滑,应收账款、存货周转率大幅下降
《港湾商业观察》廖紫雯 日前,苏州江天包装科技股份有限公司(以下简称:江天科技)冲击北交所,保荐机构为国投证券。 江天科技主要从事标签印刷产品的研发、生产与销售,公司主要产品包括薄膜类和纸张类的不…...
HTB:Remote[WriteUP]
目录 连接至HTB服务器并启动靶机 信息收集 使用rustscan对靶机TCP端口进行开放扫描 将靶机TCP开放端口号提取并保存 使用nmap对靶机TCP开放端口进行脚本、服务扫描 使用nmap对靶机TCP开放端口进行漏洞、系统扫描 使用nmap对靶机常用UDP端口进行开放扫描 使用ftp尝试匿名…...
【开源免费】基于SpringBoot+Vue.JS密接者跟踪系统(JAVA毕业设计)
本文项目编号 T 145 ,文末自助获取源码 \color{red}{T145,文末自助获取源码} T145,文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…...
机器学习10-解读CNN代码Pytorch版
机器学习10-解读CNN代码Pytorch版 我个人是Java程序员,关于Python代码的使用过程中的相关代码事项,在此进行记录 文章目录 机器学习10-解读CNN代码Pytorch版1-核心逻辑脉络2-参考网址3-解读CNN代码Pytorch版本1-MNIST数据集读取2-CNN网络的定义1-无注释版…...
Python 电脑定时关机工具
Python 电脑定时关机工具 相关资源文件已经打包成EXE文件,可双击直接运行程序,且文章末尾已附上相关源码,以供大家学习交流,博主主页还有更多Python相关程序案例,秉着开源精神的想法,望大家喜欢,…...
机器学习09-Pytorch功能拆解
机器学习09-Pytorch功能拆解 我个人是Java程序员,关于Python代码的使用过程中的相关代码事项,在此进行记录 文章目录 机器学习09-Pytorch功能拆解1-核心逻辑脉络2-个人备注3-Pytorch软件包拆解1-Python有参和无参构造构造方法的基本语法示例解释注意事项…...
Qt之QDjango-db的简单使用
QDjango是一款由C编写、依托于Qt库的Web开发框架,其设计理念受到了广受欢迎的Python框架Django的影响。这个项目旨在提供一个高效、灵活且易于使用的工具集,帮助开发者构建高质量的Web应用。其项目地址: https://gitcode.com/gh_mirrors/qd/qdjango&…...
滑动窗口最大值(力扣239)
刚拿到这道题,我们第一反应就是遍历每一个滑动窗口,然后在滑动窗口中遍历找到该窗口的最大值,但是这样的时间复杂度为O(k*n).有没有更简单的方法呢?答案是使用队列。更准确的说是双向队列。下面我将详细讲解一下如何使用双向队列解决这道问题…...
HTTP / 2
序言 在之前的文章中我们介绍过了 HTTP/1.1 协议,现在再来认识一下迭代版本 2。了解比起 1.1 版本,后面的版本改进在哪里,特点在哪里?话不多说,开始吧⭐️! 一、 HTTP / 1.1 存在的问题 很多时候新的版本的…...
Python 脚本-显示给定文件的文件信息
目录 Python 代码实现 Python 代码解释 1.导入必要的模块: 2.函数 get_file_info: 3.函数 print_file_info: 4.主函数 main: 5.程序入口: 使用方法 Python 代码实现 import os import stat import sys import…...
C# 通用缓存类开发:开启高效编程之门
引言 嘿,各位 C# 开发者们!在当今快节奏的软件开发领域,提升应用程序的性能就如同给跑车装上涡轮增压,能让你的项目在激烈的竞争中脱颖而出。而构建一个高效的 C# 通用缓存类,无疑是实现这一目标的强大武器。 想象一…...
Mac安装Homebrew
目录 安装修改homeBrew源常用命令安装卸载软件升级软件相关清理相关 安装 官网 https://brew.sh/不推荐官网安装方式(很慢很慢或者安装失败联网失败) 检测是否安装homebrewbrew -v执行安装命令 苹果电脑 常规安装脚本 (推荐 完全体 几分钟就…...
MySQL面试题2025 每日20道【其四】
1、你们生产环境的 MySQL 中使用了什么事务隔离级别?为什么? 中等 在生产环境中,MySQL数据库的事务隔离级别通常由开发团队或数据库管理员根据应用的需求来设定。MySQL支持四种标准的事务隔离级别: 读未提交(Read Unc…...
maven 微服务项目多 包版本问题
mvn dependency:tree查看jar包直接的关系 找到重复的包!!!! 可以查看包版本问题 [INFO] | - org.jpedal:OpenViewerFX:jar:6.6.14:compile [INFO] | | - org.eclipse.birt.runtime.3_7_1:org.mozilla.javascript:jar:1.7.2:compile [INFO] | | - bouncycastle:bcprov-j…...
skipcrossnets模型详解及代码复现
模型背景 在SkipcrossNets模型提出之前,多模态融合在自动驾驶领域已取得显著进展。然而,传统的两流网络仅在特定层进行融合,需要大量人工尝试来设置,并且随着网络深度增加,模态特征差异增大,容易影响性能。 为解决这些问题,研究人员开发了SkipcrossNets模型,旨在提供…...
【0397】Postgres内核 checkpoint process ⑦ 获取 delaying checkpoint VXIDs(delayChkpt)
1. Top-level transactions 顶级事务(Top-level transactions)通过由 PGPROC 字段 backendId 和 lxid 组成的 VirtualTransactionIDs 来标识。对于已准备的事务,LocalTransactionId 是一个普通的 XID。这些在短期内保证唯一,但在数据库重启或 XID 滚转后会被重新使用;因此…...
Go语言-学习一
简介:Go语言经过多年的快速发展,已经被光广泛应用到各个领域,成为当前最热的计算机语言之一。Go自带许多高级特性及常用工具,使Go一出世就以高并发和高性能为程序员所追捧。 go语言特点 1、静态类型、编译型、开源 2、脚本化&…...
学习记录-统计记录场景下的Redis写请求合并优化实践
学习记录-使用Redis合并写请求来优化性能 1.业务背景 学习进度的统计功能:为了更精确的记录用户上一次播放的进度,采用的方案是:前端每隔15秒就发起一次请求,将播放记录写入数据库。但问题是,提交播放记录的业务太复杂了&#x…...
Java中json的一点理解
一、Java中json字符串与json对象 1、json本质 json是一种数据交换格式。 常说的json格式的字符串 > 发送和接收时都只是一个字符串,它遵循json这种格式。 2、前后端交互传输的json是什么? 前后端交互传输的json都是json字符串 比如:…...
用于牙科的多任务视频增强
Multi-task Video Enhancement for Dental Interventions 2022 miccai Abstract 微型照相机牢牢地固定在牙科手机上,这样牙医就可以持续地监测保守牙科手术的进展情况。但视频辅助牙科干预中的视频增强减轻了低光、噪音、模糊和相机握手等降低视觉舒适度的问题。…...
二、vue智能Ai对话(高仿通义千问)流式进阶版
1、安装依赖、启动 // 安装依赖 npm install // 启动服务 node server.js 2、浏览器运行html 3、流式进阶版视频地址:流式进阶版视频 4、各位如有需要,请下载源码包。...
Python新春烟花
目录 系列文章 写在前面 技术需求 完整代码 下载代码 代码分析 1. 程序初始化与显示设置 2. 烟花类 (Firework) 3. 粒子类 (Particle) 4. 痕迹类 (Trail) 5. 烟花更新与显示 6. 主函数 (fire) 7. 游戏循环 8. 总结 注意事项 写在后面 系列文章 序号直达链接爱…...
《自动驾驶与机器人中的SLAM技术》ch4:基于预积分和图优化的 GINS
前言:预积分图优化的结构 1 预积分的图优化顶点 这里使用 《自动驾驶与机器人中的SLAM技术》ch4:预积分学 中提到的散装的形式来实现预积分的顶点部分,所以每个状态被分为位姿()、速度、陀螺零偏、加计零偏四种顶点&am…...
「2024·我的成长之路」:年终反思与展望
文章目录 1. 前言2.创作历程2.1 摆烂期2.2 转变期3. 上升期 2. 个人收获3.经验分享4. 展望未来 1. 前言 2025年1月16日,2024年博客之星入围公布,很荣幸获得了这次入围的机会。2024年对我个人是里程碑的一年,是意义非凡的一年,是充…...
P8738 [蓝桥杯 2020 国 C] 天干地支
两种方法 #include<bits/stdc.h> using namespace std;int main(){int year;cin>>year;string tg[10] {"geng", "xin", "ren", "gui","jia", "yi", "bing", "ding", "wu&…...
Linux网络 TCP socket
TCP简介 TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。它位于OSI模型的第四层,主要为应用层提供数据传输服务。TCP通过三次握手建立连接,确保数据在发送和接收过程中的准确性和顺序…...
IntelliJ IDEA 2023.3 中配置 Spring Boot 项目的热加载
IntelliJ IDEA 2023.3 中配置 Spring Boot 项目的热加载 在 IntelliJ IDEA 2023.3 中配置 Spring Boot 项目的热加载,可以让你在不重启应用的情况下看到代码修改的效果。以下是详细的配置步骤: 添加 spring-boot-devtools 依赖 在 pom.xml 文件中添加 …...
【网络协议】【http】【https】RSA+AES-TLS1.2
【网络协议】【http】【https】RSAAES-TLS1.2 https并不是一个协议 而是在传输层之间添加了SSL/TLS协议 TLS 协议用于应用层协议(如 HTTP)和传输层(如 TCP)之间,增加了一层安全性来解决 HTTP 存在的问题,H…...
Java中如何安全地停止线程?
大家好,我是锋哥。今天分享关于【Java中如何安全地停止线程?】面试题。希望对大家有帮助; Java中如何安全地停止线程? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在Java中,安全地停止线程是一项重要的任务,尤其…...
01.04、回文排序
01.04、[简单] 回文排序 1、题目描述 给定一个字符串,编写一个函数判定其是否为某个回文串的排列之一。回文串是指正反两个方向都一样的单词或短语。排列是指字母的重新排列。回文串不一定是字典当中的单词。 2、解题思路 回文串的特点: 一个回文串在…...
[深度学习]多层神经网络
多层神经网络 文章目录 多层神经网络单个神经元人类大脑神经与神经元神经元与矩阵神经元的串联激活函数激活函数位置神经网络的三种表现形式神经网络的参数(可训练的) 深度学习的训练过程全连接网络过拟合和欠拟合 单个神经元 一个神经元实际表示的数据公…...
JavaScript语言的正则表达式
JavaScript语言的正则表达式详解 正则表达式(Regular Expression,简称Regex或RegExp)是一种强大的文本处理工具,可以在字符串中执行模式匹配和替换操作。在JavaScript中,正则表达式是处理字符串时不可或缺的部分&…...
yolo系列模型为什么坚持使用CNN网络?
在深度学习领域,目标检测是一项至关重要的任务,而YOLO(You Only Look Once)系列模型无疑是这一领域的佼佼者。YOLO以其高效、准确的特点,在实时目标检测任务中占据了重要地位。然而,随着Transformer模型在自…...