從零開始實現放置遊戲(十三)——實現戰鬥掛機(4)添加websocket組件
前兩張,我們已經實現了登陸介面和遊戲的主介面。不過遊戲主介面的數據都是在前端寫死的文本,本章我們給game模組添加websocket組件,實現前後端通訊,這樣,前端的數據就可以從後端動態獲取到了。
一、添加maven依賴
在game模組的pom中,我們添加3個依賴包如下:
1 <!-- websocket組件 --> 2 <dependency> 3 <groupId>org.springframework</groupId> 4 <artifactId>spring-websocket</artifactId> 5 <version>5.1.6.RELEASE</version> 6 </dependency> 7 <dependency> 8 <groupId>org.springframework</groupId> 9 <artifactId>spring-messaging</artifactId> 10 <version>5.1.6.RELEASE</version> 11 </dependency> 12 <dependency> 13 <groupId>javax.websocket</groupId> 14 <artifactId>javax.websocket-api</artifactId> 15 <version>1.1</version> 16 <scope>provided</scope> 17 </dependency>
二、後端添加MessageHub
在com.idlewow.game.hub下MessageHub,這個類將主要負責接收客戶端的websocket資訊。程式碼如下:


1 @Component 2 @ServerEndpoint(value = "/hub", configurator = HttpSessionConfigurator.class) 3 public class MessageHub { 4 private static final Logger logger = LogManager.getLogger(MessageHub.class); 5 6 @Autowired 7 MessageHandler messageHandler; 8 @Autowired 9 CharacterService characterService; 10 11 @OnOpen 12 public void onOpen(Session session, EndpointConfig config) { 13 logger.info("[websocket][" + session.getId() + "]建立連接"); 14 try { 15 HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getSimpleName()); 16 if (httpSession == null) { 17 logger.error("[websocket][" + session.getId() + "]獲取HttpSession失敗!"); 18 throw new Exception("獲取HttpSession失敗!"); 19 } 20 21 22 if (httpSession.getAttribute(GameWorld.SK_CharId) == null) { 23 logger.error("[websocket][" + session.getId() + "]獲取角色Id為空!"); 24 throw new Exception("獲取角色ID為空!"); 25 } 26 27 String charId = httpSession.getAttribute(GameWorld.SK_CharId).toString(); 28 CommonResult commonResult = characterService.find(charId); 29 if (commonResult.isSuccess()) { 30 Character character = (Character) commonResult.getData(); 31 /* 載入成功,添加快取 */ 32 GameWorld.OnlineSession.add(session); 33 GameWorld.OnlineCharacter.put(session.getId(), character); 34 GameWorld.MapCharacter.get(character.getMapId()).add(character); 35 } else { 36 logger.error("載入角色資訊失敗!charId:" + charId + " message:" + commonResult.getMessage()); 37 throw new Exception("載入角色資訊失敗!"); 38 } 39 } catch (Exception ex) { 40 logger.error("[websocket][" + session.getId() + "]建立連接異常:" + ex.getMessage(), ex); 41 this.closeSession(session, ex.getMessage()); 42 } 43 } 44 45 @OnMessage 46 public void onMessage(Session session, String message) { 47 logger.info("[websocket][" + session.getId() + "]接收消息:" + message); 48 messageHandler.handleMessage(session, message); 49 } 50 51 @OnClose 52 public void onClose(Session session) { 53 logger.info("[websocket][" + session.getId() + "]關閉連接"); 54 /* 清理快取 */ 55 Character character = GameWorld.OnlineCharacter.get(session.getId()); 56 GameWorld.OnlineSession.remove(session); 57 GameWorld.OnlineCharacter.remove(session.getId()); 58 GameWorld.MapCharacter.get(character.getMapId()).remove(character); 59 } 60 61 @OnError 62 public void onError(Session session, Throwable t) { 63 logger.error("[websocket][" + session.getId() + "]發生異常:" + t.getMessage(), t); 64 } 65 66 private void closeSession(Session session, String message) { 67 try { 68 logger.info("[websocket][" + session.getId() + "]關閉連接,原因:" + message); 69 CloseReason closeReason = new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, message); 70 session.close(closeReason); 71 } catch (Exception ex) { 72 logger.error("[websocket]關閉連接異常:" + ex.getMessage(), ex); 73 } 74 } 75 }
MessageHub
Hub類主要包括OnOpen、OnMessage、OnClose、OnError 4個方法。
在OnOpen建立連接時,我們從HttpSession中獲取角色Id,並載入角色資訊,更新在線數據等。這裡我們創建一個GameWorld類,將在線列表等遊戲世界的全局靜態數據保存在其中。
在OnMessage方法接收到客戶端數據時,我們將消息在MessageHandler中統一處理。
OnClose和OnError對應關閉連接和異常發生事件,關閉連接時,需要將遊戲角色從在線列表中清除。發生異常時,我們暫時僅記錄日誌。
注意:在MesssageHub的註解中,我們給其配置了一個HttpSessionConfigurator。是為了在socket消息中獲取到HttpSession數據。如果不加這個配置,HttpSession是獲取不到的。其程式碼如下:
1 public class HttpSessionConfigurator extends SpringConfigurator { 2 @Override 3 public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { 4 HttpSession httpSession = (HttpSession) request.getHttpSession(); 5 sec.getUserProperties().put(HttpSession.class.getSimpleName(), httpSession); 6 super.modifyHandshake(sec, request, response); 7 } 8 }
三、定義消息類型
在socket通訊時,我們必須定義消息的數據結構,並準備相應文檔,方便前後端通訊。
這裡我們創建消息類WowMessage,並規定其由header和content兩部分構成。header中主要包括消息類型,請求時間等通用參數。content主要包括具體的業務數據。
整個消息類的UML圖如下,其中例舉了4種具體的消息類型,LoadCache快取載入,Login登陸消息,Chat聊天消息,Move地圖移動消息。
四、後端消息處理
在定義好消息類型後,我們即可在後端對相應的消息進行處理。程式碼如下:
在handleMessage方法中,我們根據header中傳入的messageCode,來確定是何種消息,並轉入對應的處理子方法。
比如處理地圖移動的handleMoveMessage方法,在這個方法中,我們將人物資訊快取數據中的當前地圖ID修改為移動後的地圖ID,從原地圖在線列表中移除此角色,在目標地圖在線列表中添加此角色。並返回目標地圖的資訊給前端以便展示。


