前言:
这是一个较简单的项目,主要实现⼀个网页版的五子棋对战游戏,基于WebSocket的网页端和服务端保持长连接的消息推送机制。而且程序的用户数据依赖cookie,有比较大的局限性,毕竟一个浏览器只有一个cookie;主要功能有,用户管理, 匹配对战,聊天功能。三大板块;
开发与搭建环境
我的环境是Linux(Ubuntu-22.04),VSCode,g++/gdb和Makefile等。
要用的库有
- boost库
- jsoncpp库
- mysql数据库
若没有,需要在自己的环境安装哦;安装教程在博客上有很多的,搜一搜就有了;
要注意的是在安装完mysql数据库后,尽量进行安全性配置
这个都y同意进行,还可以对数据库改密码;
然后也可以进⾏内部密码强度等级的进⼀步设置与查看
可以进入mysql 进行设置,下图分别是 设置全局密码安全等级,最小密码的字符长度
注意修改一下字符集,改一下对应文件
如果客户端、连接层、服务器层使用的字符集不一致,你存储的中文可能会变成一堆问号'???'或其他乱码
在/etc/mysql/my.cnf中添加下面代码
然后在数据库中查看,如图则正常
知识点代码用例
Websocketpp
认识Websocket
WebSocket协议本质上是一个基于TCP的协议。
首先客户浏览器会先向服务器发一个升级请,求这个请求和通常的HTTP请求不同,包含了⼀些附加头信息,通过这个附加头信息完成握手过程并升级协议的过程

具体协议升级的过程如下:

报文格式:

