百萬在線:大型遊戲服務端開發

羅培羽

買這商品的人也買了...

商品描述

第1部分“學以致用”揭示了 Skynet 引擎的使用方法及注意事項,以“球球大作戰”的案例貫穿本篇內容,
全面又詳盡地剖析服務端結構設計、通信協議格式、數據表結構設計、斷線重連方案等眾多核心技術。
第二部分“入木三分”揭示了在多核時代採用現代 C++ 編寫多線程 TCP 網絡服務器的高效做法,
以 C++ 重寫 Skynet 的案例貫穿本篇內容,使用大量圖表,生動翔實地描述 Linux 環境下的編程技術。
第三部分“各個擊破”列舉了同步算法、熱更新、
防外掛等實際工程難題,並對其逐一擊破,非常具有實用價值。

作者簡介

羅培羽,現就職於廣州四三九九公司技術研發中心,參與遊戲研發和技術研究工作。
曾組織“民間團隊”開發同人遊戲《仙劍奇俠傳5前傳之心願》,是多年來“民間團隊”製作3D仙劍同人遊戲的一次成功嘗試。
著有《手把手教你用C#製作RPG遊戲》《Unity3D網絡遊戲實戰》等計算機圖書,所著圖書被部分高校的軟件工程專業選為教材。

目錄大綱

前 言
第一部分 學以致用
第1章 從角色走路說起 2
1.1 每走一步都有事情發生 2
1.1.1 走路的五個步驟 3
1.1.2 服務端視角的遊戲流程 3
1.2 從網絡編程著手 4
1.2.1 用打電話做比喻 4
1.2.2 最少要掌握的三個概念 5
1.2.3 搭一個簡單的服務器 6
1.2.4 讓角色走起來 8
1.3 能夠承載多少玩家 9
1.3.1 單線事件模型 9
1.3.2 承載量估算 9
1.4 用分佈式擴能 10
1.4.1 多個程序協同工作 11
1.4.2 三個層次的交互 11
1.4.3 搭個簡單的分佈式服務端 12
1.4.4 一致性問題 14
1.5 回頭看操作系統 15
1.5.1 多進程為什麼能提升性能 15
1.5.2 阻塞為什麼不佔CPU 16
1.5.3 線程會佔用多少資源 17
1.6 一張地圖的極限 18
1.6.1 難以分割的業務 19
1.6.2 在延遲和容量間權衡 19
1.7 萬物皆Actor 19
1.7.1 靈感來自Erlang 20
1.7.2 對世界的抽象 20
1.7.3 為何適用 22

第2章 Skynet入門精要 25
2.1 下載、編譯、運行 26
2.1.1 下載和編譯 26
2.1.2 運行範例 27
2.2 理解Skynet 28
2.2.1 節點和服務 28
2.2.2 配置文件 29
2.2.3 目錄結構 30
2.2.4 啟動流程 31
2.3 第一個程序PingPong 32
2.3.1 功能需求 32
2.3.2 學習服務模塊 32
2.3.3 代碼實現 33
2.3.4 運行結果 35
2.4 寫Echo,練習網絡編程 35
2.4.1 功能需求 36
2.4.2 學習網絡模塊 36
2.4.3 代碼實現 37
2.4.4 運行結果 37
2.5 做聊天室,學習多人交互 38
2.5.1 功能需求 38
2.5.2 代碼實現 38
2.6 做留言板,使用數據庫 39
2.6.1 功能需求 39
2.6.2 學習數據庫模塊 40
2.6.3 準備數據庫 40
2.6.4 代碼實現 43
2.6.5 運行結果 45
2.7 監控服務狀態 45
2.7.1 啟用調試控制台 45
2.7.2 監控指令 46
2.8 使用節點集群建立分佈式系統 48
2.8.1 功能需求 48
2.8.2 學習集群模塊 48
2.8.3 節點配置 49
2.8.4 代碼實現 49
2.8.5 運行結果 51
2.8.6 使用代理 52
2.9 使用Skynet的注意事項 52
2.9.1 協程的作用 52
2.9.2 扣除金幣的Bug 52

