瀏覽代碼

1.创建五子项目目录
2.上传代码:服务器连接、对话、双人对战等等功能

yaozheng0922 4 月之前
父節點
當前提交
8a8f70888b

二進制
Online backgammon battles/example/bind


+ 17 - 0
Online backgammon battles/example/bind.cc

@@ -0,0 +1,17 @@
+#include <iostream>
+#include <string>
+#include <functional>
+
+void print(const std::string& s, int num)
+{
+    std::cout << s << " " << num << std::endl;
+}
+
+int main()
+{
+    print("nihao", 10);
+    auto func = std::bind(print, "nihao", std::placeholders::_1);
+    func(20);
+
+    return 0;
+}

二進制
Online backgammon battles/example/json_serialize


+ 71 - 0
Online backgammon battles/example/json_serialize.cc

@@ -0,0 +1,71 @@
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <jsoncpp/json/json.h>
+using std::cout;
+using std::endl;
+
+/*使用json完成数据的序列化工作*/
+std::string serialize()
+{
+    // 1. 将需要序列化的数据存储在Json::Value对象中
+    Json::Value root;
+    root["姓名"] = "小明";
+    root["年龄"] = 18;
+    root["成绩"].append(80);  //成绩是数组类型
+    root["成绩"].append(90);
+    root["成绩"].append(100);
+    // 2. 实例化StreamWriterBuilder工厂类对象
+    Json::StreamWriterBuilder swb;
+    // 3. 使用StreamWriterBuilder工厂类对象实例化StreamWriter对象
+    Json::StreamWriter *sw = swb.newStreamWriter();
+    // 4. 使用StreamWriter对象完成Json::Value中数据的序列化工作,并将序列化结果存放到ss中
+    std::stringstream ss;
+    int n = sw->write(root, &ss);
+    if(n != 0)
+    {
+        cout << "json serialize fail" << endl;
+        delete sw;
+        return "";  
+    }
+    
+    delete sw;
+    return ss.str();
+}
+
+/*使用Json完成序列化数据的反序列化工作*/
+void deserialize(const std::string &str)
+{
+    // 1. 实例化一个CharReaderBuilder工厂类对象
+    Json::CharReaderBuilder crb;
+    // 2. 使用CharReaderBuilder对象实例化一个CharReader对象
+    Json::CharReader *cr = crb.newCharReader();
+    // 3. 创建一个Json::Value对象,用于保存json格式字符串反序列化后的结果
+    Json::Value root;
+    // 4. 使用CharReader对象完成json格式字符串的反序列化工作
+    std::string errmsg;
+    bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), &root, &errmsg);
+    if(ret == false)
+    {
+        cout << "json deserialize fail: " << errmsg << endl;
+        delete cr;
+        return;
+    }
+    // 5. 依次打印Json::Value中的数据
+    cout << "姓名: " << root["姓名"].asString() << endl;
+    cout << "年龄: " << root["年龄"].asInt() << endl; 
+    int size = root["成绩"].size();
+    for(int i = 0; i < size; i++)
+    {
+        cout << "成绩: " << root["成绩"][i].asFloat() << endl;
+    }
+}
+
+int main()
+{
+    std::string str = serialize();
+    cout << str << endl;
+    deserialize(str);
+    
+    return 0;
+}

+ 40 - 0
Online backgammon battles/example/jsoncpp.hpp

@@ -0,0 +1,40 @@
+#include <jsoncpp/json/json.h>
+
+/*json数据对象类*/
+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();
+};
+/*序列化接口*/
+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;
+};

+ 14 - 0
Online backgammon battles/example/makefile

@@ -0,0 +1,14 @@
+all:wsserver json_serialize mysql_connect
+
+wsserver:wsserver.cc
+	g++ -o $@ $^ -std=c++11 -lpthread -lboost_system
+
+json_serialize:json_serialize.cc
+	g++ -o $@ $^ -std=c++11 -ljsoncpp
+
+mysql_connect:mysql_connect.cc
+	g++ -o $@ $^ -std=c++11 -lmysqlclient -L/usr/lib64/mysql/
+
+.PHONY:clean
+clean:
+	rm -f wsserver json_serialize mysql_connect

+ 89 - 0
Online backgammon battles/example/mysql_c_api.hpp

@@ -0,0 +1,89 @@
+#include <mysql/mysql.h>
+
+// Mysql操作句柄初始化
+// 参数说明:
+// mysql为空则动态申请句柄空间进⾏初始化
+// 返回值: 成功返回句柄指针, 失败返回NULL
+MYSQL *mysql_init(MYSQL *mysql);
+
+// 连接mysql服务器
+// 参数说明:
+// mysql--初始化完成的句柄
+// host---连接的mysql服务器的地址
+// user---连接的服务器的⽤⼾名
+// passwd-连接的服务器的密码
+// db ----默认选择的数据库名称
+// port---连接的服务器的端⼝: 默认0是3306端⼝
+// unix_socket---通信管道⽂件或者socket⽂件,通常置NULL
+// client_flag---客⼾端标志位,通常置0
+// 返回值:成功返回句柄指针,失败返回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_FIELD *mysql_fetch_field(MYSQL_RES *result)
+MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *result)
+
+// 遍历结果集, 并且这个接⼝会保存当前读取结果位置,每次获取的都是下⼀条数据
+// 参数说明:
+// result--保存到本地的结果集地址
+// 返回值:实际上是⼀个char **的指针,将每⼀条数据做成了字符串指针数组
+// row[0]-第0列 row[1]-第1列 ...
+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);

二進制
Online backgammon battles/example/mysql_connect


+ 109 - 0
Online backgammon battles/example/mysql_connect.cc

@@ -0,0 +1,109 @@
+#include <iostream>
+#include <string>
+#include <mysql/mysql.h>
+using std::cout;
+using std::endl;
+
+int main()
+{
+    // 1. 初始化mysql句柄
+    // MYSQL *mysql_init(MYSQL *mysql)
+    MYSQL *mysql = mysql_init(nullptr);
+    if(mysql == nullptr)
+    {
+        cout << "mysql init failed" << endl;
+        return -1;
+    }
+
+    // 2. 连接mysql数据库
+    // MYSQL *mysql_real_connect(...)
+    const std::string host = "127.0.0.1";
+    const std::string user = "thj";
+    const std::string password = "Abcd1234@";
+    const std::string db = "gobang";
+    unsigned int port = 4106;
+    mysql = mysql_real_connect(mysql, host.c_str(), user.c_str(), password.c_str(), db.c_str(), port, nullptr, 0);
+    if(mysql == nullptr)
+    {
+        cout << "connection mysql server failed" << endl;
+        mysql_close(mysql);
+        return -1;
+    }
+
+    // 3. 设置客户端字符集
+    // int mysql_set_character_set(MYSQL *mysql, const char *csname)
+    if (mysql_set_character_set(mysql, "utf8") != 0)
+    {
+        cout << "set client character failed: " << mysql_error(mysql) << endl;
+    }
+
+    // 4. 选择要操作的数据库
+    // int mysql_select_db(MYSQL *mysql, const char *db)
+    // mysql_select_db(mysql, db.c_str());
+
+    // 5. 执行sql语句 -- 增删改
+    // int mysql_query(MYSQL *mysql, const char *stmt_str)
+    if(mysql_query(mysql, "insert into user values(null, '张三', 18, '11122223333')") != 0)
+    {
+        cout << "sql query failed: " << mysql_error(mysql) << endl;
+    }
+
+    // 6. 执行sql语句 -- 查询
+    if (mysql_query(mysql, "select * from user") != 0)
+    { 
+        cout << "sql query failed: " << mysql_error(mysql) << endl;
+    }
+    // 6.1 将查询结果转储到本地
+    // MYSQL_RES *mysql_store_result(MYSQL *mysql)
+    MYSQL_RES *res = mysql_store_result(mysql);
+    if(res == nullptr)
+    {
+        cout << "store query result failed: " << mysql_error(mysql) << endl;
+        mysql_close(mysql);
+        return -1;
+    }
+    // 6.2 获取结果集的行数与列数
+    // uint64_t mysql_num_rows(MYSQL_RES *result)
+    // unsigned int mysql_num_fields(MYSQL_RES *result)
+    int rowCount = mysql_num_rows(res);
+    int colCount = mysql_num_fields(res);
+    // 6.3 打印结果集中的列属性信息
+    // MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *result) 一次获取一列
+    // MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *result) 一次获取全部
+    for(int i = 0; i < colCount; i++)
+    {
+        // 自动迭代
+        MYSQL_FIELD *field = mysql_fetch_field(res);
+        cout << field->name << '\t';
+    }
+    cout << endl;
+    // 一次获取全部列字段的属性信息,然后分别打印
+    // MYSQL_FIELD *total_fields = mysql_fetch_fields(res);
+    // for(int i = 0; i < colCount; i++)
+    // {
+    //     cout << total_fields[i].name << '\t';
+    // }
+    // cout << endl;
+
+    // 6.4 打印结果集中的行内容
+    // MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)
+    for(int i = 0; i < rowCount; i++)
+    {
+        // 一行的所有内容 -- 自动迭代
+        MYSQL_ROW row = mysql_fetch_row(res);
+        for(int j = 0; j < colCount; j++)
+        {
+            // 一行内容中的某一列的内容
+            cout << row[j] << '\t';
+        }
+        cout << endl;
+    }
+
+   // 7. 释放结果集
+   mysql_free_result(res);
+
+   // 8. 关闭mysql连接
+   mysql_close(mysql);
+
+    return 0;
+}

+ 205 - 0
Online backgammon battles/example/websocketpp.hpp

@@ -0,0 +1,205 @@
+#include <websocketpp/server.hpp>
+#include <websocketpp/config/asio_no_tls.hpp>
+
+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);
+    };
+    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();
+    };
+    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 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 devel = 0x400;
+            static level const app = 0x800;
+            static level const http = 0x1000;
+            static level const fail = 0x2000;
+            static level const access_core = 0x00003003;
+            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,
+                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
+            };
+        }
+    }
+    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_rsvb = 0xB,
+                control_rsvc = 0xC,
+                control_rsvd = 0xD,
+                control_rsve = 0xE,
+                control_rsvf = 0xF,
+            };
+        }
+    }
+}