@Component public class MessageHandler { private static final Logger logger = LogManager.getLogger(MessageHandler.class); @Autowired CharacterService characterService; @Autowired WowMapService wowMapService; @Autowired MapMobService mapMobService; @Autowired MapCoordService mapCoordService; /** * 消息處理 * * @param session session * @param message 消息 */ public void handleMessage(Session session, String message) { WowMessage<?> wowMessage = JSONObject.parseObject(message, WowMessage.class); WowMessageHeader header = wowMessage.getHeader(); String messageCode = header.getMessageCode(); switch (messageCode) { case WowMessageCode.LoadCache: this.handleLoadCacheMessage(session, (WowMessage<LoadCacheRequest>) wowMessage); break; case WowMessageCode.RefreshOnline: this.handleRefreshOnlineMessage(session, (WowMessage<RefreshOnlineRequest>) wowMessage); break; case WowMessageCode.Login: this.handleLoginMessage(session, (WowMessage<LoginRequest>) wowMessage); break; case WowMessageCode.Chat: this.handleChatMessage(session, (WowMessage<ChatRequest>) wowMessage); break; case WowMessageCode.Move: this.handleMoveMessage(session, (WowMessage<MoveRequest>) wowMessage); break; default: break; } } /** * 給指定客戶端發送消息 * * @param session 客戶端session * @param message 消息內容 */ private void sendOne(Session session, String message) { try { session.getBasicRemote().sendText(message); } catch (Exception ex) { logger.error(ex.getMessage(), ex); } } /** * 給所有客戶端發送消息 * * @param message 消息內容 */ private void sendAll(String message) { try { for (Session session : GameWorld.OnlineSession) { session.getBasicRemote().sendText(message); } } catch (Exception ex) { logger.error(ex.getMessage(), ex); } } /** * 登陸載入 * * @param session session * @param message 消息 */ private void handleLoginMessage(Session session, WowMessage<LoginRequest> message) { WowMessageHeader header = message.getHeader(); header.setResponseTime(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss")); LoginResponse response = new LoginResponse(); Character character = GameWorld.OnlineCharacter.get(session.getId()); String mapId = character.getMapId(); MapInfo mapInfo = this.loadMapInfo(mapId); response.setMapInfo(mapInfo); OnlineInfo onlineInfo = this.loadOnlineInfo(mapId); response.setOnlineInfo(onlineInfo); WowMessage wowMessage = new WowMessage<>(header, response); this.sendOne(session, JSON.toJSONString(wowMessage)); } /** * 發送聊天 * * @param session session * @param message 消息 */ private void handleChatMessage(Session session, WowMessage<ChatRequest> message) { WowMessageHeader header = message.getHeader(); header.setResponseTime(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss")); ChatRequest request = message.getContent(); ChatResponse response = new ChatResponse(); response.setSendId(request.getSendId()); response.setSendName(request.getSendName()); response.setRecvId(request.getRecvId()); response.setRecvName(request.getRecvName()); response.setMessage(request.getMessage()); response.setChannel(request.getChannel()); WowMessage wowMessage = new WowMessage<>(header, response); if (request.getChannel().equals(GameWorld.ChatChannel.WORLD)) { this.sendAll(JSON.toJSONString(wowMessage)); } else if (request.getChannel().equals(GameWorld.ChatChannel.PRIVATE)) { // todo 發送消息給指定玩家 } else if (request.getChannel().equals(GameWorld.ChatChannel.LOCAL)) { // todo 發送消息給當前地圖玩家 } } /** * 載入快取 * * @param session session * @param message 消息 */ private void handleLoadCacheMessage(Session session, WowMessage<LoadCacheRequest> message) { WowMessageHeader header = message.getHeader(); header.setResponseTime(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss")); LoadCacheResponse response = new LoadCacheResponse(); Map<String, Integer> levelExpMap = new HashMap<>(); for (Integer key : CacheUtil.levelExpMap.keySet()) { levelExpMap.put(key.toString(), CacheUtil.levelExpMap.get(key)); } response.setLevelExpMap(levelExpMap); WowMessage wowMessage = new WowMessage<>(header, response); this.sendOne(session, JSON.toJSONString(wowMessage)); } /** * 地圖移動 * * @param session session * @param message 消息 */ private void handleMoveMessage(Session session, WowMessage<MoveRequest> message) { WowMessageHeader header = message.getHeader(); header.setResponseTime(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss")); MoveRequest request = message.getContent(); Character character = GameWorld.OnlineCharacter.get(session.getId()); String fromMapId = character.getMapId(); String destMapId = request.getDestMapId(); GameWorld.MapCharacter.get(fromMapId).remove(character); GameWorld.MapCharacter.get(destMapId).add(character); character.setMapId(destMapId); MapInfo mapInfo = this.loadMapInfo(destMapId); OnlineInfo onlineInfo = this.loadOnlineInfo(destMapId); MoveResponse response = new MoveResponse(); response.setMapInfo(mapInfo); response.setOnlineInfo(onlineInfo); WowMessage wowMessage = new WowMessage<>(header, response); this.sendOne(session, JSON.toJSONString(wowMessage)); } /** * 刷新在線列表 * * @param session session * @param message 消息 */ private void handleRefreshOnlineMessage(Session session, WowMessage<RefreshOnlineRequest> message) { WowMessageHeader header = message.getHeader(); header.setResponseTime(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss")); Character character = GameWorld.OnlineCharacter.get(session.getId()); String mapId = character.getMapId(); OnlineInfo onlineInfo = this.loadOnlineInfo(mapId); RefreshOnlineResponse response = new RefreshOnlineResponse(); response.setOnlineInfo(onlineInfo); WowMessage wowMessage = new WowMessage<>(header, response); this.sendOne(session, JSON.toJSONString(wowMessage)); } /** * 讀取地圖資訊 * * @param mapId 地圖ID * @return */ private MapInfo loadMapInfo(String mapId) { MapInfo mapInfo = new MapInfo(); CommonResult commonResult = wowMapService.find(mapId); if (commonResult.isSuccess()) { WowMap wowMap = (WowMap) commonResult.getData(); mapInfo.setWowMap(wowMap); } List<MapCoord> mapCoordList = mapCoordService.listByFromMapId(mapId); mapInfo.setMapCoordList(mapCoordList); return mapInfo; } /** * 讀取在線列表 * * @param mapId 地圖ID * @return */ private OnlineInfo loadOnlineInfo(String mapId) { OnlineInfo onlineInfo = new OnlineInfo(); List<MapMob> mapMobList = mapMobService.listByMapId(mapId); onlineInfo.setMapMobList(mapMobList); List<Character> mapCharacterList = GameWorld.MapCharacter.get(mapId); onlineInfo.setMapCharacterList(mapCharacterList); return onlineInfo; } }
MessageHandler
五、前端socket處理
對應後端的MessageHub,前端也需要一個socket客戶端,這裡我們創建一個WowClient對象,負責最外層的消息處理邏輯。
1 const WowClient = function () { 2 this.cache = { 3 version: 0, 4 levelExpMap: [] 5 }; 6 this.cacheKey = "idlewow_client_cache"; 7 this.hubUrl = "ws://localhost:20010/hub"; 8 this.webSocket = new WebSocket(this.hubUrl); 9 this.webSocket.onopen = function (event) { 10 console.log('WebSocket建立連接'); 11 wowClient.sendLogin(); 12 wowClient.loadCache(); 13 }; 14 this.webSocket.onmessage = function (event) { 15 console.log('WebSocket收到消息:%c' + event.data, 'color:green'); 16 var message = JSON.parse(event.data) || {}; 17 console.log(message); 18 wowClient.receive(message); 19 }; 20 this.webSocket.onclose = function (event) { 21 console.log('WebSocket關閉連接'); 22 }; 23 this.webSocket.onerror = function (event) { 24 console.log('WebSocket發生異常'); 25 }; 26 };
另外,前端同樣也需要定義消息類型,
1 const RequestMessage = function () { 2 this.header = { 3 messageCode: "", 4 requestTime: new Date(), 5 version: "1.0" 6 }; 7 this.content = {}; 8 }; 9 10 const MessageCode = { 11 // 預處理 12 LoadCache: "0010", 13 // 系統命令 14 Login: "1001", 15 RefreshOnline: "1002", 16 // 玩家命令 17 Chat: "2001", 18 Move: "2002", 19 BattleMob: "2100" 20 };
具體的消息處理邏輯和消息實體的創建,通過原型方法生成。完整的js文件如下:


1 const WowClient = function () { 2 this.cache = { 3 version: 0, 4 levelExpMap: [] 5 }; 6 this.cacheKey = "idlewow_client_cache"; 7 this.hubUrl = "ws://localhost:20010/hub"; 8 this.webSocket = new WebSocket(this.hubUrl); 9 this.webSocket.onopen = function (event) { 10 console.log('WebSocket建立連接'); 11 wowClient.sendLogin(); 12 wowClient.loadCache(); 13 }; 14 this.webSocket.onmessage = function (event) { 15 console.log('WebSocket收到消息:%c' + event.data, 'color:green'); 16 var message = JSON.parse(event.data) || {}; 17 console.log(message); 18 wowClient.receive(message); 19 }; 20 this.webSocket.onclose = function (event) { 21 console.log('WebSocket關閉連接'); 22 }; 23 this.webSocket.onerror = function (event) { 24 console.log('WebSocket發生異常'); 25 }; 26 }; 27 28 const RequestMessage = function () { 29 this.header = { 30 messageCode: "", 31 requestTime: new Date(), 32 version: "1.0" 33 }; 34 this.content = {}; 35 }; 36 37 const MessageCode = { 38 // 預處理 39 LoadCache: "0010", 40 // 系統命令 41 Login: "1001", 42 RefreshOnline: "1002", 43 // 玩家命令 44 Chat: "2001", 45 Move: "2002", 46 BattleMob: "2100" 47 }; 48 49 WowClient.prototype = { 50 ////////////////// 51 //// 對外介面 //// 52 ////////////////// 53 // 讀取快取 54 loadCache: function () { 55 let storage = localStorage.getItem(this.cacheKey); 56 let cache = storage ? JSON.parse(storage) : null; 57 if (!cache || (new Date().getTime() - cache.version) > 1000 * 60 * 60 * 24) { 58 this.sendLoadCache(); 59 } else { 60 this.cache = cache; 61 } 62 }, 63 64 ////////////////// 65 //// 消息處理 //// 66 ////////////////// 67 68 // 發送消息 69 send: function (message) { 70 let msg = JSON.stringify(message); 71 this.webSocket.send(msg); 72 }, 73 // 接收消息 74 receive: function (message) { 75 switch (message.header.messageCode) { 76 case MessageCode.LoadCache: 77 this.recvLoadCache(message); 78 break; 79 case MessageCode.RefreshOnline: 80 this.recvRefreshOnline(message); 81 break; 82 case MessageCode.Login: 83 this.recvLogin(message); 84 break; 85 case MessageCode.Chat: 86 this.recvChat(message); 87 break; 88 case MessageCode.Move: 89 this.recvMove(message); 90 break; 91 case MessageCode.BattleMob: 92 this.recvBattleMob(message); 93 break; 94 default: 95 break; 96 } 97 }, 98 99 // 讀取快取 100 sendLoadCache: function () { 101 this.send(new RequestMessage().loadCache()); 102 }, 103 recvLoadCache: function (message) { 104 this.cache.levelExpMap = message.content.levelExpMap; 105 this.cache.version = new Date().getTime(); 106 localStorage.setItem(this.cacheKey, JSON.stringify(this.cache)); 107 }, 108 // 刷新在線列表 109 sendRefreshOnline: function () { 110 this.send(new RequestMessage().refreshOnline()); 111 }, 112 recvRefreshOnline: function (message) { 113 this.refreshOnlineInfo(message.content.onlineInfo); 114 }, 115 // 登陸 116 sendLogin: function () { 117 this.send(new RequestMessage().login()); 118 }, 119 recvLogin: function (message) { 120 this.refreshMapInfo(message.content.mapInfo); 121 this.refreshOnlineInfo(message.content.onlineInfo); 122 }, 123 // 聊天 124 sendChat: function () { 125 this.send(new RequestMessage().chat()); 126 }, 127 recvChat: function (message) { 128 let channel = "【當前】"; 129 let content = "<p>" + channel + message.content.senderName + ": " + message.content.message + "</p>"; 130 $('.msg-chat').append(content); 131 }, 132 // 移動 133 sendMove: function (mapId) { 134 this.send(new RequestMessage().move(mapId)); 135 }, 136 recvMove: function (message) { 137 this.refreshMapInfo(message.content.mapInfo); 138 this.refreshOnlineInfo(message.content.onlineInfo); 139 }, 140 // 戰鬥 141 sendBattleMob: function (mobId) { 142 this.send(new RequestMessage().battleMob(mobId)); 143 }, 144 recvBattleMob: async function (message) { 145 $('.msg-battle').html(''); 146 let battleResult = message.content.battleResult; 147 if (battleResult.roundList) { 148 var rounds = battleResult.roundList; 149 for (var i = 0; i < rounds.length; i++) { 150 var round = rounds[i]; 151 var content = "<p>【第" + round.round + "回合】</p>"; 152 if (round.atkStage) { 153 content += "<p>" + round.atkStage.desc + "</p>"; 154 } 155 156 if (round.defStage) { 157 content += "<p>" + round.defStage.desc + "</p>"; 158 } 159 160 $('.msg-battle').append(content); 161 await this.sleep(1500); 162 } 163 164 $('.msg-battle').append("<p><strong>戰鬥結束," + battleResult.winName + " 獲得勝利!</strong></p>"); 165 if (battleResult.isPlayerWin) { 166 this.settlement(battleResult); 167 } 168 169 let that = this; 170 await this.sleep(5000).then(function () { 171 that.sendBattleMob(battleResult.atkId, battleResult.defId); 172 }); 173 } 174 }, 175 176 ////////////////// 177 //// 輔助方法 //// 178 ////////////////// 179 180 // 刷新地圖資訊 181 refreshMapInfo: function (mapInfo) { 182 let wowMap = mapInfo.wowMap; 183 let mapCoordList = mapInfo.mapCoordList; 184 $('#mapName').html(wowMap.name); 185 $('#mapDesc').html(wowMap.description); 186 $('#mapImg').attr('src', '/images/wow/map/' + wowMap.name + '.jpg'); 187 let coordsHtml = ''; 188 for (let index in mapCoordList) { 189 let mapCoord = mapCoordList[index]; 190 coordsHtml += '<area shape="' + mapCoord.shape + '" coords="' + mapCoord.coord + '" onclick="wowClient.sendMove(\'' + mapCoord.destMapId + '\');" href="javascript:void(0);" alt="' + mapCoord.destMapName + '" title="' + mapCoord.destMapName + '"/>'; 191 } 192 193 $('#map-coords').html(coordsHtml); 194 }, 195 // 刷新在線列表 196 refreshOnlineInfo: function (onlineInfo) { 197 let mapCharacterList = onlineInfo.mapCharacterList; 198 let mapMobList = onlineInfo.mapMobList; 199 // 更新在線列表 200 $('#online-all').html(''); 201 $('#online-player').html(''); 202 $('#online-mob').html(''); 203 for (let index in mapCharacterList) { 204 let mapCharacter = mapCharacterList[index]; 205 let row = '<div class="layui-row"><div class="layui-col-md9"><label style="color: blue;">' + mapCharacter.name + '</label><label> - 等級:' + mapCharacter.level + '</label></div><div class="layui-col-md3"><button type="button" style="height:14px;line-height: 14px;">私聊</button></div></div>'; 206 $('#online-all').append(row); 207 $('#online-player').append(row); 208 } 209 210 for (let index in mapMobList) { 211 let mapMob = mapMobList[index]; 212 let row = '<div class="layui-row"><div class="layui-col-md9"><label style="color: red;">' + mapMob.name + '</label><label> - 等級:' + mapMob.level + '</label></div><div class="layui-col-md3"><button type="button" style="height:14px;line-height: 14px;" onclick="wowClient.sendBattleMob(\'' + mapMob.id + '\');">戰鬥</button><button type="button" style="height:14px;line-height:14px;" onclick="guaji();">掛機</button></div></div>'; 213 $('#online-all').append(row); 214 $('#online-mob').append(row); 215 } 216 }, 217 // 戰鬥結算 218 settlement: function (battleResult) { 219 $('.lbl-level').html(battleResult.settleLevel); 220 $('.lbl-exp').html(battleResult.settleExp); 221 }, 222 // 休眠 223 sleep: function (milliseconds) { 224 let p = new Promise(function (resolve) { 225 setTimeout(function () { 226 resolve(); 227 }, milliseconds) 228 }); 229 return p; 230 }, 231 // 關閉 232 close: function () { 233 this.webSocket.close(); 234 } 235 }; 236 237 RequestMessage.prototype = { 238 loadCache: function () { 239 this.header.messageCode = MessageCode.LoadCache; 240 }, 241 login: function () { 242 this.header.messageCode = MessageCode.Login; 243 }, 244 chat: function () { 245 this.header.messageCode = MessageCode.Chat; 246 this.content = { 247 senderId: charId, 248 senderName: charName, 249 receiverId: '', 250 receiverName: '', 251 message: $('#msg').val() 252 }; 253 }, 254 move: function (mapId) { 255 this.header.messageCode = MessageCode.Move; 256 this.content = { 257 destMapId: mapId 258 }; 259 }, 260 battleMob: function (mobId) { 261 this.header.messageCode = MessageCode.BattleMob; 262 this.content = { 263 mobId: mobId 264 }; 265 }, 266 refreshOnline: function () { 267 this.header.messageCode = MessageCode.RefreshOnline; 268 } 269 }; 270 271 // wow客戶端 272 window.wowClient = new WowClient(); 273 274 // 關閉窗口 275 window.onbeforeunload = function (event) { 276 wowClient.close(); 277 }; 278 279 document.onkeydown = function (event) { 280 let e = event || window.event || arguments.callee.caller.arguments[0]; 281 if (e.keyCode === 13 && document.activeElement.id === 'msg') { 282 wowClient.sendChat(); 283 } 284 };
main.js
小結
本章主要實現的socket的通訊邏輯,對消息的處理涉及了遊戲的業務處理邏輯,僅簡單的講了一些。
另外因為時隔較長,程式碼裁剪工作量較大。本章僅對已完成的程式碼做了粗略裁剪。源程式碼的一些變動,文中將講解一些主要的,其他的就不再贅述了。
對一些邊角的內容,程式碼會變化,但文中未體現的,如有問題,可留言諮詢。
源碼下載地址://545c.com/file/14960372-437680954
本文原文地址://www.cnblogs.com/lyosaki88/p/idlewow_13.html
項目交流群:329989095