第3章 案例:《球球大作戰》 54
3.1 功能需求 54
3.2 方案設計 55
3.2.1 拓撲結構 55
3.2.2 各服務功能 56
3.2.3 消息流程 57
3.2.4 設計要點 57
3.3 搭架子:目錄結構和配置 58
3.3.1 目錄結構 58
3.3.2 配置文件 58
3.3.3 第1版主服務 59
3.3.4 啟動腳本 60
3.3.5 服務配置 60
3.4 磨刀工:封裝易用的API 62
3.4.1 定義屬性 63
3.4.2 啟動邏輯 63
3.4.3 消息分發 64
3.4.4 輔助方法 65
3.4.5 編寫空服務 66
3.5 分佈式登錄流程 67
3.5.1 完整的登錄流程 67
3.5.2 掉線登出流程 69
3.5.3 協議格式 69
3.6 代碼實現:gateway 70
3.6.1 連接類和玩家類 70
3.6.2 接收客戶端連接 71
3.6.3 處理客戶端協議 74
3.6.4 編碼和解碼 75
3.6.5 消息分發 76
3.6.6 發送消息接口 78
3.6.7 確認登錄接口 79
3.6.8 登出流程 80
3.7 代碼實現:login 81
3.7.1 登錄協議 81
3.7.2 客戶端消息分發 81
3.7.3 登錄流程處理 82
3.8 代碼實現:agentmgr 83
3.8.1 玩家類 83
3.8.2 請求登錄接口 84
3.8.3 請求登出接口 86
3.9 代碼實現:nodemgr 86
3.10 代碼實現:agent(單機版) 87
3.10.1 消息分發 87
3.10.2 數據加載 87
3.10.3 保存和退出 88
3.10.4 單機測試 88
3.11 測試登錄流程 89
3.11.1 第2版主服務 89
3.11.2 單節點測試 90
3.11.3 跨節點測試 90
3.12 戰鬥流程梳理 91
3.12.1 戰鬥流程 91
3.12.2 協議 91
3.13 代碼實現:場景服務 93
3.13.1 Ball類 93
3.13.2 Food類 94
3.13.3 進入戰鬥 95
3.13.4 退出戰鬥 97
3.13.5 操作移動 97
3.13.6 主循環 97
3.13.7 移動邏輯 99
3.13.8 生成食物 99
3.13.9 吞下食物 100
3.13.10 第3版主服務 101
3.14 代碼實現:agent(跨服務器版) 101
3.14.1 多個模塊 101
3.14.2 進入戰鬥 101
3.14.3 退出戰鬥 103
3.14.4 最後的輔助方法 103
3.14.5 運行結果 104
3.15 改進 104

第4章 Skynet進階技法 106
4.1 用“長度信息”解TCP包 107
4.1.1 長度信息法 107
4.1.2 使用netpack模塊解析網絡包 107
4.1.3 測試小案例 110
4.1.4 阻塞方法的時序 113
4.2 用Json序列化協議 114
4.2.1 安裝lua-cjson模塊 115
4.2.2 使用lua-cjson模塊 115
4.2.3 設計完整協議格式 117
4.2.4 編碼Json協議 117
4.2.5 解碼Json協議 118
4.2.6 測試 118
4.3 用Protobuf高效傳輸 119
4.3.1 什麼是Protobuf 120
4.3.2 安裝Protobuf和pbc 120
4.3.3 編譯proto文件 121
4.3.4 編碼和解碼 121
4.4 如何設計遊戲數據庫 122
4.4.1 傳統設計方法 123
4.4.2 傳統的數據庫難以應對版本更新 124
4.4.3 Key-Value表結構 125
4.4.4 用Protobuf描述玩家數據 126
4.4.5 創建角色 126
4.4.6 讀取角色數據 127
4.4.7 應對遊戲版本更新 128
4.4.8 拆分數據表 129
4.5 如何關閉服務器 130
4.5.1 管理控制台 130
4.5.2 關閉服務器的流程 131
4.5.3 阻止新玩家連入 132
4.5.4 緩緩踢下線 133
4.5.5 測試關閉服務器的功能 135
4.6 怎樣做定時系統 136
4.6.1 每天第一次登錄 136
4.6.2 定時喚醒 137
4.7 斷線重連 138
4.7.1 原理解析 139
4.7.2 身份標識 139
4.7.3 消息緩存 140
4.7.4 處理重連請求 141
4.7.5 斷線處理 142
4.7.6 測試 143