二進制
Online backgammon battles/example/wsserver


+ 57 - 0
Online backgammon battles/example/wsserver.cc

@@ -0,0 +1,57 @@
+#include <iostream>
+#include <string>
+#include <functional>
+#include <websocketpp/server.hpp>
+#include <websocketpp/config/asio_no_tls.hpp>
+using std::cout;
+using std::endl;
+
+typedef websocketpp::server<websocketpp::config::asio> wsserver_t;
+
+void http_callback(wsserver_t *srv, websocketpp::connection_hdl hdl) {
+    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;
+
+    // 响应一个hello world页面
+    std::string body = "<html><body><h1>Hello World</h1></body></html>";
+    conn->set_body(body);
+    conn->append_header("Content-Type", "text/html");
+    conn->set_status(websocketpp::http::status_code::ok);
+}
+void wsopen_callback(wsserver_t *srv, websocketpp::connection_hdl hdl) {
+    cout << "websocket握手成功" << std::endl;
+}
+void wsclose_callback(wsserver_t *srv, websocketpp::connection_hdl hdl) {
+    cout << "websocket连接关闭" << endl;
+}
+void wsmessage_callback(wsserver_t *srv, websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg) {
+    wsserver_t::connection_ptr conn = srv->get_con_from_hdl(hdl);
+    cout << "wsmsg: " << msg->get_payload() << endl;
+    std::string rsp = "[client]# " + 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();
+    // 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(wsmessage_callback, &wssrv, std::placeholders::_1, std::placeholders::_2));
+    // 5. 设置监听端口
+    wssrv.listen(8080);
+    wssrv.set_reuse_addr(true);
+    // 6. 开始获取新连接
+    wssrv.start_accept();
+    // 7. 启动服务器
+    wssrv.run();
+    return 0; 
+}

+ 199 - 0
Online backgammon battles/source/db.hpp

