在線五子棋對決
- 一. 項目介紹及鏈接
- 二. 項目結構設計
- 項目模塊劃分
- 業務處理模塊的子模塊劃分
- 項目流程圖
- 玩家流程圖
- 服務器流程圖
- 三. 數據管理模塊
- 數據庫設計
- 創建 user_table 類
- 四. 在線用戶管理模塊
- 五. 游戲房間管理模塊
- 游戲房間類實現
- 游戲房間管理類實現
- 六. Session 管理模塊
- Session 類實現
- Session 管理類實現
- 七. 五子棋對戰玩家匹配管理模塊
- 匹配隊列類實現
- 匹配隊列管理類實現
- 八. 整合封裝服務器模塊
- 通信接口設計 (Restful風格)
- 靜態資源請求
- 注冊用戶請求
- 用戶登錄請求
- 獲取用戶信息請求
- 切換 WebSocket 通信協議請求 (進入游戲大廳)
- 對戰匹配請求
- 停止對戰匹配請求
- 切換 WebSocket 通信協議請求 (進入游戲房間)
- 下棋請求
- 聊天請求
- 服務器類實現
- 九. 客戶端開發
- 注冊頁面:register.html
- 登入頁面:login.html
- 游戲大廳頁面:game_hall.html
- 游戲房間頁面:game_room.html
一. 項目介紹及鏈接
本項目主要實現一個網頁版的五子棋對戰游戲,其主要支持以下核心功能:
- 用戶管理:實現用戶注冊,用戶登錄、獲取用戶信息、用戶天梯分數記錄、用戶比賽場次記錄等。
- 匹配對戰:實現兩個玩家在網頁端根據天梯分數匹配游戲對手,并進行五子棋游戲對戰的功能。
- 聊天功能:實現兩個玩家在下棋的同時可以進行實時聊天的功能。
二. 項目結構設計
項目模塊劃分
項目模塊劃分三個大模塊來進行:
- 數據管理模塊:基于 MySQL 數據庫進行用戶數據的管理。
- 前端界面模塊:基于 JS 實現前端頁面 (注冊,登錄,游戲大廳,游戲房間) 的動態控制以及與服務器的通信。
- 業務處理模塊:搭建 WebSocket 服務器與客戶端進行通信,接收請求并進行業務處理。
項目要實現的是一個在線五子棋對戰服務器,提供用戶通過瀏覽器進行用戶注冊,登錄,以及實時匹配,對戰,聊天等功能。而如果要實現這些功能,那么就需要對業務處理模塊再次進行細分為多個模塊來實現各個功能。
業務處理模塊的子模塊劃分
- 網絡通信模塊:基于 websocketpp 庫實現 HTTP 和 WebSocket 服務器的搭建,提供網絡通信功能。
- 會話管理模塊:對客戶端的連接進行 Cookie 和 Session管理,實現 HTTP 短連接時客戶端身份識別功能。
- 在線管理模塊:對進入游戲大廳與游戲房間中用戶進行管理,提供用戶是否在線以及獲取用戶連接的功能。
- 房間管理模塊:為匹配成功的用戶創建對戰房間,提供實時的五子棋對戰與聊天業務功能。
- 用戶匹配模塊:根據天梯分數不同進行不同層次的玩家匹配,為匹配成功的玩家創建房間并加入房間。
項目流程圖
玩家流程圖
服務器流程圖
三. 數據管理模塊
數據管理模塊主要負責對于數據庫中數據進行統一的增刪改查管理,其他模塊要對數據操作都必須通過數據管理模塊完成。
數據庫設計
創建 user 表,用來表示用戶信息及積分信息。
- 用戶信息:實現登錄、注冊、游戲對戰數據管理等功能 (用戶 id,用戶名,密碼,總場數,勝場數)
- 積分信息:實現匹配功能 (游戲分數)
drop database if exists gobang;
create database if not exists gobang;
use gobang;
create table if not exists user(id int primary key auto_increment,username varchar(32) unique key not null,password varchar(128) not null,score int,total_count int,win_count int
);
驗證數據庫是否創建成功:
創建 user_table 類
數據庫中有可能存在很多張表,每張表中管理的數據又有不同,要進行的數據操作也各不相同,因此我們可以為每一張表中的數據操作都設計一個類,通過類實例化的對象來訪問這張數據庫表中的數據,這樣的話當我們要訪問哪張表的時候,使用哪個類實例化的對象即可。
創建 user_table 類,該類的作用是負責通過 MySQL 接口管理用戶數據,主要提供了四個方法:
- select_by_name:根據用戶名查找用戶信息,用于實現登錄功能。
- insert:新增用戶,用戶實現注冊功能。
- login:登錄驗證,并獲取完整的用戶信息。
- win:用于給獲勝玩家修改分數。
- lose:用戶給失敗玩家修改分數。
位于 db.hpp 文件
存在密碼不滿足要求,也就是密碼太簡單了,需要設置密碼等級,如下:
四. 在線用戶管理模塊
- 管理的兩類用戶:是對于 “進入游戲大廳” 和 “進入游戲房間” 的用戶。
- 原因:進入游戲大廳和進入游戲房間的用戶才會建立 WebSocket 長連接。
- 管理:將用戶 id 和對應的客戶端 WebSocket 長鏈接關聯起來。
- 作用:當一個用戶發送了消息 (實時聊天/下棋消息),可以找房間中的其它用戶,在在線用戶管理模塊中,找到這個用戶對應的 WebSocket 長連接,然后將消息發送給指定的用戶,模塊的兩個功能如下:
- 通過用戶 id 找到用戶的 WebSocket 連接進而實現向指定用戶的客戶端推送消息,WebSocket 連接關閉時,會自動在在線用戶管理模塊中刪除自己的信息。
- 可以通過在線用戶管理模塊判斷一個用戶是否在線,或者判斷用戶是否已經掉線。
位于 online.hpp 文件
五. 游戲房間管理模塊
游戲房間類實現
- 對匹配成功的玩家創建房間,建立起一個小范圍的玩家之間的關聯關系,房間里的一個玩家產生的動作會廣播給房間里的其它用戶。
- 因為房間有可能會有很多,因此需要將這些房間管理起來以便于對于房間生命周期的控制。
- 房間中產生的動作:下棋和聊天都要廣播給房間里的其它用戶。
- 實現的兩個部分:房間的設計、房間管理的設計。
- 房間的設計:房間的ID、房間的狀態 (正在游戲/游戲結束,決定玩家退出時誰勝利)、房間中用戶的數量 (決定了房間什么時候銷毀)、白棋玩家ID、黑棋玩家ID、用戶信息表的句柄 (當玩家勝利/失敗時更新用戶數據)、棋盤信息 (二維數組)、在線用戶的管理句柄 (廣播數據時需要)
位于 room.hpp 文件
游戲房間管理類實現
需要管理的數據:
- 數據管理模塊句柄。
- 在線用戶管理模塊句柄。
- 房間 ID 分配計數器。
- 用戶 ID 映射房間 ID 的哈希表。
- 房間 ID 映射房間信息的哈希表。
- 互斥鎖。
功能:
- 創建房間:兩個玩家對戰匹配完成了,為他們創建一個房間,需要傳入兩個玩家的用戶 ID
- 查找房間:通過房間 ID 查找房間信息,通過用戶 ID 查找所在房間信息。
- 銷毀房間:通過房間 ID 銷毀,房間中所有用戶都退出了,銷毀房間。
位于 room.hpp 文件
六. Session 管理模塊
在 Web 開發中,HTTP 協議是一種無狀態短鏈接的協議,這就導致一個客戶端連接到服務器上之后,服務器不知道當前的連接對應的是哪個用戶,也不知道客戶端是否登錄成功,這時候為客戶端提供所有服務是不合理的。第一次HTTP請求登入使用的是密碼,接下來的HTTP請求,由于無狀態短鏈接不知道用戶是誰,需要重新登入,這是不合理的。
因此,服務器為每個用戶瀏覽器創建一個會話對象 (Session 對象),注意:一個瀏覽器獨占一個 Session 對象 (默認情況下)。因此,在需要保存用戶數據時,服務器程序可以把用戶數據寫到用戶瀏覽器獨占的 Session 中,當用戶使用瀏覽器訪問其它程序時,其它程序可以從用戶的 Session 中取出該用戶的數據,識別該連接對應的用戶,并為用戶提供服務。
Session 工作原理:
使用 Cookie 和 Session 進行 HTTP 在短連接通信的情況下進行用戶狀態管理,但是這個服務器上管理的 Session 都會有過期時間,超過時間就會將對應的 Session 刪除,每次客戶端與服務器的通信都需要延長 Session 的過期時間。
Session 類實現
簡單設計 session 類,但是 session 對象不能一直存在,這是一種資源泄漏,因此需要使用定時器對每個創建的 session 對象進行定時銷毀 (一個客戶端連接斷開后,一段時間內都沒有重新連接則銷毀 session),成員對象如下:
- ssid:會話id
- uid:用戶id
- status:用戶狀態 (登入/未登入)
- timer_ptr:保存當前 session 對應的定時銷毀任務。
位于 session.hpp 文件
Session 管理類實現
session 管理:
- 創建一個新的 session
- 通過 ssid 獲取 session
- 通過 ssid 判斷 session 是否存在。
- 銷毀 session
- 為 session 設置過期時間,過期后 session 被銷毀。
位于 session.hpp 文件
七. 五子棋對戰玩家匹配管理模塊
匹配隊列類實現
五子棋對戰的玩家匹配是根據自己的天梯分數進行匹配,而服務器中將玩家天梯分數分為三個檔次:
- 青銅:天梯分數小于 2000 分。
- 白銀:天梯分數介于 2000~3000 分之間。
- 黃金:天梯分數大于 3000 分。
實現玩家匹配的思想非常簡單,為不同的檔次設計各自的匹配隊列,當一個隊列中的玩家數量大于等于2的時候,則意味著同一檔次中,有2個及以上的人要進行實戰匹配,則出隊隊列中的前兩個用戶,相當于隊首2個玩家匹配成功,這時候為其創建房間,并將兩個用戶信息加入房間中。
位于 matcher.hpp 文件
匹配隊列管理類實現
匹配管理:
- 三個不同玩家水平的隊列。
- 三個線程分別對三個隊列中的玩家進行匹配。
- 房間管理模塊的句柄:為匹配成功的玩家創建房間。
- 在線用戶管理模塊句柄:為匹配成功的玩家添加到在線用戶管理中。
- 數據庫管理模塊用戶表句柄:通過玩家 id 查看玩家的天梯分數,進行玩家匹配。
功能接口:
- 添加用戶到匹配隊列。
- 從匹配隊列移除玩家。
- 線程入口函數:判斷指定隊列人數是否大于2,出隊兩個玩家,創建房間,將兩個玩家添加到房間中,向兩個玩家發送對戰匹配成功的消息。
位于 matcher.hpp 文件
八. 整合封裝服務器模塊
通信接口設計 (Restful風格)
靜態資源請求
- 靜態資源頁面:在后臺服務器上就是個 html/css/js 文件。
- 注冊頁面請求。
- 登入頁面請求。
- 游戲大廳頁面請求。
- 游戲房間頁面請求。
- 靜態資源請求的處理:就是將文件中的內容發送給客戶端。
# 注冊頁面請求
請求:GET /register.html HTTP/1.1
響應:
HTTP/1.1 200 OK
Content-Length: xxx
Content-Type: text/htmlregister.html文件內容數據# 登錄頁面請求
請求:GET /login.html HTTP/1.1
# 大廳頁面請求
請求:GET /game_hall.html HTTP/1.1
# 房間頁面請求
請求:GET /game_room.html HTTP/1.1
注冊用戶請求
// 請求
POST /reg HTTP/1.1
Content-Type: application/json{"username":"zhangsan", "password":"123456"}// 成功時的響應
HTTP/1.1 200 OK
Content-Type: application/json{"result":true, "reason": "注冊用戶成功"}// 失敗時的響應
HTTP/1.1 400 Bad Request
Content-Type: application/json{"result":false, "reason": "用戶名已經被占用"}
用戶登錄請求
// 請求
POST /login HTTP/1.1
Content-Type: application/json{"username":"zhangsan", "password":"123456"}// 成功時的響應
HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: 123456{"result":true, "reason": "登入成功"}// 失敗時的響應
HTTP/1.1 400 Bad Request
Content-Type: application/json{"result":false, "reason": "用戶名/密碼錯誤"}
獲取用戶信息請求
// 請求
POST /info HTTP/1.1
Content-Type: application/json// 成功時的響應
HTTP/1.1 200 OK
Content-Type: application/json{"username":"xzy", "score":1000, "total_count":4, "win_count":2}// 失敗時的響應
HTTP/1.1 400 Bad Request
Content-Type: application/json{"result":false, "reason": "找不到用戶信息信息, 請重新登入"}
切換 WebSocket 通信協議請求 (進入游戲大廳)
// 成功響應
{"optype": "hall_ready","result": true;
}
// 失敗響應
{"optype": "hall_ready","reason": "玩家重復登入""result": false;
}
對戰匹配請求
// 客戶端:開始匹配玩家的信息
{"optype": "match_start","uid": 1
}// 服務器:開始匹配玩家正確的信息
{"optype": "match_start","result": true
}
// 服務器:開始匹配玩家錯誤的信息
{"optype": "match_start","result": false,"reason": "具體原因...."
}// 服務器:匹配玩家成功的信息
{"optype": "match_success","result": true
}
停止對戰匹配請求
// 客戶端:停止匹配玩家的信息
{"optype": "match_stop""uid": 1
}// 服務器:停止匹配玩家正確的信息
{"optype": "match_stop","result": true
}
// 服務器:停止匹配玩家錯誤的信息
{"optype": "match_stop","result": false,"reason": "具體原因...."
}
切換 WebSocket 通信協議請求 (進入游戲房間)
// 成功響應
{"optype": "room_ready","room_id": 222,"uid": 1,"white_id": 1,"black_id": 2,"result": true;
}
// 失敗響應
{"optype": "room_ready","reason": "玩家重復登入""result": false;
}
下棋請求
// 請求
{"optype": "put_chess","room_id": 222,"uid": 1, // 下棋用戶的id"row": 3,"col": 2
}// 失敗響應
{"optype": "put_chess","result": false"reason": "走棋失敗具體原因...."
}
// 成功響應
{"optype": "put_chess","room_id": 222,"uid": 1, // 下棋用戶的id"row": 3,"col": 2,"result": true,"reason": "五星連珠,戰無敵","winner": 0 // 0 代表沒有分出勝負,具體的數字代表獲勝用戶的id
}
聊天請求
// 請求
{"optype": "chat","room_id": 222,"uid": 1,"message": "快點吧,等的花都謝了"
}// 失敗響應
{"optype": "chat","result": false"reason": "消息中包含敏感詞匯,無法發送"
}
// 成功響應
{"optype": "chat","result": true,"room_id": 222,"uid": 1,"message": "快點吧,等的花都謝了"
}
服務器類實現
服務器模塊:對當前所實現的所有模塊的一個整合,并進行服務器搭建的一個模塊,最終封裝實現出一個 gobang_server 的服務器模塊類,向外提供搭建五子棋對戰服務器的接口,通過實例化的對象可以簡便的完成服務器的搭建。
服務器的整合實現:
- 網絡通信接口的設計:
- 收到一個什么格式的數據,代表了什么樣的請求,應該給與什么樣的業務處理以及響應。
- 開始搭建服務器:
- 搭建 websocket 服務器,實現網絡通信。
- 針對各種不同的請求進行不同的業務處理。
客戶端所存在的請求:
- 客戶端從服務器獲取一個 “注冊” 頁面。
- 客戶端給服務器發送一個 “注冊” 請求 (提交用戶名和密碼)
- 客戶端從服務器獲取一個 “登入” 頁面。
- 客戶端給服務器發送一個 “登入” 請求 (提交用戶名和密碼)
- 客戶端從服務器獲取一個 “游戲大廳” 頁面。
- 客戶端給服務器發送一個 “獲取個人信息” 請求 (展示個人信息)
- 客戶端給服務器發送一個 “切換 WebSocket 通信協議” 的請求 (建立游戲大廳長連接)
- 客戶端給服務器發送一個 “對戰匹配” 的請求 (匹配玩家)
- 客戶端給服務器發送一個 “停止對戰匹配” 的請求 (停止匹配玩家)
- 客戶端從服務器獲取一個 “游戲房間” 頁面。
- 客戶端給服務器發送一個 “切換 WebSocket 通信協議” 的請求 (建立游戲房間長連接)
- 客戶端給服務器發送一個 “下棋” 的請求 (下棋)
- 客戶端給服務器發送一個 “聊天” 的請求 (聊天)
- 客戶端從服務器獲取一個 “游戲大廳” 頁面 (游戲結束返回游戲大廳)
九. 客戶端開發
注冊頁面:register.html
通過 43.143.27.66:8080/register.html 訪問該網頁時,返回給客戶端一個注冊界面,用戶輸入用戶名密碼后點擊按鈕,自動通過 ajax 向后臺發送用戶注冊請求,并將用戶名和密碼存儲到數據庫中,最后直接跳轉到登入頁面。
登入頁面:login.html
通過 43.143.27.66:8080/login.html 訪問該網頁時,返回給客戶端一個登入界面,用戶輸入用戶名密碼后點擊按鈕,自動通過 ajax 向后臺發送用戶登入請求,并直接跳轉到游戲大廳頁面。
游戲大廳頁面:game_hall.html
進入游戲大廳 websocket 長連接頁面后,通過點擊開始匹配按鈕,網頁向服務器發送對戰匹配請求,再次點擊按鈕,網頁向服務器發送取消對戰匹配請求,當存在兩個人匹配對戰的話,則玩家匹配成功,直接跳轉到游戲房間頁面。
游戲房間頁面:game_room.html
進入游戲房間 websocket 長連接頁面后,就可以開始實時下棋和聊天了。