文章目录

前言
服务器模块,是对当前所实现的所有模块的一个整合,并进行服务器搭建的一个模块,最终封装实现出一个 gobang_server
的服务器模块类,向外提供搭建五子棋对战服务器的接口。通过实例化的对象可以简便的完成服务器的搭建。服务器的整合设计主要是分为下面两个过程:
- 网络通信接口的设计
- 收到一个什么格式的数据,是代表什么样的请求,要将该请求给什么样的业务处理以及响应
- 搭建服务器
- 搭建
websocket
服务器,实现网络通信 - 针对各自不同的请求进行不同的业务处理
- 搭建
那么可能客户端会发什么样的请求呢,如下图所示:
Ⅰ. 采用 RESTful 风格的数据格式规定
整个项目的通信接口的设计,是采用 RESTful
风格的,如下图所示:
下面我们先来了解整套通信时候的数据格式规定和响应数据格式的规定,因为我们的接口设计也是要按照这些规定来的!
1、静态资源请求
静态资源页面,在后台服务器上就是个 html/css/js 文件,而静态资源请求的处理,其实就是将文件中的内容发送给客户端。
比如我们的项目中,静态资源请求其实只有四种:
注册页面请求
请求格式:GET /register.html HTTP/1.1 响应格式: HTTP/1.1 200 OK Content-Length: xxx Content-Type: text/html register.html⽂件的内容数据
登录页面请求
GET /login.html HTTP/1.1
大厅页面请求
GET /game_hall.html HTTP/1.1
房间页面请求
GET /game_room.html HTTP/1.1
2、注册用户
POST /reg HTTP/1.1
Content-Type: application/json
Content-Length: 32
{"username":"xiaobai", "password":"123123"}
#成功时的响应
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": "用户名已被占用"}
3、用户登录
登录的请求类型也是 POST
,因为我们要提交数据!
POST /login HTTP/1.1
Content-Type: application/json
Content-Length: 32
{"username":"xiaobai", "password":"123123"}
#成功时的响应
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": "用户名或密码错误"}
4、获取客户端信息
GET /info HTTP/1.1
Content-Type: application/json
Content-Length: 0
#成功时的响应
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 58
{"id":1, "username":"xiaobai", "score":1000, "total_count":4, "win_count":2}
#-------------------------------------------------
#失败时的响应
HTTP/1.1 401 Unauthorized
Content-Type: application/json
Content-Length: 43
{"result":false, "reason": "用户还未登录"}
5、websocket长连接协议切换请求 – 进入游戏大厅
当服务器收到了切换为 websocket
协议的时候会帮我们自动切换!
/* ws://localhost:9000/hall */
GET /hall HTTP/1.1
Connection: Upgrade
Upgrade: WebSocket
......
HTTP/1.1 101 Switching
......
WebSocket
握手成功后的回复:表示游戏大厅已经进入成功,此时响应:
{
"optype": "hall_ready",
"uid": 1
}
6、开始对战匹配
这种情况比较特殊,我们要在加入匹配队列和匹配成功的时候都给客户端发送响应!
{
"optype": "match_start"
}
/* 后台正确处理后回复 */
{
"optype": "match_start", // 表示成功加入匹配队列
"result": true
}
#-------------------------------------------------
/* 后台处理出错回复 */
{
"optype": "match_start"
"result": false,
"reason": "具体原因...."
}
/* 匹配成功了给客户端的回复 */
{
"optype": "match_success", // 表示匹配成功
"result": true
}
7、取消匹配
{
"optype": "match_stop"
}
/*后台正确处理后回复*/
{
"optype": "match_stop"
"result": true
}
#-------------------------------------------------
/*后台处理出错回复*/
{
"optype": "match_stop"
"result": false,
"reason": "具体原因...."
}
8、websocket长连接协议切换请求 – 进入游戏房间
注意进入大厅和房间的 websocket 连接是不同的,所以要再次进行切换!
/* ws://localhost:9000/room */
GET /room HTTP/1.1
Connection: Upgrade
Upgrade: WebSocket
......
HTTP/1.1 101 Switching
......
WebSocket
握手成功后的回复:表示游戏房间已经进入成功,此时响应:
/* 协议切换成功,房间已经建立 */
{
"optype": "room_ready",
"room_id": 222, // 房间ID
"self_id": 1, // 自身ID
"white_id": 1, // 白棋ID
"black_id": 2, // 黑棋ID
}
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": 222,
"uid": 1,
"row": 3,
"col": 2,
"winner": 0 // 0表示未分胜负,非0表示已分胜负 (uid是谁,谁就赢了)
}
10、聊天
{
"optype": "chat",
"room_id": 222,
"uid": 1,
"message": "赶紧点"
}
{
"optype": "chat",
"result": false
"reason": "聊天失败具体原因....比如有敏感词..."
}
#-------------------------------------------------
{
"optype": "chat",
"result": true,
"room_id": 222,
"uid": 1,
"message": "赶紧点"
}
Ⅱ. 搭建websocket服务器框架
因为我们之前的模块都是为服务器做准备的,所以直接将这些模块在服务器类中进行实例化和初始化即可!要注意的是初始化的顺序是有讲究的,因为有些类是依赖其它类的,那些被依赖的类应该先初始化,最好还要将声明位置也改一下,保持和初始化顺序一致!
而服务器的搭建我们之前也有学过,就是调用几个接口,然后通过再通过 start()
函数进行服务器的启动!
#ifndef __MY_SERVER_H__
#define __MY_SERVER_H__
#include "db.hpp"
#include "online.hpp"
#include "util.hpp"
#include "session.hpp"
#include "matcher.hpp"
#include "room.hpp"
#define WEBROOT "./wwwroot/"
class gobang_server
{
private:
std::string _webroot; // 静态资源目录,默认为./wwwroot/
wsserver_t _server; // websocket服务器实例
user_table _user_table; // 数据库用户信息实例
online_manager _online_user; // 在线用户管理实例
session_manager _session; // 会话管理实例
room_manager _room; // 房间管理实例
match_manager _matcher; // 匹配管理实例
private:
void http_callback(websocketpp::connection_hdl hdl)
{}
void open_callback(websocketpp::connection_hdl hdl)
{}
void close_callback(websocketpp::connection_hdl hdl)
{}
void message_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msgptr)
{}
public:
// 构造函数,完成成员变量的初始化以及服务器的设置
gobang_server(const std::string& host,
const std::string& user,
const std::string& passwd,
const std::string& dbname,
uint16_t port = 3306,
const std::string& webroot = WEBROOT)
: _webroot(webroot)
, _user_table(host, user, passwd, dbname, port)
, _session(&_server)
, _room(&_user_table, &_online_user)
, _matcher(&_online_user, &_user_table, &_room)
{
// 设置日志等级
_server.set_access_channels(websocketpp::log::alevel::none);
// 初始化asio调度器和地址重用
_server.init_asio();
_server.set_reuse_addr(true);
// 设置回调函数
_server.set_http_handler(std::bind(&gobang_server::http_callback, this, std::placeholders::_1));
_server.set_open_handler(std::bind(&gobang_server::open_callback, this, std::placeholders::_1));
_server.set_close_handler(std::bind(&gobang_server::close_callback, this, std::placeholders::_1));
_server.set_message_handler(std::bind(&gobang_server::message_callback, this, std::placeholders::_1, std::placeholders::_2));
}
// 启动服务器接口
void start(uint16_t port)
{
_server.listen(port); // 设置监听窗口
_server.start_accept(); // 开始获取新连接
_server.run(); // 启动服务器
}
};
#endif
下面为了测试服务器是否搭建成功,我们在 http_callback()
函数里面,对浏览器发送的 http
请求,返回对应的静态资源,代码如下:
void http_callback(websocketpp::connection_hdl hdl)
{
wsserver_t::connection_ptr conn = _server.get_con_from_hdl(hdl);
websocketpp::http::parser::request req = conn->get_request();
std::string pathname = _webroot + req.get_uri();
std::string body;
file_util::read(pathname, body);
conn->set_status(websocketpp::http::status_code::ok);
conn->set_body(body);
}
编译之后执行可执行文件,然后通过浏览器输入对应的服务器 IP
和端口号和资源路径就能请求到对应的 html
等文件!注意这只是测试代码,后面这段代码是不存在的!
下面我们针对不同的请求进行不同的业务处理,划分为 http请求 和 websocket长连接请求 两部分进行设计和实现!