这里简单说一下我理解的
- FIN: WebSocket传输数据以消息为概念单位,一个消息有可能由一个或多个帧组成,FIN字段为1表示未尾帧。
- RSV1~3:保留字段,只在扩展时使用,若未启用扩展则应置1,若收到不全为0的数据帧,且未协商扩展则立即终止连接。
- opcode : 标志当前数据帧的类型
- 0x0: 表示这是个延续帧,当 opcode 为 0 表示本次数据传输采用了数据分片,当前收到的帧为其中一个分片
- 0x1: 表示这是文本帧
- 0x2: 表示这是二进制帧
- 0x3-0x7: 保留,暂未使用
- 0x8: 表示连接断开
- 0x9: 表示 ping 帧
- 0xa: 表示 pong 帧
- 0xb-0xf: 保留,暂未使用
- mask: 表示Payload数据是否被编码,若为1则必有Mask-Key,用于解码Payload数据。仅客户端发送给服务端的消息需要设置。
- Payload length :数据载荷的长度,单位是字节,有可能为7位、7+16位、7+64位。假设Payload length = x
- x为0~126:数据的长度为x字节
- x为126:后续2个字节代表一个16位的无符号整数,该无符号整数的值为数据的长度
- x为127:后续8个字节代表一个64位的无符号整数(最高位为0),该无符号整数的值为数据的长度
- Mask-Key:当mask为1时存在,长度为4字节,解码规则:DECODED[i] = ENCODED[i] XOR MASK[i % 4]
- Payload data:报文携带的载荷数据
为什么要用websocket,不用传统的web;
传统的web,都是一问一答的形式,即客户端给服务器发送了一个HTTP请求,服务器给客户端返回⼀个HTTP响应;这样,服务器一直都是被动的一方,客户端不主动发起请求,服务器一直不会响应;
像网页,比如我们这种五子棋很依赖消息推送,即需要服务器主动推动消息到客户端。如果只是使用原生的HTTP协议;(要想实现消息推送一般需要通过"轮询"的方式实现,而轮询的成本较高并且也不能及时的获取到消息的响应。)
Websocketpp
- WebSocketpp是⼀个跨平台的开源头部C++库,它实现了RFC6455(WebSocket 协议)和RFC7692(WebSocketCompression Extensions)。它允许将WebSocket客户端和服务器功 能集成到C++程序中。
- 在常见的配置中,全功能网络I/O由Asio网络库提供(#include<websocketpp/config/asio_no_tls.hpp>)
在此项目中,使用到的常用接口介绍
Websocketpp类
cpp
namespace websocketpp {
typedef lib::weak_ptr<void> connection_hdl;
template <typename config>
class endpoint : public config::socket_type {
typedef lib::shared_ptr<lib::asio::steady_timer> timer_ptr;
typedef typename connection_type::ptr connection_ptr;
typedef typename connection_type::message_ptr message_ptr;
typedef lib::function<void(connection_hdl)> open_handler;
typedef lib::function<void(connection_hdl)> close_handler;
typedef lib::function<void(connection_hdl)> http_handler;
typedef lib::function<void(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);
};
server服务器 与 connection连接类
cpp
template <typename config>
class server : public endpoint<connection<config>,config> {
/*初始化并启动服务端监听连接的accept事件处理*/
void start_accept();
};
template <typename config>
class connection
: public config::transport_type::transport_con_type
, public config::connection_base
{
/*发送数据接口*/
error_code send(std::string&payload, frame::opcode::value
op=frame::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();
};
用于获取请求的http,message_buffer类
cpp
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帧的操作码
frame::opcode::value get_opcode()
//获取websocket帧的载荷数据
std::string const & get_payload()
}
}
一些其他端口
其实也用不到,了解一下就好,200,201,404等一些常用的稍微记一下得了
cpp
namespace log {
struct alevel {
static level const none = 0x0;
static level const connect = 0x1;
static level const disconnect = 0x2;
static level const control = 0x4;
static level const frame_header = 0x8;
static level const frame_payload = 0x10;
static level const message_header = 0x20;
static level const message_payload = 0x40;
static level const endpoint = 0x80;
static level const debug_handshake = 0x100;
static level const debug_close = 0x200;
static level const developer = level;
static level const all = level;
static level const fatal = 0x1000;
static level const error = 0x2000;
static level const warn = 0x4000;
static level const info = 0x8000;
static level const debug = 0x10000;
static level const access_core = 0x20000;
static level const all = 0xffffffff;
}
}
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,
requested_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
});
namespace frame {
namespace opcode {
enum value {
continuation = 0x0,
text = 0x1,
binary = 0x2,
rsv3 = 0x3,
rsv4 = 0x4,
rsv5 = 0x5,
rsv6 = 0x6,
rsv7 = 0x7,
close = 0x8,
ping = 0x9,
pong = 0xA,
control_rsv1 = 0xB,
control_rsv2 = 0xC,
control_rsv3 = 0xD,
control_rsv4 = 0xE,
control_rsv5 = 0xF,
}
}
}
实践:http/websocket服务器,使用Websocketpp实现一个简单的http和websocket服务器
这些demo 都在我的git里,若想看一看,点击
https://gitee.com/yaokong123/gobang/tree/master/web-gobang/example
JsonCpp使用
Json介绍
Json 是一种数据交换格式,它采用完全独立于编程语言的文本格式来存储和表示数据。
简单来说,就是用json的文本格式,表示代码
cpp
C代码
char *name = "xx";
int age = 18;
float score[3] = {88.5, 99, 58};
转化为JSon串
{
"姓名" : "xx",
"年龄" : 18,
"成绩" : [88.5, 99, 58]
}
[
{"姓名":"⼩明", "年龄":18, "成绩":[23, 65, 78]},
{"姓名":"⼩红", "年龄":19, "成绩":[88, 95, 78]}
]
简单认识
Json 的数据类型包括对象,数组,字符串,数字等
- 对象:使用花括号 {} 括起来的表示一个对象
- 数组:使⽤中括号 [] 括起来的表示一个数组
- 字符串:使⽤常规双引号 "" 括起来的表示一个字符串
- 数字:包括整形和浮点型,直接使用
JsonCpp介绍
Jsoncpp 库主要是用于实现 Json 格式数据的序列化和反序列化,
仔细点说就是实现了将多个数据对象转化成 json 格式字符串,以及将 Json 格式字符串解析得到多个数据对象的功能。
Json 数据对象类的表示
cpp
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 库
Jsoncpp 库主要借助三个类以及其对应的少量成员函数完成序列化及反序列化
序列化接口
cppclass JSON_API StreamWriter { virtual int write(Value const& root, std::ostream* sout) = 0; } class JSON_API StreamWriterBuilder : public StreamWriter::Factory { virtual StreamWriter* newStreamWriter() const; }反序列化接口
cppclass 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; }
JsonCpp功能代码用例编写
这些demo 都在我的git里,若想看一看,点击
https://gitee.com/yaokong123/gobang/tree/master/web-gobang/example
MySQL API
MySQL 是 C/S 模式, C API 其实就是⼀个 MySQL 客户端,提供⼀种⽤ C 语⾔代码操作数据 库的流程;
我们了解的主要目的就是,封装一个MySQL工具类,以便我们在项目中直接调用MySQL;
MySQL 的 C API 接口
这里粗略的介绍⼀下 MySQL 的 C API 接口
cpp
// mysql为空调用请求时需要调用访问的地址
// 返回值:成功返回访问的地址,失败返回NULL
MYSQL *mysql_init(MYSQL *mysql);
// 连接mysql服务器
// 参数说明:
// mysql--初始化完成访问的地址
// host--连接的mysql服务器
// user--连接的服务器用户名
// passwd--连接的服务器密码
// db--默认选择的数据库名称
// port--连接的服务器端口:默认创建3306端口
// unix_socket--通过查询文件更新socket文件,通常需NULL
// client_flag--客户端在路由器上设置的
// 返回值:成功返回访问的地址,失败返回NULL
MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user,
const char *passwd, const char *db, unsigned int port,
const char *unix_socket, unsigned long client_flag);
// 设置当前客户端的字符集
// 参数说明:
// mysql--初始化完成的访问
// csname--字符串名,通常:"utf8"
// 返回值:成功返回0,失败返回非0
int mysql_set_character_set(MYSQL *mysql, const char *csname);
// 选择操作的数据库
// 参数说明:
// mysql--初始化完成的访问
// db--要切换到新的数据库
// 返回值:成功返回0,失败返回非0
int mysql_select_db(MYSQL *mysql, const char *db);
// 执行sql语句
// 参数说明:
// mysql--初始化完成的访问
// stmt_str--要执行的SQL语句
// 返回值:成功返回0,失败返回非0
int mysql_query(MYSQL *mysql, const char *stmt_str);
// 保存查询结果到本地
// 参数说明:
// mysql--初始化完成的访问
// 返回值:成功返回结果集的地址,失败返回NULL
MYSQL_RES *mysql_store_result(MYSQL *mysql);
// 获取结果集的行数
// 参数说明:
// result--保存到本地的结果集地址
// 返回值:结果集的行数
uint64_t mysql_num_rows(MYSQL_RES *result);
// 获取结果集的列数
// 参数说明:
// result--保存到本地的结果集地址
// 返回值:结果集的列数
unsigned int mysql_num_fields(MYSQL_RES *result);
// 返回结果集的下一行
// 参数说明:
// result--保存到本地的结果集地址
// 返回值:结果集的下一行
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);
// 释放结果集的内存
// 参数说明:
// result--保存到本地的结果集地址
void mysql_free_result(MYSQL_RES *result);
// 关闭数据库客户端连接,删除所有
// 参数说明:
// mysql--初始化完成的访问
void mysql_close(MYSQL *mysql);
// 获取mysql接口执行错误信息
// 参数说明:
// mysql--初始化完成的访问
const char *mysql_error(MYSQL *mysql);
MySQL API使用
封装MySQL工具类
这些demo 都在我的git里,若想看一看,点击
https://gitee.com/yaokong123/gobang/tree/master/web-gobang/example