@@ -0,0 +1,199 @@
+#ifndef __DB_HPP__
+#define __DB_HPP__
+#include "util.hpp"
+#include <mutex>
+#include <cassert>
+
+/*用户数据管理模块 -- 用于管理数据库数据,为数据库中的每张表都设计一个类,然后通过类对象来操作数据库表中的数据*/
+/*用户信息表*/
+class user_table {
+public:
+    user_table(const std::string &host, const std::string &user, const std::string &passwd, \
+                               const std::string db = "gobang", uint16_t port = 4106) 
+    {
+        _mysql = mysql_util::mysql_create(host, user, passwd, db, port);
+        assert(_mysql != nullptr);
+        LOG(DEBUG, "用户数据管理模块初识化完毕");
+    }
+
+    ~user_table() {
+        if(_mysql != nullptr) { 
+            mysql_util::mysql_destroy(_mysql);
+            _mysql = nullptr;
+        }
+        LOG(DEBUG, "用户数据管理模块已被销毁");
+    }
+
+    /*新用户注册*/
+    bool registers(Json::Value &user) {
+        if(user["username"].isNull() || user["password"].isNull()) {
+            LOG(NORMAL, "please input username and password");
+            return false;
+        }
+        // 由于用户名有唯一键约束,所以不需要担心用户已被注册的情况
+        char sql[1024];
+#define INSERT_USER "insert into user values(null, '%s', password('%s'), 1000, 0, 0)"
+        sprintf(sql, INSERT_USER, user["username"].asCString(), user["password"].asCString());
+        // LOG(DEBUG, "%s", sql);
+        if(mysql_util::mysql_execute(_mysql, sql) == false) {
+            LOG(NORMAL, "user register failed");
+            return false;
+        }
+        LOG(NORMAL, "%s register success", user["username"].asCString());
+        return true;
+    }
+
+    /*用户登录验证*/
+    bool login(Json::Value &user) {
+        // 与数据库中的用户名+密码进行比对
+        // 注意:数据库的password是经过mysql password函数转换后的,所以sql查询时也需要对user["password"].asString()进行转化
+#define SELECT_USER "select id, score, total_count, win_count from user where username = '%s' and password = password('%s')"   
+        char sql[1024];
+        sprintf(sql, SELECT_USER, user["username"].asCString(), user["password"].asCString());
+        MYSQL_RES *res = nullptr;
+        {
+            // mysql查询与查询结果的本地保存两步操作需要加锁,避免多线程使用同一句柄进行操作的情况下发送结果集的数据覆盖问题
+            // 将锁交给RAII unique_lock进行管理
+            std::unique_lock<std::mutex> lock(_mutex);
+            if(mysql_util::mysql_execute(_mysql, sql) == false) return false;;
+            // 获取查询到的结果--一行记录
+            res = mysql_store_result(_mysql);
+            // 注意:当mysql查询结果为空时,mysql_store_result也不会返回空,所以不能在这里判断用户名密码是否正确
+            if(res == nullptr) {
+                LOG(NORMAL, "mysql store failed: ", mysql_error(_mysql));
+                return false;
+            }
+        }
+        int row_count = mysql_num_rows(res);
+        int col_count = mysql_num_fields(res);
+        // row_count 为0说明查询不到与当前用户名+密码匹配的数据,即用户名或密码错误
+        if(row_count == 0) {
+            LOG(NORMAL, "the username or password error, please input again");
+            return false;
+        }
+        // 用户名存在唯一键约束
+        if(row_count > 1) {
+            LOG(ERROR, "there are same user %s in the database", user["username"].asCString());
+            return false;
+        }        
+        LOG(NORMAL, "%s login success", user["username"].asCString());
+        // 填充该用户的其他详细信息
+        MYSQL_ROW row = mysql_fetch_row(res);
+        user["id"] = std::stoi(row[0]);
+        user["score"] = std::stoi(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 SELECT_BY_USERNAME "select id, score, total_count, win_count from user where username = '%s'"
+        char sql[1024];
+        sprintf(sql, SELECT_BY_USERNAME, name.c_str());
+        MYSQL_RES *res = nullptr;
+        {
+            // 加锁
+            std::unique_lock<std::mutex> lock(_mutex);
+            if(mysql_util::mysql_execute(_mysql, sql) == false) return false;
+            // 获取查询到的结果--一行记录
+            res = mysql_store_result(_mysql);
+            // 注意:当mysql查询结果为空时,mysql_store_result也不会返回空,所以不能在这里判断用户是否存在
+            if(res == nullptr) {
+                LOG(NORMAL, "mysql store failed: ", mysql_error(_mysql));
+                return false;
+            }
+        }
+        int row_count = mysql_num_rows(res);
+        int col_count = mysql_num_fields(res);
+        // row_count为0说明查询不到与当前用户名匹配的数据,即用户不存在
+        if(row_count == 0) {
+            LOG(NORMAL, "the user with name %s does not exist", name.c_str());
+            return false;
+        }
+        // 用户名存在唯一键约束
+        if(row_count > 1) {
+            LOG(ERROR, "there are same user name %s in the database", name.c_str());
+            return false;
+        }  
+        MYSQL_ROW row = mysql_fetch_row(res);
+        // password是转换后的,获取无意义
+        user["id"] = std::stoi(row[0]);
+        user["username"] = name.c_str();
+        user["score"] = std::stoi(row[1]);
+        user["total_count"] = std::stoi(row[2]);
+        user["win_count"] = std::stoi(row[3]);
+        mysql_free_result(res);
+        return true;
+    }
+
+    /*使用用户ID查找用户的详细信息*/
+    bool select_by_id(uint64_t id, Json::Value &user) {
+#define SELECT_BY_ID "select username, score, total_count, win_count from user where id = %d"
+        char sql[1024];
+        sprintf(sql, SELECT_BY_ID, id);
+        MYSQL_RES *res = nullptr;
+        {
+            // 加锁
+            std::unique_lock<std::mutex> lock(_mutex);
+            if(mysql_util::mysql_execute(_mysql, sql) == false) return false;
+            // 获取查询到的结果--一行记录
+            res = mysql_store_result(_mysql);
+            // 注意:当mysql查询结果为空时,mysql_store_result也不会返回空,所以不能在这里判断用户是否存在
+            if(res == nullptr) {
+                LOG(NORMAL, "mysql store failed: ", mysql_error(_mysql));
+                return false;
+            }
+        }
+        int row_count = mysql_num_rows(res);
+        int col_count = mysql_num_fields(res);
+        // row_count为0说明查询不到与当前用户名ID匹配的数据,即用户不存在
+        if(row_count == 0) {
+            LOG(NORMAL, "the user with ID %d does not exist", id);
+            return false;
+        }
+        // 用户名存在唯一键约束
+        if(row_count > 1) {
+            LOG(ERROR, "there are same user with ID %d in the database", id);
+            return false;
+        }
+        MYSQL_ROW row = mysql_fetch_row(res);
+        // password是转换后的,获取无意义
+        user["id"] = (Json::UInt64)id;
+        user["username"] = row[0];
+        user["score"] = std::stoi(row[1]);
+        user["total_count"] = std::stoi(row[2]);
+        user["win_count"] = std::stoi(row[3]);
+        mysql_free_result(res);
+        return true;        
+    }
+
+    /*用户对战胜利,修改分数以及比赛和胜利场次,胜利一场增加30分*/
+    bool win(uint64_t id) {
+#define UPDATE_WIN "update user set score=score+30, total_count=total_count+1, win_count=win_count+1 where id = %d"
+        char sql[1024];
+        sprintf(sql, UPDATE_WIN, id);
+        if(mysql_util::mysql_execute(_mysql, sql) == false) {
+            LOG(ERROR, "update the user info of win failed");
+            return false;
+        }
+        return true;
+    }
+
+    /*用户对战失败,修改分数以及比赛场次*,失败一场减少30分*/
+    bool lose(uint64_t id) {
+#define UPDATE_LOSE "update user set score=score-30, total_count=total_count+1 where id = %d"
+        char sql[1024];
+        sprintf(sql, UPDATE_LOSE, id);
+        if(mysql_util::mysql_execute(_mysql, sql) == false) {
+            LOG(ERROR, "update the user info of lose failed");
+            return false;
+        }
+        return true;
+    }
+private:
+    MYSQL *_mysql;     //mysql操作句柄
+    std::mutex _mutex;  //解决多线程使用同一类对象(句柄)访问数据库时可能发生的线程安全问题
+};
+#endif

+ 10 - 0
Online backgammon battles/source/db.sql

@@ -0,0 +1,10 @@
+create database if not exists gobang;
+use gobang;
+create table if not exists user (
+    id bigint unsigned primary key auto_increment key,
+    username varchar(32) unique key not null ,
+    password varchar(64) not null,
+    score int default 1000,
+    total_count int default 0,
+    win_count int default 0
+);

二進制
Online backgammon battles/source/gobang


+ 40 - 0
Online backgammon battles/source/logger.hpp

@@ -0,0 +1,40 @@
+#ifndef __LOGGER_HPP__
+#define __LOGGER_HPP__
+#include <cstdio>
+#include <time.h>
+
+/*日志等级*/    
+enum  {
+    NORMAL, 
+    DEBUG, 
+    ERROR,
+    FATAL
+};
+
+/*将日志等级转化为字符串*/
+const char* level_to_stirng(int level) {
+    switch (level)
+    {
+    case NORMAL:
+        return "NORMAL";
+    case DEBUG:
+        return "DEBUG";
+    case ERROR:
+        return "ERROR";
+    case FATAL:
+        return "FATAL";
+    default:
+        return nullptr;
+    }
+}
+
+#define LOG(level, format, ...) do {\
+    const char* levelstr = level_to_stirng(level); /*日志等级*/\
+    time_t ts = time(NULL);  /*时间戳*/\  
+    struct tm *lt = localtime(&ts);  /*格式化时间*/\ 
+    char buffer[32] = { 0 };\
+    strftime(buffer, sizeof(buffer) - 1, "%y-%m-%d %H:%M:%S", lt);  /*格式化时间到字符串*/\
+    fprintf(stdout, "[%s][%s][%s:%d] " format "\n", levelstr, buffer, __FILE__, __LINE__, ##__VA_ARGS__); /*##解除必须传递可变参数的限制*/\
+} while(0)
+
+#endif

+ 11 - 0
Online backgammon battles/source/main.cc

@@ -0,0 +1,11 @@
+#include "server.hpp"
+
+#define HOST "127.0.0.1"
+#define USER "thj"
+#define PASSWD "Abcd1234@"
+
+int main() 
+{
+    gobang_server server(HOST, USER, PASSWD);
+    server.start(8081);
+}

+ 12 - 0
Online backgammon battles/source/makefile

@@ -0,0 +1,12 @@
+.PHONY:all
+all:gobang #test
+
+test:test.cc logger.hpp util.hpp db.hpp online.hpp room.hpp session.hpp matcher.hpp
+	g++ -o $@ $^ -std=c++11 -lmysqlclient  -L/usr/lib64/mysql/ -ljsoncpp -lpthread -lboost_system
+
+gobang:main.cc server.hpp
+	g++ -o $@ $^ -g -std=c++11 -lmysqlclient  -L/usr/lib64/mysql/ -ljsoncpp -lpthread -lboost_system
+
+.PHONY:clean
+clean:
+	rm -f gobang

+ 173 - 0
Online backgammon battles/source/matcher.hpp

@@ -0,0 +1,173 @@
+#ifndef __MATCHER_HPP__
+#define __MATCHER_HPP__
+#include "db.hpp"
+#include "room.hpp"
+#include "util.hpp"
+#include "online.hpp"
+#include <list>
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+
+/*用户对战匹配管理模块 -- 将用户按分数分为青铜、黄金、王者三档,并分别为它们设计一个匹配队列,队列元素>=2则匹配成功,否则阻塞*/
+/*匹配队列类*/
+template <class T>
+class match_queue {
+public:
+    match_queue() {}
+    ~match_queue() {}
+    /*目标元素入队列,并唤醒线程*/
+    void push(const T& data) {
+        std::unique_lock<std::mutex> lock(_mutex);
+        _list.push_back(data);
+        LOG(DEBUG, "%d用户加入匹配队列", data);
+        // 匹配队列每新增一个元素,就唤醒对应的匹配线程,判断是否满足匹配要求(队列人数>=2)
+        _cond.notify_all();
+    }
+
+    /*队头元素出队列并返回队头元素*/
+    bool pop(T& data) {
+        std::unique_lock<std::mutex> lock(_mutex);
+        if(_list.empty()) return false;
+        data = _list.front();
+        _list.pop_front();
+        LOG(DEBUG, "%d用户从匹配队列中移除", data);
+        return true;
+    }
+
+    /*移除队列中的目标元素*/
+    void remove(const T& data) {
+        std::unique_lock<std::mutex> lock(_mutex);
+        _list.remove(data);
+        LOG(DEBUG, "%d用户从匹配队列中移除", data);
+    }
+    
+    /*阻塞线程*/
+    void wait() {
+        std::unique_lock<std::mutex> lock(_mutex);
+        _cond.wait(lock);
+    }
+
+    /*获取队列元素个数*/
+    int size() { 
+        std::unique_lock<std::mutex> lock(_mutex);
+        return _list.size(); 
+    }
+
+    /*判断队列是否为空*/
+    bool empty() {
+        std::unique_lock<std::mutex> lock(_mutex); 
+        return _list.empty();
+    }
+private:
+    std::list<T> _list;  // 使用双向链表而不是queue充当匹配队列,便于用户取消匹配时将该用户从匹配队列中移除
+    std::mutex _mutex;   // 实现线程安全
+    std::condition_variable _cond;  // 条件变量,当向队列中push元素时唤醒,用于阻塞消费者
+};
+
+/*匹配管理类*/
+class matcher {
+private:
+    void handler_match(match_queue<uint64_t> &mq) {
+        while(true) {
+            // 检查匹配条件是否满足(人数>=2),不满足则继续阻塞
+            while(mq.size() < 2) mq.wait();
+            // 条件满足,从队列中取出两个玩家
+            uint64_t uid1, uid2;
+            if(mq.pop(uid1) == false) continue;
+            if(mq.pop(uid2) == false) {
+                // 如果第二个玩家出队列失败,则需要将第一个玩家重新添加到队列中
+                this->add(uid1);
+                continue;
+            }
+            // 检查两个玩家是否都处于大厅在线状态,若一方掉线,则需要将另一方重新添加到队列
+            wsserver_t::connection_ptr conn1 = _om->get_conn_from_hall(uid1);
+            wsserver_t::connection_ptr conn2 = _om->get_conn_from_hall(uid2);
+            if(conn1.get() == nullptr) {
+                this->add(uid2);
+                continue;
+            }
+            if(conn2.get() == nullptr) {
+                this->add(uid1);
+                continue;
+            }
+            // 为两个玩家创建房间,失败则重新添加到队列
+            room_ptr rp = _rm->create_room(uid1, uid2);
+            if(rp.get() == nullptr) {
+                this->add(uid1);
+                this->add(uid2);
+                continue;
+            }
+            // 给玩家返回匹配成功的响应
+            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_low_entry() { handler_match(_q_low); }
+    void th_mid_entry() { handler_match(_q_mid); }
+    void th_high_entry() { handler_match(_q_high); }
+public:
+    matcher(user_table *ut, online_manager *om, room_manager *rm)
+    : _ut(ut), _om(om), _rm(rm), 
+    _th_low(std::thread(&matcher::th_low_entry, this)),
+    _th_mid(std::thread(&matcher::th_mid_entry, this)),
+    _th_high(std::thread(&matcher::th_high_entry, this)) {
+        LOG(DEBUG, "游戏对战匹配管理模块初始化完毕");
+    }
+
+    ~matcher() {
+        LOG(DEBUG, "游戏对战匹配管理模块已被销毁");
+    }
+
+    /*添加用户到匹配队列*/
+    bool add(uint64_t uid) {
+        // 根据用户id获取用户数据库信息
+        Json::Value user;
+        if(_ut->select_by_id(uid, user) == false) {
+            LOG(DEBUG, "查找玩家%d信息失败", uid);
+            return false;
+        }
+        // 根据用户分数将用户添加到对应的匹配队列中去
+        int score = user["score"].asInt();
+        if(score < 2000) _q_low.push(uid);
+        else if(score >= 2000 && score < 3000) _q_mid.push(uid);
+        else _q_high.push(uid);
+        return true;
+    }
+
+    /*将用户从匹配队列中移除*/
+    bool remove(uint64_t uid) {
+        // 根据用户id获取用户数据库信息
+        Json::Value user;
+        if(_ut->select_by_id(uid, user) == false) {
+            LOG(DEBUG, "查找用户%d信息失败", uid);
+            return false;
+        }
+        // 根据用户分数将用户从对应的匹配队列中移除
+        int score = user["score"].asInt();
+        if(score < 2000) _q_low.remove(uid);
+        else if(score >= 2000 && score < 3000) _q_mid.remove(uid);
+        else _q_high.remove(uid);
+        return true;        
+    }
+private:
+    // 三个匹配队列(青铜/黄金/王者 -> low/mid/high)
+    match_queue<uint64_t> _q_low;
+    match_queue<uint64_t> _q_mid;
+    match_queue<uint64_t> _q_high;
+    // 三个管理匹配队列的线程
+    std::thread _th_low;
+    std::thread _th_mid;
+    std::thread _th_high;
+    room_manager *_rm;    // 游戏房间管理句柄
+    online_manager *_om;  // 在线用户管理句柄
+    user_table *_ut;      // 用户数据管理句柄
+};
+#endif

+ 76 - 0
Online backgammon battles/source/online.hpp

@@ -0,0 +1,76 @@
+#ifndef __ONLINE_HPP__
+#define __ONLINE_HPP__
+#include "util.hpp"
+#include <mutex>
+#include <unordered_map>
+#include <websocketpp/server.hpp>
+#include <websocketpp/config/asio_no_tls.hpp>
+
+typedef websocketpp::server<websocketpp::config::asio> wsserver_t;
+
+/*在线用户管理模块 -- 用于管理在游戏大厅以及游戏房间中的用户,建立用户id与websocket长连接的对应关系*/
+class online_manager {
+public:
+    online_manager() { LOG(DEBUG, "在线用户管理模块初始化完毕"); }
+    ~online_manager() { LOG(DEBUG, "在线用户管理模块已被销毁"); }
+
+    /*用户进入游戏大厅(此时用户websocket长连接已建立好)*/
+    void enter_game_hall(uint64_t uid, wsserver_t::connection_ptr conn) {
+        std::unique_lock<std::mutex> lock(_mutex);
+        _hall_user[uid] = conn;
+    }
+    /*用户进入游戏房间*/
+    void enter_game_room(uint64_t uid, wsserver_t::connection_ptr conn) {
+        std::unique_lock<std::mutex> lock(_mutex);
+        _room_user[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 nullptr;
+        return _hall_user[uid];
+    }
+
+    /*通过用户id获取游戏房间用户的通信连接*/
+    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 nullptr;
+        return _room_user[uid];
+    }
+private:
+    std::mutex _mutex;  // 解决多线程模式下的线程安全问题
+    std::unordered_map<uint64_t, wsserver_t::connection_ptr> _hall_user;  // 建立游戏大厅用户id与通信连接之间的关系
+    std::unordered_map<uint64_t, wsserver_t::connection_ptr> _room_user;  // 建立游戏房间用户id与通信连接之间的关系
+};
+#endif

+ 312 - 0
Online backgammon battles/source/room.hpp

@@ -0,0 +1,312 @@
+#ifndef __ROOM_HPP__
+#define __ROOM_HPP__
+#include "util.hpp"
+#include "db.hpp"
+#include "online.hpp"
+#include <vector>
+
+#define BOARD_ROW 15
+#define BOARD_COL 15
+#define CHESS_WHITE 1
+#define CHESS_BLACK 2
+
+typedef enum {
+    GAME_START,
+    GAME_OVER
+} room_status;
+
+/*游戏房间管理模块 -- 用于管理在游戏房间中产生的各种数据以及动作,同时也包括对多个游戏房间本身的管理*/
+/*游戏房间类*/
+class room {
+private:
+    /*check_win子函数,其中row/col表示下棋位置,row_off/col_off表示是否偏移*/
+    bool five_piece(int row, int col, int row_off, int col_off, int color) {
+        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;
+    }
+
+    /*判断是否有用户胜利并返回winner_id (0表示没有用户胜利,非0表示有)*/
+    uint64_t check_win(int chess_row, int chess_col, int cur_color) {
+        uint64_t winner_id = cur_color == CHESS_WHITE ? _white_user_id : _black_user_id;
+        // 横行方向:当前位置开始,行不变,列++/--
+        if(five_piece(chess_row, chess_col, 0, 1, cur_color)) return winner_id;
+        // 纵列方向:当前位置开始,行++/--,列不变
+        if(five_piece(chess_row, chess_col, 1, 0, cur_color)) return winner_id;
+        // 正斜方向:当前位置开始,行++列-- 以及 行--列++
+        if(five_piece(chess_row, chess_col, 1, -1, cur_color)) return winner_id;
+        // 反斜方向:当前位置开始,行++列++ 以及 行--列--
+        if(five_piece(chess_row, chess_col, 1, 1, cur_color)) return winner_id;
+        // 没有人获胜返回0
+        return 0;
+    }
+
+    /*用户胜利或失败后更新用户数据库信息*/
+    void update_db_info(uint64_t winner_id, uint64_t loser_id) {
+        _tb_user->win(winner_id);
+        _tb_user->lose(loser_id);
+    }
+public:
+    room(uint64_t room_id, user_table *tb_user, online_manager *online_user)
+    : _room_id(room_id), _statu(GAME_START), _tb_user(tb_user), _online_user(online_user), _board(BOARD_ROW, std::vector<int>(BOARD_COL, 0))
+    {
+        LOG(DEBUG, "%d号房间创建成功", _room_id);
+    }
+    ~room() { LOG(DEBUG, "%d号房间已被销毁", _room_id); }
+
+    /*添加白棋用户*/
+    void add_white_user(uint64_t id) {
+        _white_user_id = id;
+        ++_player_count;
+    }
+
+    /*添加黑棋用户*/
+    void add_black_user(uint64_t id) {
+        _black_user_id = id;
+        ++_player_count;
+    }
+
+    /*处理玩家下棋动作并返回响应*/
+    Json::Value handler_chess(Json::Value &req) {
+        Json::Value resp = req;
+        // 判断白棋与黑棋用户是否在线,若一方不在线,另一方直接获胜
+        if(_online_user->is_in_game_room(_white_user_id) == false) {
+            resp["result"] = true;
+            resp["reason"] = "对方已掉线,游戏获胜";  // 在黑棋的视角,白棋是"对方"  
+            resp["winner"] = (Json::UInt64)_black_user_id;  // 白棋掉线,黑棋用户
+        }
+        if(_online_user->is_in_game_room(_black_user_id) == false) {
+            resp["result"] = true;
+            resp["reason"] = "对方已掉线,游戏胜利";    
+            resp["winner"] = (Json::UInt64)_white_user_id;  
+        }
+        // 获取下棋位置,判断位置是否合理并下棋
+        uint64_t cur_uid = req["uid"].asUInt64();
+        int chess_row = req["row"].asInt();
+        int chess_col = req["col"].asInt();
+        if(_board[chess_row][chess_col] != 0) {
+            resp["result"] = false;
+            resp["reason"] = "该位置已被占用";
+            return resp;            
+        }
+        int cur_color = (cur_uid == _white_user_id ? CHESS_WHITE : CHESS_BLACK);
+        _board[chess_row][chess_col] = cur_color;
+        // 判断是否有玩家获胜(存在五星连珠的情况) 其中0表示没有玩家胜利,非0表示胜利的玩家id
+        uint64_t winner_id = check_win(chess_row, chess_col, cur_color);
+        resp["result"] = true;
+        resp["reason"] = "下棋成功";  
+        resp["winner"] = (Json::UInt64)winner_id;
+        if(winner_id != 0) { resp["reason"] = "五星连珠,游戏胜利"; }
+        return resp;
+    }
+
+    /*处理玩家聊天动作并返回响应*/
+    Json::Value handler_chat(Json::Value &req) {
+        Json::Value resp = req;
+        // 检查消息中是否包含敏感词
+        std::string msg = req["message"].asString();
+        size_t pos = msg.find("垃圾");
+        if(pos != std::string::npos) {
+            resp["result"] = false;
+            resp["reason"] = "消息中包含敏感词";
+            return resp;
+        }
+        resp["reslut"] = true;
+        return resp;
+    }
+
+    /*处理玩家退出动作并返回响应*/
+    void handler_exit(uint64_t uid) {
+        // 如果玩家在下棋中,则对方直接获胜
+        if(_statu == GAME_START) {
+            Json::Value resp;
+            resp["optype"] = "put_chess";
+            resp["result"] = true;
+            resp["reason"] = "对方已退出,游戏胜利";
+            resp["room_id"] = (Json::UInt64)_room_id;
+            resp["uid"] = (Json::UInt64)uid;
+            resp["row"] = -1;
+            resp["col"] = -1;
+            resp["winner"] = (Json::UInt64)(uid == _white_user_id ? _black_user_id : _white_user_id);
+            // 更新用户数据库信息与游戏房间的状态
+            uint64_t loser_id = uid;
+            uint64_t winner_id = loser_id == _white_user_id ? _black_user_id : _white_user_id;
+            update_db_info(winner_id, loser_id);
+            _statu = GAME_OVER;
+            // 将消息广播给房间其他玩家
+            broadcast(resp);
+        }
+        // 游戏结束正常退出直接更新玩家数量
+        --_player_count;
+    }
+
+    /*总的动作处理函数,负责判断动作类型并调用对应的处理函数,得到处理响应后将其广播给房间中其他用户*/
+    /*注意:玩家退出动作属于玩家断开连接后调用的操作,不属于handler的一种*/
+    void handler(Json::Value &req) {
+        Json::Value resp;
+        // 判断房间号是否匹配
+        if(_room_id != req["room_id"].asUInt64()) {
+            resp["optype"] = req["optype"].asString();
+            resp["result"] = false;
+            resp["reason"] = "房间号不匹配";
+            broadcast(resp);
+            return;
+        }
+        // 根据请求类型调用不同的处理函数
+        std::string type = req["optype"].asString();
+        if(type == "put_chess") {
+            resp = handler_chess(req);
+            // 判断是否有玩家获胜,如果有则需要更新用户数据库信息与游戏房间的状态
+            if(resp["winner"].asUInt64() != 0) {
+                uint64_t winner_id = resp["winner"].asUInt64();
+                uint64_t loser_id = (winner_id == _white_user_id ? _black_user_id : _white_user_id);
+                update_db_info(winner_id, loser_id);
+                _statu = GAME_OVER;
+            }
+        } else if(type == "chat") {
+            resp = handler_chat(req);
+        } else {
+            resp["optype"] = req["optype"].asString();
+            resp["result"] = false;
+            resp["reason"] = "位置请求类型";
+        }
+        // 将消息广播给房间中的其他玩家
+        broadcast(resp);
+    }
+
+    /*将动作响应广播给房间中的其他玩家*/
+    void broadcast(Json::Value &resp) {
+        // 将Json响应进行序列化
+        std::string body;
+        json_util::serialize(resp, body);
+        // 获取房间中的所有玩家的通信连接
+        wsserver_t::connection_ptr conn_white = _online_user->get_conn_from_room(_white_user_id);
+        wsserver_t::connection_ptr conn_black = _online_user->get_conn_from_room(_black_user_id);
+        // 如果玩家连接没有断开,则将消息广播给他
+        if(conn_white.get() != nullptr) {
+            conn_white->send(body);
+        }
+        if(conn_black.get() != nullptr) {
+            conn_black->send(body);
+        }
+    }
+public:
+    // 将部分成员变量设为public,供外部类访问
+    uint64_t _room_id;             // 房间ID
+    room_status _statu;            // 房间状态
+    int _player_count;             // 玩家数量
+    uint64_t _white_user_id;       // 白棋玩家ID
+    uint64_t _black_user_id;       // 黑棋玩家ID
+private:
+    user_table *_tb_user;          // 管理玩家数据的句柄
+    online_manager *_online_user;  // 管理玩家在线状态的句柄 
+    std::vector<std::vector<int>> _board;  // 二维棋盘
+};
+
+/*管理房间数据的智能指针*/
+using room_ptr = std::shared_ptr<room>;  
+
+/*游戏房间管理类*/
+class room_manager {
+public:
+    room_manager(user_table *tb_user, online_manager *online_user)
+    : _next_rid(1), _tb_user(tb_user), _online_user(online_user) {
+        LOG(DEBUG, "游戏房间管理模块初始化成功");
+    }
+    ~room_manager() { LOG(NORMAL, "游戏房间管理模块已被销毁"); }
+
+    /*为两个玩家创建房间,并返回房间信息*/
+    room_ptr create_room(uint64_t uid1, uint64_t uid2) {
+        // 判断两个玩家是否都处于游戏大厅中
+        if(_online_user->is_in_game_hall(uid1) == false || _online_user->is_in_game_hall(uid2) == false) {
+            LOG(DEBUG, "玩家不在游戏大厅中,匹配失败");
+            return room_ptr();
+        }
+        // 创建游戏房间,将用户信息添加到房间中
+        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);
+        // 将游戏房间管理起来(建立房间id与房间信息以及玩家id与房间id的关联关系)
+        _rooms[_next_rid] = rp;
+        _users[uid1] = _next_rid;
+        _users[uid2] = _next_rid;
+        // 更新下一个房间的房间id
+        ++_next_rid;
+        // 返回房间信息
+        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 _rooms[rid];
+    }
+
+    /*通过用户id获取房间信息*/
+    room_ptr get_room_by_uid(uint64_t uid) {
+        std::unique_lock<std::mutex> lock(_mutex);
+        // 获取房间id
+        auto it1 = _users.find(uid);
+        if(it1 == _users.end()) return room_ptr();
+        uint64_t rid = _users[uid];
+        // 获取房间信息(这里不能直接调用get_room_by_rid,会造成死锁)
+        auto it2 = _rooms.find(rid);
+        if(it2 == _rooms.end()) return room_ptr();
+        return _rooms[rid];
+    }
+
+    /*通过房间id销毁房间*/
+    void remove_room(uint64_t rid) {
+        // 通过房间id获取房间信息
+        room_ptr rp = get_room_by_rid(rid);
+        if(rp.get() == nullptr) return;
+        // 通过房间信息获取房间中的玩家
+        uint64_t white_user_id = rp->_white_user_id;
+        uint64_t black_user_id = rp->_black_user_id;
+        // 移除房间管理中的玩家信息
+        std::unique_lock<std::mutex> lock(_mutex);
+        _users.erase(white_user_id);
+        _users.erase(black_user_id);
+        // 移除房间管理信息 -- 移除房间对应的shared_ptr(room_ptr)
+        _rooms.erase(rid);
+    }
+
+    /*删除房间中的指定用户,若房间中没有用户则销毁房间(用户断开websocket连接时调用)*/
+    void remove_room_user(uint64_t uid) {
+        // 通过玩家id获取房间信息
+        room_ptr rp = get_room_by_uid(uid);
+        if(rp.get() == nullptr) return;
+        // 玩家退出
+        rp->handler_exit(uid);
+        // 如果房间中没有玩家了,则移除房间
+        if(rp->_player_count == 0) remove_room(rp->_room_id);
+    }
+private:
+    uint64_t _next_rid;             //房间ID分配计数器
+    std::mutex _mutex;              
+    user_table *_tb_user;           // 管理玩家数据的句柄
+    online_manager *_online_user;   // 管理玩家在线状态的句柄
+    std::unordered_map<uint64_t, room_ptr> _rooms;  // 建立房间id与房间信息的关联关系
+    std::unordered_map<uint64_t, uint64_t> _users;  // 建立用户id与房间id的关联关系
+};
+#endif

+ 442 - 0
Online backgammon battles/source/server.hpp

@@ -0,0 +1,442 @@
+/* 
+服务器模块 
+通过对之前所有模块进行整合以及进行服务器搭建,最终封装实现出⼀个gobang_server的服务器模块类,向外提供搭建五⼦棋对战服务器的接⼝。
+达到通过实例化的对象就可以简便的完成服务器搭建的目的
+*/
+
+#ifndef __SERVER_HPP__
+#define __SERVER_HPP__
+#include "util.hpp"
+#include "db.hpp"
+#include "online.hpp"
+#include "room.hpp"
+#include "matcher.hpp"
+#include "session.hpp"
+
+#define WWWROOT "./wwwroot"
+
+typedef websocketpp::server<websocketpp::config::asio> wsserver_t;
+
+class gobang_server {
+private:
+    /*http静态资源请求处理函数(注册界面、登录界面、游戏大厅界面)*/
+    void file_handler(wsserver_t::connection_ptr conn) {
+        // 获取http请求对象与请求uri
+        websocketpp::http::parser::request req = conn->get_request();
+        std::string uri = req.get_uri();
+        // 根据uri组合出文件路径,如果文件路径是目录(/结尾)则追加login.html,否则返回相应界面
+        std::string pathname = _wwwroot + uri;
+        if(pathname.back() == '/') {
+            pathname += "login.html";
+        }
+        // 读取文件内容,如果文件不存在,则返回404
+        std::string body;
+        if(file_util::read(pathname.c_str(), body) == false) {
+            body += "<html><head><meta charset='UTF-8'/></head><body><h1> 404 Not Found </h1></body></html>";
+            // 设置响应状态码
+            conn->set_status(websocketpp::http::status_code::not_found);
+        }
+        else conn->set_status(websocketpp::http::status_code::ok);
+        // 添加响应头部
+        conn->append_header("Content-Length", std::to_string(body.size()));
+        // 设置响应正文
+        conn->set_body(body);        
+    }
+
+    /*处理http响应的子功能函数*/
+    void http_resp(wsserver_t::connection_ptr conn, bool result, websocketpp::http::status_code::value code, const std::string &reason) {
+            // 设置响应正文及其序列化
+            Json::Value resp;
+            std::string resp_body;
+            resp["result"] = result;
+            resp["reason"] = reason;
+            json_util::serialize(resp, resp_body);
+            // 设置响应状态码,添加响应正文以及正文类型
+            conn->set_status(code);
+            conn->append_header("Content-Type", "application/json");
+            conn->set_body(resp_body);
+    }
+
+    /*http动态功能请求处理函数 -- 用户注册*/
+    void reg(wsserver_t::connection_ptr conn) {
+        // 获取json格式的请求正文
+        std::string req_body = conn->get_request_body();
+        // 将正文反序列化得到username和password
+        Json::Value user_info;
+        if(json_util::deserialize(req_body, user_info) == false) {
+            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请求正文格式错误");
+        }
+        // 数据库新增用户
+        if(user_info["username"].isNull() || user_info["password"].isNull()) {
+            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名/密码");
+        }
+        if(_ut.registers(user_info) == false) {
+            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "该用户名已被占用");
+        }
+        return http_resp(conn, true, websocketpp::http::status_code::ok, "用户注册成功");
+    }
+
+    /*http动态功能请求处理函数 -- 用户登录*/
+    void login(wsserver_t::connection_ptr conn) {
+        // 获取请求正文并反序列化
+        std::string req_body = conn->get_request_body();
+        Json::Value user_info;
+        if(json_util::deserialize(req_body, user_info) == false) {
+            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请求正文格式错误");
+        }
+        if(user_info["username"].isNull() || user_info["password"].isNull()) {
+            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名/密码");
+        }
+        // 用户登录 -- 登录失败返回404
+        if(_ut.login(user_info) == false) {
+            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名/密码错误");
+        }
+        // 登录成功则为用户创建session信息以及session生命周期
+        session_ptr ssp = _sm.create_session(user_info["id"].asUInt64());
+        if(ssp.get() == nullptr) {
+            return http_resp(conn, false, websocketpp::http::status_code::internal_server_error, "用户会话创建失败");
+        }
+        _sm.set_session_expire_time(ssp->get_ssid(), SESSION_TIMEOUT);
+        // 设置过响应头部 将cookie返回给客户端
+        std::string cookie_ssid = "SSID=" + std::to_string(ssp->get_ssid());
+        conn->append_header("Set-Cookie", cookie_ssid);
+        return http_resp(conn, true, websocketpp::http::status_code::ok, "用户登录成功");
+    }
+
+    /*从http请求头部Cookie中获取指定key对应的value*/
+    bool get_cookie_val(const std::string &cookie_str, const std::string &key, std::string &val) {
+        // cookie_str格式:SSID=XXX; path=/XXX
+        // 先以逗号为分割将cookie_str中的各个cookie信息分割开
+        std::vector<std::string> cookies;
+        string_util::split(cookie_str, ";", cookies);
+        // 再以等号为分割将单个cookie中的key与val分割开,比对查找目标key对应的val
+        for(const auto cookie : cookies) {
+            std::vector<std::string> kv;
+            string_util::split(cookie, "=", kv);
+            if(kv.size() != 2) continue;
+            if(kv[0] == key) {
+                val = kv[1];
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /*http动态功能请求处理函数 -- 获取用户信息*/
+    void info(wsserver_t::connection_ptr conn) {
+        // 通过http请求头部中的cookie字段获取用户ssid
+        std::string cookie_str = conn->get_request_header("Cookie");
+        if(cookie_str.empty()) {
+            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "找不到Cookie信息,请重新登录");
+        }
+        std::string ssid_str;
+        if(get_cookie_val(cookie_str, "SSID", ssid_str) == false) {
+            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "找不到Session信息,请重新登录");
+        }
+        // 根据ssid_str获取用户Session信息
+        session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
+        if(ssp.get() == nullptr) {
+            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "Session已过期,请重新登录");
+        }
+        // 通过用户session获取用户id,再根据用户id获取用户详细信息
+        uint64_t uid = ssp->get_user(); 
+        Json::Value user;
+        if(_ut.select_by_id(uid, user) == false) {
+            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户信息不存在");
+        }
+        // 返回用户详细信息
+        std::string body;
+        json_util::serialize(user, body);
+        std::string resp_cookie = "SSID=" + ssid_str;
+        conn->set_status(websocketpp::http::status_code::ok);
+        conn->append_header("Content-Type", "application/json");
+        conn->append_header("Set-Cookie", resp_cookie);
+        conn->set_body(body);
+        // 更新用户session过期时间
+        _sm.set_session_expire_time(ssp->get_ssid(), SESSION_TIMEOUT);        
+    }
+private:
+    /*http请求回调函数*/
+    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();
+        // 根据不同的请求方法和请求路径类型调用不同的处理函数
+        // 动态功能请求
+        if(method == "POST" && uri == "/reg") reg(conn);
+        else if(method == "POST" && uri == "/login") login(conn);
+        else if(method == "GET" && uri == "/info") info(conn);
+        // 静态资源请求
+        else file_handler(conn);
+    }
+
+    /*游戏大厅websocket长连接建立后的响应子函数*/
+    void game_hall_resp(wsserver_t::connection_ptr conn, bool result, const std::string &reason = "") {
+        Json::Value resp;
+        resp["optype"] = "hall_ready";
+        resp["result"] = result;
+        // 只有错误才返回错误信息reason
+        if(result == false) resp["reason"] = reason;
+        std::string body;
+        json_util::serialize(resp, body);
+        conn->send(body);
+    }
+
+    /*wsopen_callback子函数 -- 游戏大厅websocket长连接建立后的处理函数*/
+    void wsopen_game_hall(wsserver_t::connection_ptr conn) {
+        // 检查用户是否登录 -- 检查cookie&session信息
+        // 通过http请求头部中的cookie字段获取用户ssid
+        std::string cookie_str = conn->get_request_header("Cookie");
+        if(cookie_str.empty()) {
+            return game_hall_resp(conn, false, "找不到Cookie信息,请重新登录");
+        }
+        std::string ssid_str;
+        if(get_cookie_val(cookie_str, "SSID", ssid_str) == false) {
+            return game_hall_resp(conn, false, "找不到Session信息,请重新登录");
+        }
+        // 根据ssid_str获取用户Session信息
+        session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
+        if(ssp.get() == nullptr) {
+            return game_hall_resp(conn, false, "Session已过期,请重新登录");
+        }
+        // 通过用户session获取用户id
+        uint64_t uid = ssp->get_user();
+        // 检查用户是否重复登录 -- 用户游戏大厅长连接/游戏房间长连接是否已经存在
+        if(_om.is_in_game_hall(uid) == true) {
+            return game_hall_resp(conn, false, "玩家重复登录");
+        }        
+        // 将玩家及其连接加入到在线游戏大厅中
+        _om.enter_game_hall(uid, conn);
+        // 返回响应
+        game_hall_resp(conn, true);
+        // 将用户Session过期时间设置为永不过期
+        _sm.set_session_expire_time(ssp->get_ssid(), SESSION_FOREVER);
+    }
+
+    /*游戏房间websocket长连接建立后的响应子函数*/
+    void game_room_resp(wsserver_t::connection_ptr conn, bool result, const std::string &reason,  
+                        uint64_t room_id = 0, uint64_t self_id = 0, uint64_t white_id = 0, uint64_t black_id = 0) {
+        Json::Value resp;
+        resp["optype"] = "room_ready";
+        resp["result"] = result;
+        // 如果成功返回room_id,self_id,white_id,black_id等信息,如果错误则返回错误信息
+        if(result == true) {
+            resp["room_id"] = (Json::UInt64)room_id;
+            resp["uid"] = (Json::UInt64)self_id;
+            resp["white_id"] = (Json::UInt64)white_id;
+            resp["black_id"] = (Json::UInt64)black_id;
+        }
+        else resp["reason"] = reason;        
+        std::string body;
+        json_util::serialize(resp, body);
+        conn->send(body);
+    }    
+
+    /*wsopen_callback子函数 -- 游戏房间websocket长连接建立后的处理函数*/
+    void wsopen_game_room(wsserver_t::connection_ptr conn) {
+        // 获取cookie&session信息
+        std::string cookie_str = conn->get_request_header("Cookie");
+        if(cookie_str.empty()) {
+            return game_room_resp(conn, false, "找不到Cookie信息,请重新登录");
+        }
+        std::string ssid_str;
+        if(get_cookie_val(cookie_str, "SSID", ssid_str) == false) {
+            return game_room_resp(conn, false, "找不到Session信息,请重新登录");
+        }
+        // 根据ssid_str获取用户Session信息
+        session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
+        if(ssp.get() == nullptr) {
+            return game_room_resp(conn, false, "Session已过期,请重新登录");
+        }    
+        // 判断用户是否已经处于游戏大厅/房间中了(在创建游戏房间长连接之前,游戏大厅的长连接已经断开了) -- 在线用户管理
+        if(_om.is_in_game_hall(ssp->get_user()) || _om.is_in_game_room(ssp->get_user())) {
+            return game_room_resp(conn, false, "玩家重复登录");
+        } 
+        // 判断游戏房间是否被创建 -- 游戏房间管理
+        room_ptr rp = _rm.get_room_by_uid(ssp->get_user());
+        if(rp.get() == nullptr) {
+            return game_room_resp(conn, false, "找不到房间信息");
+        }
+        // 将玩家加入到在线游戏房间中
+        _om.enter_game_room(ssp->get_user(), conn);
+        // 返回响应信息
+        game_room_resp(conn, true, "", rp->_room_id, ssp->get_user(), rp->_white_user_id, rp->_black_user_id);
+        // 将玩家session设置为永不过期
+        _sm.set_session_expire_time(ssp->get_ssid(), SESSION_FOREVER);
+    }
+
+    /*websocket长连接建立之后的处理函数*/
+    void wsopen_callback(websocketpp::connection_hdl hdl) {
+        // 获取通信连接、http请求对象和请求uri
+        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();
+        // 进入游戏大厅与进入游戏房间需要分别建立websocket长连接
+        if(uri == "/hall") wsopen_game_hall(conn);
+        else if(uri == "/room") wsopen_game_room(conn);
+    }
+
+    /*wsclose_callback子函数 -- 游戏大厅websocket长连接断开后的处理函数*/
+    void wsclose_game_hall(wsserver_t::connection_ptr conn) {
+        // 获取cookie&session,如果不存在则说明websocket长连接未建立(websocket长连接建立后Session永久存在),直接返回
+       std::string cookie_str = conn->get_request_header("Cookie");
+        if(cookie_str.empty()) return;
+        std::string ssid_str;
+        if(get_cookie_val(cookie_str, "SSID", ssid_str) == false) return;
+        session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
+        if(ssp.get() == nullptr) return;
+        // 将玩家从游戏大厅移除
+        _om.exit_game_hall(ssp->get_user());
+        // 将玩家session设置为定时删除
+        _sm.set_session_expire_time(ssp->get_ssid(), SESSION_TIMEOUT);           
+    }    
+
+    /*wsclose_callback子函数 -- 游戏房间websocket长连接断开后的处理函数*/
+    void wsclose_game_room(wsserver_t::connection_ptr conn) {
+        // 获取cookie&session,如果不存在直接返回
+       std::string cookie_str = conn->get_request_header("Cookie");
+        if(cookie_str.empty()) return;
+        std::string ssid_str;
+        if(get_cookie_val(cookie_str, "SSID", ssid_str) == false) return;
+        session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
+        if(ssp.get() == nullptr) return;
+        // 将玩家从在线用户管理的游戏房间中移除
+        _om.exit_game_room(ssp->get_user());
+        // 将玩家从游戏房间管理的房间中移除
+        _rm.remove_room_user(ssp->get_user());
+        // 设置玩家session为定时删除
+        _sm.set_session_expire_time(ssp->get_ssid(), SESSION_TIMEOUT);        
+    }
+
+    /*websocket长连接断开之间的处理函数*/
+    void wsclose_callback(websocketpp::connection_hdl hdl) {
+        // 获取通信连接、http请求对象和请求uri
+        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();
+        // 离开游戏大厅与离开游戏房间需要分别断开websocket长连接
+        if(uri == "/hall") wsclose_game_hall(conn);
+        else if(uri == "/room") wsclose_game_room(conn);  
+    }
+
+    /*wsmsg_callback子函数 -- 游戏大厅通信处理函数*/
+    void wsmsg_game_hall(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg) {
+        // 获取cookie&session,如果不存在则返回错误信息
+        std::string cookie_str = conn->get_request_header("Cookie");
+        if(cookie_str.empty()) {
+            return game_hall_resp(conn, false, "找不到Cookie信息,请重新登录");
+        }
+        std::string ssid_str;
+        if(get_cookie_val(cookie_str, "SSID", ssid_str) == false) {
+            return game_hall_resp(conn, false, "找不到Session信息,请重新登录");
+        }
+        session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
+        if(ssp.get() == nullptr) {
+            return game_hall_resp(conn, false, "Session已过期,请重新登录");
+        }
+        // 获取请求信息 
+        std::string req_msg_body = msg->get_payload(); 
+        Json::Value req_msg;
+        if(json_util::deserialize(req_msg_body, req_msg) == false)  {
+            return game_hall_resp(conn, false, "请求信息解析失败"); 
+        }
+        // 处理请求信息 -- 开始对战匹配与停止对战匹配
+        Json::Value resp = req_msg;
+        std::string resp_body;
+        // 开始对战匹配请求则将用户加入到匹配队列中,取消对战匹配请求则将用户从匹配队列中移除
+        if(req_msg["optype"].isNull() == false && req_msg["optype"].asString() == "match_start") {
+            _mm.add(ssp->get_user());
+            resp["result"] = true;
+            json_util::serialize(resp, resp_body);
+            conn->send(resp_body);
+        } else if(req_msg["optype"].isNull() == false && req_msg["optype"].asString() == "match_stop") {
+            _mm.remove(ssp->get_user());
+            resp["result"] = true;
+            json_util::serialize(resp, resp_body);
+            conn->send(resp_body);
+        } else {
+            resp["optype"] = "unknown";
+            resp["result"] = false;
+            json_util::serialize(resp, resp_body);
+            conn->send(resp_body);
+        }       
+    }    
+
+    /*wsmsg_callback子函数 -- 游戏房间通信处理函数*/
+    void wsmsg_game_room(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg) {
+        // 获取cookie&session,如果不存在则返回错误信息
+        std::string cookie_str = conn->get_request_header("Cookie");
+        if(cookie_str.empty()) {
+            return game_room_resp(conn, false, "找不到Cookie信息,请重新登录");
+        }
+        std::string ssid_str;
+        if(get_cookie_val(cookie_str, "SSID", ssid_str) == false) {
+            return game_room_resp(conn, false, "找不到Session信息,请重新登录");
+        }
+        session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
+        if(ssp.get() == nullptr) {
+            return game_room_resp(conn, false, "Session已过期,请重新登录");
+        }
+        // 获取房间信息
+        room_ptr rp = _rm.get_room_by_uid(ssp->get_user());
+        if(rp.get() == nullptr) {
+            return game_room_resp(conn, false, "找不到房间信息");
+        }
+        // 获取请求信息 
+        std::string req_msg_body = msg->get_payload(); 
+        Json::Value req_msg;
+        if(json_util::deserialize(req_msg_body, req_msg) == false)  {
+            return game_room_resp(conn, false, "请求信息解析失败"); 
+        }
+        // 处理请求信息 -- 下棋动作与聊天动作
+        rp->handler(req_msg);
+    }    
+
+    /*websocket长连接建立后通信的处理函数*/
+    void wsmsg_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg) {
+        // 获取通信连接、http请求对象和请求uri
+        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") wsmsg_game_hall(conn, msg);
+        else if(uri == "/room") wsmsg_game_room(conn, msg);  
+    }   
+public:
+    /*成员初始化与服务器回调函数设置*/
+    gobang_server(const std::string &host, const std::string &user, const std::string &passwd, \
+                               const std::string db = "gobang", uint16_t port = 4106)
+        : _wwwroot(WWWROOT), _ut(host, user, passwd, db, port), _sm(&_wssrv), _rm(&_ut, &_om), _mm(&_ut, &_om, &_rm) {
+        // 设置日志等级
+        _wssrv.set_access_channels(websocketpp::log::alevel::none);
+        // 初始化asio调度器
+        _wssrv.init_asio();
+        // 设置回调函数
+        _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(uint16_t port) {
+        // 设置监听端口
+        _wssrv.listen(port);
+        _wssrv.set_reuse_addr(true);
+        // 开始获取新连接
+        _wssrv.start_accept();
+        // 启动服务器
+        _wssrv.run();        
+    }
+private:
+    std::string _wwwroot;    // 静态资源根目录
+    user_table _ut;          // 用户数据管理模块句柄
+    session_manager _sm;     // 用户session信息管理模块句柄
+    online_manager _om;      // 用户在线信息管理模块句柄
+    room_manager _rm;        // 游戏房间管理模块句柄
+    matcher _mm;             // 用户对战匹配管理模块句柄
+    wsserver_t _wssrv;       // websocketpp::server 句柄
+};
+#endif