第二部分 入木三分
第5章 你好,C 並發世界 146
5.1 從HelloWorld開始 147
5.1.1 HelloWorld 147
5.1.2 用CMake構建工程 147
5.1.3 “學貓叫”小例子 149
5.1.4 各文件的依賴關係 151
5.1.5 模仿Skynet寫底層 151
5.2 多核多線程 153
5.2.1 操作系統調度原理 153
5.2.2 創建線程對象 154
5.2.3 模仿Skynet開啟線程 155
5.2.4 等待線程退出 158
5.2.5 Worker設計模式 159
5.3 探索C 對像模型 160
5.3.1 Actor模型的消息類 160
5.3.2 棧、堆、智能指針 161
5.3.3 對象的內存分佈 162
5.4 隊列與鎖 162
5.4.1 模仿Skynet寫服務類 163
5.4.2 鎖的初始化 164
5.4.3 多線程隊列插入 166
5.4.4 在多線程隊列取出元素 166
5.4.5 三個回調方法 168
5.4.6 分析臨界區 168
5.5 多線程下的對像管理 170
5.5.1 使用哈希表 170
5.5.2 淺析讀寫鎖 171
5.5.3 新建服務 172
5.5.4 查找服務 172
5.5.5 刪除服務 173
5.5.6 程序終於能運行了 174
5.6 充分利用CPU 175
5.6.1 全局消息隊列 175
5.6.2 插入和彈出 177
5.6.3 標誌位 178
5.6.4 模仿Skynet發送消息 179
5.6.5 工作線程調度 180
5.7 演示程序PingPong 183
5.7.1 輔助函數 183
5.7.2 編寫ping服務 183
5.7.3 測試 184
5.8 條件變量與喚醒機制 185
5.8.1 改進版線程調度 185
5.8.2 如何使用條件變量 186
5.8.3 工作線程的等待與喚醒 188
5.8.4 測試 189
5.9 後台運行 189

第6章 圖解TCP網絡模塊 191
6.1 啟動網絡線程 192
6.1.1 方案設計 192
6.1.2 新增兩種消息 192
6.1.3 套接字到底是什麼 193
6.1.4 模仿Skynet的網絡線程 195
6.1.5 自定義連接類 197
6.2 半小時搞懂Epoll的用法 201
6.2.1 從輪詢說起 202
6.2.2 創建epoll對象 203
6.2.3 直觀理解監聽列表 204
6.2.4 最重要的步驟:等待 207
6.2.5 監聽對象的生命週期 210
6.2.6 水平觸發和邊緣觸發 211
6.3 打開監聽端口 212
6.3.1 三個API:socket、bind和listen 212
6.3.2 如何保障線程安全 215
6.3.3 關閉連接 215
6.3.4 測試:感知新連接 216
6.4 網絡事件分發 216
6.4.1 拆分事件 217
6.4.2 接收新客戶端 218
6.4.3 傳遞可讀寫事件 220
6.4.4 測試:Echo程序 220
6.5 如何安全讀寫數據 222
6.5.1 消息分支 222
6.5.2 可讀、可寫、關閉 224
6.5.3 處理莫名其妙退出的問題 226
6.5.4 PIPE信號處理 228
6.6 寫緩衝區滿 229
6.6.1 實驗:發送大數據 229
6.6.2 解決方法1:設置SNDBUFFORCE 230
6.6.3 解決方法2:自寫緩衝區 230