+ 128 - 0
Online backgammon battles/source/session.hpp

@@ -0,0 +1,128 @@
+#ifndef __SESSION_HPP__
+#define __SESSION_HPP__
+#include "online.hpp"
+#include "logger.hpp"
+#include <functional>
+
+typedef enum {
+    UNLOGIN, 
+    LOGIN
+} ss_statu;
+
+/*用户session信息管理模块 -- 用于http短连接通信情况下用户状态的管理(登录/未登录)*/
+/*session 类*/
+class session {
+public:
+    session(uint64_t ssid) : _ssid(ssid), _statu(LOGIN) { LOG(DEBUG, "session %d:%p 被创建", _ssid, this); }
+    ~session() { LOG(DEBUG, "session %d:%p 被删除", _ssid, this); }
+    /*添加用户*/
+    void add_user(uint64_t uid) { _uid = uid; }
+    /*获取用户id*/
+    uint64_t get_user() { return _uid; }
+    /*获取用户状态(检查用户是否已登录)*/
+    bool is_login() { return _statu == LOGIN; }
+    /*获取session id*/
+    uint64_t get_ssid() { return _ssid; }
+    /*设置session定时删除任务*/
+    void set_timer(const wsserver_t::timer_ptr &tp) { _tp = tp; }
+    /*获取session关联的定时器*/
+    wsserver_t::timer_ptr& get_timer() { return _tp; }
+private:
+    uint64_t _ssid;             // session id
+    uint64_t _uid;              // session对应的用户id
+    ss_statu _statu;            // 用户状态(登录/未登录)
+    wsserver_t::timer_ptr _tp;  // session关联的定时器
+};
+
+#define SESSION_TIMEOUT 30000  //30s
+#define SESSION_FOREVER -1
+
+/*使用智能指针来管理session信息*/
+using session_ptr = std::shared_ptr<session>;
+
+/*session 管理类*/
+class session_manager {
+public:
+    session_manager(wsserver_t *server)
+    : _server(server), _next_ssid(1) {
+        LOG(DEBUG, "用户session管理模块初始化成功");
+    }
+    ~session_manager() { LOG(DEBUG, "用户session管理模块已被销毁"); }
+
+    /*为指定用户创建session信息并返回*/
+    session_ptr create_session(uint64_t uid) {
+        std::unique_lock<std::mutex> lock(_mutex);
+        // 创建session信息
+        session_ptr ssp(new session(_next_ssid));
+        ssp->add_user(uid);
+        // 建立sessionID与session信息的关联关系
+        _sessions[_next_ssid] = ssp;
+        // 更新下一个session的id计数
+        ++_next_ssid;
+        return ssp;
+    }
+
+    /*通过sessionID获取session信息*/
+    session_ptr get_session_by_ssid(uint64_t ssid) {
+        std::unique_lock<std::mutex> lock(_mutex);
+        auto it = _sessions.find(ssid);
+        if(it == _sessions.end()) return session_ptr();
+        return _sessions[ssid];
+    }
+
+    /*删除session信息*/
+    void remove_session(uint64_t ssid) {
+        std::unique_lock<std::mutex> lock(_mutex);
+        _sessions.erase(ssid);
+    }
+
+    /*重新添加因cancel函数被删除的_sessions成员*/
+    void append_session(session_ptr ssp) {
+        std::unique_lock<std::mutex> lock(_mutex);
+        _sessions.insert(make_pair(ssp->get_ssid(), ssp));  // _sessions[ssp->get_ssid()] = ssp;
+    }
+
+    /*设置session过期时间(毫秒)*/
+    /*基于websocketpp定时器(timer_ptr)来完成对session生命周期的管理*/
+    void set_session_expire_time(uint64_t ssid, int ms) {
+        //当客户端与服务器建立http短连接通信(登录/注册)时,session应该是临时的,需要设置定时删除任务
+        //当客户端与服务器建立websocket长连接通信(游戏大厅/游戏房间)时,session应该是永久的,直到websocket长连接断开
+        session_ptr ssp = get_session_by_ssid(ssid);
+        if(ssp.get() == nullptr) return;
+        // 获取session状态 -- session对象创建时默认没有关联time_ptr,此时session是永久存在的(timer_ptr==nullptr)
+        wsserver_t::timer_ptr tp = ssp->get_timer();
+        // 1. 在session永久的情况下设置永久
+        if(tp.get() == nullptr && ms == SESSION_FOREVER) return;
+        // 2. 在session永久的情况下设置定时删除任务
+        else if(tp.get() == nullptr && ms != SESSION_FOREVER) {
+            wsserver_t::timer_ptr tp_task = _server->set_timer(ms, std::bind(&session_manager::remove_session, this, ssid));
+            ssp->set_timer(tp_task);  // 重新设置session关联的定时器
+        }
+        // 3. 在session定时删除的情况下设置永久(删除定时任务)
+        else if(tp.get() != nullptr && ms == SESSION_FOREVER) {
+            // 注意:websocketpp使用cancel函数删除定时任务会导致定时任务直接被执行,所以我们需要重新向_sessions中添加ssid与session_ptr
+            // 同时,由于这个定时任务不是立即被执行的(服务器处理时才处理这个任务),所以我们不能在cancel函数后面直接重新添加session_ptr(这样可能出现先添加、再删除的情况)
+            // 而是需要专门设置一个定时器来添加ssid与session_ptr
+            tp->cancel();
+            // 通过定时器来添加被删除的_sessions成员
+            _server->set_timer(0, std::bind(&session_manager::append_session, this, ssp)); 
+            ssp->set_timer(wsserver_t::timer_ptr());  // 将session关联的定时器设置为空(session永久有效)
+        }
+        // 4. 在session定时删除的情况下重置删除时间
+        else {
+            // 先删除定时任务
+            tp->cancel();
+            _server->set_timer(0, std::bind(&session_manager::append_session, this, ssp)); 
+            ssp->set_timer(wsserver_t::timer_ptr());  // 将session关联的定时器设置为空(session永久有效)
+            // 再重新添加定时任务
+            wsserver_t::timer_ptr tp_task = _server->set_timer(ms, std::bind(&session_manager::remove_session, this, ssid));
+            ssp->set_timer(tp_task);  // 重新设置session关联的定时器
+        }
+    }
+private:
+    uint64_t _next_ssid;     // sessionID计数器                             
+    std::mutex _mutex;  
+    std::unordered_map<uint64_t, session_ptr> _sessions;  // 建立ssid与session信息之间的关联关系
+    wsserver_t *_server;  // 服务器指针对象,用于设置定时任务
+};
+#endif

二進制
Online backgammon battles/source/test


+ 161 - 0
Online backgammon battles/source/test.cc

@@ -0,0 +1,161 @@
+#include "logger.hpp"
+#include "util.hpp"
+#include "db.hpp"
+#include "online.hpp"
+#include "room.hpp"
+#include "session.hpp"
+#include "matcher.hpp"
+using std::cout;
+using std::endl;
+
+#define HOST "127.0.0.1"
+#define USER "thj"
+#define PASSWD "Abcd1234@"
+#define DB "test_connection"
+#define PORT 4106
+#define FILENAME "./makefile"
+
+void log_test()
+{
+    LOG(NORMAL, "%s-%d", "hello", 10);
+    LOG(DEBUG, "hello world");
+}
+
+void mysql_test()
+{
+    MYSQL *mysql = mysql_util::mysql_create(HOST, USER, PASSWD, DB, PORT);
+    const std::string sql = "insert into user values(null, '王五', 25, '11133334444')";
+    mysql_util::mysql_execute(mysql, sql);
+    mysql_util::mysql_destroy(mysql);
+}
+
+void json_test()
+{
+    Json::Value root;
+    root["姓名"] = "小明";
+    root["年龄"] = 18;
+    root["成绩"].append(80);  
+    root["成绩"].append(90);
+    root["成绩"].append(100);
+
+    std::string str;
+    json_util::serialize(root, str);
+    cout << str << endl;
+
+    Json::Value newroot;
+    json_util::deserialize(str, newroot);
+    cout << "姓名: " << newroot["姓名"].asString() << endl;
+    cout << "年龄: " << newroot["年龄"].asInt() << endl; 
+    int size = newroot["成绩"].size();
+    for(int i = 0; i < size; i++)
+    {
+        cout << "成绩: " << newroot["成绩"][i].asFloat() << endl;
+    }
+}
+
+void string_test()
+{
+    std::string src = "  abc  dfef fe";
+    std::string sep = " ";
+    std::vector<std::string> res;
+    string_util::split(src, sep, res);
+    for(auto s : res)
+        cout << s << endl;
+    cout << endl;
+}
+
+void fileRead_test()
+{
+    std::string data;
+    file_util::read(FILENAME, data);
+    cout << data << endl;
+}
+
+void db_test()
+{
+    user_table ut(HOST, USER, PASSWD);
+    Json::Value user;
+    user["username"] = "zhangsan";
+    user["password"] = "123456";
+
+    // register
+    // ut.registers(user);
+    // user["username"] = "lisi";
+    // user["password"] = "123321";
+    // ut.registers(user);
+
+    // login
+    // ut.login(user);
+    // user["password"] = "123321";
+    // ut.login(user);
+
+    // win and lose
+    ut.win(1);
+    ut.lose(2);
+
+    // select
+    Json::Value data;
+    if(ut.select_by_name("zhangsan", data)) {
+        std::string msg;
+        json_util::serialize(data, msg);
+        LOG(DEBUG, "%s", msg.c_str());
+    }
+    if(ut.select_by_id(2, data)) {
+        std::string msg;
+        json_util::serialize(data, msg);
+        LOG(DEBUG, "%s", msg.c_str());
+    }
+}
+
+void online_test()
+{
+    uint64_t id = 1;
+    Json::Value user;
+    user_table ut(HOST, USER, PASSWD);
+    ut.select_by_id(id, user);
+    std::string name = user["username"].asString();
+
+    online_manager om;
+    wsserver_t::connection_ptr conn;
+    om.enter_game_hall(id, conn);
+    if(om.is_in_game_hall(id)) LOG(DEBUG, "%s enter game hall", name.c_str());
+    om.enter_game_room(id, conn);
+    if(om.is_in_game_room(id)) LOG(DEBUG, "%s enter game room", name.c_str());
+
+    om.exit_game_room(id);
+    if(om.is_in_game_room(id) == false) LOG(DEBUG, "%s exit game room", name.c_str());
+    om.exit_game_hall(id);
+    if(om.is_in_game_hall(id) == false) LOG(DEBUG, "%s exit game hall", name.c_str());
+}
+
+void room_test() {
+    // 游戏房间
+    user_table ut(HOST, USER, PASSWD);
+    online_manager om;
+    // room r(10, &ut, &om);
+    // 游戏房间管理
+    room_manager rm(&ut, &om);
+    room_ptr rp = rm.create_room(10, 20);
+}
+
+void matcher_test() {
+    user_table ut(HOST, USER, PASSWD);
+    online_manager om;
+    room_manager rm(&ut, &om);
+    matcher mt(&ut, &om, &rm);
+}
+
+int main()
+{
+    // log_test();
+    // mysql_test();
+    // json_test();
+    // string_test();
+    // fileRead_test();
+    // db_test();
+    // online_test();
+    // room_test();
+    matcher_test();
+
+    return 0;
+}