第7章 嵌入Lua腳本語言 234
7.1 方案設計 234
7.1.1 隔離數千個服務 235
7.1.2 目錄結構 235
7.1.3 啟動流程 235
7.2 嵌入Lua虛擬機 236
7.2.1 下載、編譯源碼 236
7.2.2 理解靜態庫 237
7.2.3 最重要的結構:lua_State 238
7.2.4 從創建到銷毀 239
7.2.5 編譯指令 240
7.3 C 調用Lua方法 241
7.3.1 代碼示例 241
7.3.2 涉及4個API 242
7.3.3 直觀理解Lua棧 243
7.3.4 再回顧調用方法 244
7.4 Lua調用C 函數 245
7.4.1 Sunnet的腳本模塊 245
7.4.2 寫接口 246
7.4.3 分析4個API 246
7.4.4 還需註冊函數 248
7.4.5 思考題 250
7.5 Lua版的PingPong 251
7.5.1 封裝兩個新接口 251
7.5.2 Lua版的ping服務 253
7.5.3 運行結果 254
7.5.4 參數序列化 255
7.6 Lua版聊天室 256
7.6.1 繼續封裝 256
7.6.2 Lua版的聊天服務 258
7.6.3 運行結果 258
7.6.4 拓展說明 259

第三部分 各個擊破
第8章 同步算法 262
8.1 同步難題 263
8.1.1 一種移動方法 263
8.1.2 瞬移、頓挫、打不中 264
8.1.3 抖動和延遲 265
8.2 客戶端障眼法 267
8.2.1 插值算法 267
8.2.2 緩存隊列 268
8.2.3 主動方優先 269
8.3 各類同步方案及適用場景 269
8.3.1 三種同步方案 270
8.3.2 適用場景 271
8.3.3 案例:《天涯明月刀》 271
8.3.4 案例:《王者榮耀》 272
8.3.5 案例:《絕地求生大逃殺》 273
8.4 幀同步 273
8.4.1 幀同步究竟是什麼 274
8.4.2 代碼範例 275
8.4.3 確定性計算 279
8.4.4 樂觀幀同步 280
8.5 AOI算法 282
8.5.1 感興趣區域 282
8.5.2 實體模型 283
8.5.3 九宮格法 284
8.6 可靠UDP 287
8.6.1 三角製約 287
8.6.2 幾種現成方案 288

第9章 熱更新 289
9.1 Skynet熱更新 290
9.1.1 業務範例 290
9.1.2 利用獨立虛擬機熱更新 292
9.1.3 注入補丁 294
9.2 切換進程 296
9.2.1 從冷更新說起 296
9.2.2 《跳一跳》遊戲案例 297
9.2.3 優雅的進程切換 298
9.2.4 fork和exec 298
9.2.5 Nginx的熱更新方法 299
9.2.6 利用網關實現熱更新 300
9.2.7 數據與邏輯分離 300
9.2.8 微服務 301
9.3 動態庫 302
9.3.1 模擬案例 302
9.3.2 實現動態庫熱更新 303
9.3.3 動態庫的限制 305
9.3.4 動態庫在遊戲中的應用 306
9.3.5 多線程與版本管理 306
9.4 腳本語言 308
9.4.1 業務需求 308
9.4.2 實現Lua熱更新 310
9.4.3 盡量做正確的熱更新 310
9.4.4 沒有“藥”的根源 311
9.4.5 工程實踐 313
9.4.6 選擇合適的熱更新範圍 316

第10章 防外掛 318
10.1 不信任客戶端 319
10.1.1 刷金幣案例 319
10.1.2 連發技能案例 320
10.1.3 透視外掛案例 322
10.2 盡可能多的校驗 324
10.2.1 小遊戲防刷榜 324
10.2.2 籃球遊戲案例 327
10.2.3 部署校驗服務 328
10.3 反外掛常用措施 329
10.3.1 防變速器 329
10.3.2 防封包工具 331
10.3.3 幀同步投票 332

第11章 未盡之路 333
11.1 高並發 333
11.1.1 Actor模型 333
11.1.2 One Loop Per Thread 334
11.2 服務端架構 334
11.2.1 大世界架構 334
11.2.2 BigWorld 334
11.2.3 無縫大地圖 335
11.2.4 滾服架構 336
11.3 工程管理 337
11.3.1 分層架構 337
11.3.2 人員分工 337
11.3.3 版本管理 338
11.4 結語 339