+ 135 - 0
Online backgammon battles/source/util.hpp

@@ -0,0 +1,135 @@
+#ifndef __UTIL_HPP__
+#define __UTIL_HPP__
+#include "logger.hpp"
+#include <iostream>
+#include <string>
+#include <vector>
+#include <memory>
+#include <fstream>
+#include <sstream>
+#include <mysql/mysql.h>
+#include <jsoncpp/json/json.h>
+
+/*MySQL C API工具类*/
+class mysql_util {
+public:
+    static MYSQL *mysql_create(const std::string &host, const std::string &user, const std::string &passwd, \
+                               const std::string db = "gobang", uint16_t port = 4106) {
+        /*初始化MYSQL句柄*/
+        MYSQL *mysql = mysql_init(nullptr);
+        if(mysql == nullptr) {
+            LOG(FATAL, "mysql init failed");
+            return nullptr;
+        }
+        /*连接MySQL数据库*/
+        mysql = mysql_real_connect(mysql, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0);
+        if(mysql == nullptr) {
+            LOG(FATAL, "mysql connect failed: %s", mysql_error(mysql));
+            mysql_close(mysql);
+            return nullptr;
+        }
+        /*设置客户端字符集*/
+        if(mysql_set_character_set(mysql, "utf8") != 0) {
+            LOG(ERROR, "client character set failed: %s", mysql_error(mysql));
+        }
+        return mysql;
+    }
+
+    static bool mysql_execute(MYSQL *mysql, const std::string &sql) {
+        if(mysql_query(mysql, sql.c_str()) != 0) {
+            LOG(ERROR, "sql query failed: %s", mysql_error(mysql));
+            return false;
+        }
+        return true;
+    }
+
+    static void mysql_destroy(MYSQL *mysql) {
+        if(mysql != nullptr) {
+            mysql_close(mysql);
+        }
+    }
+};
+
+/*jsoncpp工具类*/
+class json_util {
+public:
+    static bool serialize(Json::Value &root, std::string &str) {
+        Json::StreamWriterBuilder swb;
+        std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
+        std::stringstream ss;
+        if(sw->write(root, &ss) != 0) {
+            LOG(ERROR, "json serialize failed");
+            return false;
+        }
+        str = ss.str();
+        return true;
+    }
+
+    static bool deserialize(const std::string &str, Json::Value &root) {
+        Json::CharReaderBuilder crb;
+        std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
+        std::string err;
+        if(cr->parse(str.c_str(), str.c_str() + str.size(), &root, &err) == false) {
+            LOG(ERROR, "json deserialize failed: %s", err);
+            return false;
+        }
+        return true;
+    }
+};
+
+/*字符串处理工具类*/
+class string_util {
+public:
+    /*将源字符串按照特定分隔符分割为若干个子字符串*/
+    static int split(const std::string &src, const std::string &sep, std::vector<std::string> &res) {
+        // ..abc..de..ef
+        int index = 0, pos = 0;
+        while(index < src.size()) {
+            pos = src.find(sep, index);
+            if(pos == std::string::npos) {
+                res.push_back(src.substr(index));
+                break;
+            }
+            if(index == pos) {
+                index += sep.size();
+                continue;
+            }
+            else {
+                res.push_back(src.substr(index, pos - index));
+                index = pos + sep.size();
+            }
+        }
+        return res.size();
+    }
+};
+
+/*读取文件数据工具类*/
+class file_util {
+public:
+    static bool read(const std::string &filename, std::string &data) {
+        /*以二进制形式打开文件*/
+        std::ifstream ifs(filename, std::ios::binary);
+        if(ifs.is_open() == false) {
+            LOG(ERROR, "open %s file failed", filename);
+            return false;
+        }
+        /*获取文件大小*/
+        size_t size;
+        ifs.seekg(0, std::ios::end);
+        size = ifs.tellg();
+        ifs.seekg(0, std::ios::beg);
+        /*读取文件内容*/
+        data.resize(size);
+        ifs.read(&data[0], size);
+        if(ifs.good() == false) {
+            LOG(ERROR, "read %s file content failed", filename);
+            ifs.close();
+            return false;
+        }
+        /*关闭文件*/
+        ifs.close();
+        return true;
+    }
+};
+
+#endif