《攻城Online》快速原型:服务端设计
“攻城”服務(wù)端采用Photon引擎的框架,其主要邏輯如以下UML所示。
服務(wù)端的啟動入口為ServerApplication,該類包含著相關(guān)的Collection數(shù)據(jù)集合,而Collection內(nèi)又有與數(shù)據(jù)庫文件夾Database關(guān)聯(lián)的文件。兩個文件夾的內(nèi)容如圖。
簡單來說,ServerApplication內(nèi)緩存著各類數(shù)據(jù),并完成與數(shù)據(jù)庫等的關(guān)聯(lián)。而本篇的重點是ServerPeer這個類。下面介紹什么是Peer。
每當(dāng)一個客戶端連接到服務(wù)端時,服務(wù)端會自動生成一個客戶端連接實例,稱其為Peer。通過Peer,便能完成服務(wù)端與客戶端之間的數(shù)據(jù)交互,ServerPeer類便是完成這個任務(wù)的類。在這里通過簡單代碼介紹這個類的內(nèi)容。
1 //----------------------------------------------------------------------------------------------------------- 2 // Copyright (C) 2015-2016 SiegeOnline 3 // 版權(quán)所有 4 // 5 // 文件名:ServerPeer.cs 6 // 7 // 文件功能描述: 8 // 9 // 服務(wù)端與客戶端的連線實例 10 // 11 // 創(chuàng)建標(biāo)識:taixihuase 20150712 12 // 13 // 修改標(biāo)識: 14 // 修改描述: 15 // 16 // 17 // 修改標(biāo)識: 18 // 修改描述: 19 // 20 //----------------------------------------------------------------------------------------------------------- 21 22 using System; 23 using ExitGames.Logging; 24 using Photon.SocketServer; 25 using PhotonHostRuntimeInterfaces; 26 using SiegeOnlineServer.Protocol; 27 using SiegeOnlineServer.ServerLogic; 28 29 namespace SiegeOnlineServer 30 { 31 /// <summary> 32 /// 類型:類 33 /// 名稱:ServerPeer 34 /// 作者:taixihuase 35 /// 作用:用于服務(wù)端與客戶端之間的數(shù)據(jù)傳輸 36 /// 編寫日期:2015/7/12 37 /// </summary> 38 public class ServerPeer : PeerBase 39 { 40 // 日志 41 public static readonly ILogger Log = LogManager.GetCurrentClassLogger(); 42 43 // 索引 44 public Guid PeerGuid { get; protected set; } 45 46 // 服務(wù)端 47 public readonly ServerApplication Server; 48 49 /// <summary> 50 /// 類型:方法 51 /// 名稱:ServerPeer 52 /// 作者:taixihuase 53 /// 作用:構(gòu)造 ServerPeer 對象 54 /// 編寫日期:2015/7/12 55 /// </summary> 56 /// <param name="protocol"></param> 57 /// <param name="unmanagedPeer"></param> 58 /// <param name="server"></param> 59 public ServerPeer(IRpcProtocol protocol, IPhotonPeer unmanagedPeer, ServerApplication server) : base(protocol, unmanagedPeer) 60 { 61 PeerGuid = Guid.NewGuid(); 62 Server = server; 63 64 // 將當(dāng)前 peer 加入連線列表 65 Server.Users.AddConnectedPeer(PeerGuid, this); 66 } 67 68 /// <summary> 69 /// 類型:方法 70 /// 名稱:OnOperationRequest 71 /// 作者:taixihuase 72 /// 作用:響應(yīng)并處理客戶端發(fā)來的請求 73 /// 編寫日期:2015/7/14 74 /// </summary> 75 /// <param name="operationRequest"></param> 76 /// <param name="sendParameters"></param> 77 protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters) 78 { 79 switch (operationRequest.OperationCode) 80 { 81 // 賬號登陸 82 case (byte) OperationCode.Login: 83 Login.OnRequest(operationRequest, sendParameters, this); 84 break; 85 86 // 創(chuàng)建新角色 87 case (byte) OperationCode.CreateCharacter: 88 CreateCharacter.OnRequest(operationRequest, sendParameters, this); 89 break; 90 91 // 角色進(jìn)入場景 92 case (byte) OperationCode.WorldEnter: 93 WorldEnter.OnRequest(operationRequest, sendParameters, this); 94 break; 95 96 97 } 98 } 99 100 /// <summary> 101 /// 類型:方法 102 /// 名稱:OnDisconnect 103 /// 作者:taixihuase 104 /// 作用:當(dāng)與客戶端失去連接時進(jìn)行處理 105 /// 編寫日期:2015/7/12 106 /// </summary> 107 /// <param name="reasonCode"></param> 108 /// <param name="reasonDetail"></param> 109 protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail) 110 { 111 Server.Players.RemoveCharacter(PeerGuid); 112 Server.Users.UserOffline(PeerGuid); 113 Server.Users.RemovePeer(PeerGuid); 114 } 115 } 116 }可以看到,ServerPeer的重點在于OnOperationRequest方法,該方法其中一個參數(shù)為OperationRequest類型的對象,這個對象中包含著一個byte型的OperationCode對象和一個Dictionary<byte, object>的對象Parameters。其中OperationCode即為客戶端的操作請求碼,服務(wù)端需要通過識別這個操作碼才能對特定數(shù)據(jù)執(zhí)行正確的處理。Parameters包含著等待處理的數(shù)據(jù),這是個字典類型的對象,其鍵為參數(shù)類型碼,對應(yīng)的值即為該參數(shù)類型碼所說明的數(shù)據(jù)對象。由于Photon的這個字典中object無法直接對自定義類進(jìn)行序列化,因此需要通過手動序列化為二進(jìn)制數(shù)據(jù)后再傳入字典,取出時也要根據(jù)操作碼或參數(shù)類型碼手動反序列化為特定實例。這里封裝好這兩個操作為靜態(tài)方法,直接調(diào)用即可。
1 //----------------------------------------------------------------------------------------------------------- 2 // Copyright (C) 2015-2016 SiegeOnline 3 // 版權(quán)所有 4 // 5 // 文件名:Serialization.cs 6 // 7 // 文件功能描述: 8 // 9 // 數(shù)據(jù)對象二進(jìn)制序列化及反序列化 10 // 11 // 創(chuàng)建標(biāo)識:taixihuase 20150714 12 // 13 // 修改標(biāo)識: 14 // 修改描述: 15 // 16 // 17 // 修改標(biāo)識: 18 // 修改描述: 19 // 20 //---------------------------------------------------------------------------------------------------------- 21 22 using System.IO; 23 using System.Runtime.Serialization; 24 using System.Runtime.Serialization.Formatters.Binary; 25 26 namespace SiegeOnlineServer.Protocol 27 { 28 /// <summary> 29 /// 類型:類 30 /// 名稱:Serialization 31 /// 作者:taixihuase 32 /// 作用:對數(shù)據(jù)進(jìn)行二進(jìn)制序列化與反序列化 33 /// 編寫日期:2015/7/14 34 /// </summary> 35 public class Serialization 36 { 37 /// <summary> 38 /// 類型:方法 39 /// 名稱:Serialize 40 /// 作者:taixihuase 41 /// 作用:將一個對象二進(jìn)制序列化 42 /// 編寫日期:2015/7/14 43 /// </summary> 44 /// <param name="unSerializedObj"></param> 45 /// <returns></returns> 46 public static byte[] Serialize(object unSerializedObj) 47 { 48 MemoryStream stream = new MemoryStream(); 49 IFormatter formatter = new BinaryFormatter(); 50 formatter.Serialize(stream, unSerializedObj); 51 return stream.ToArray(); 52 } 53 54 /// <summary> 55 /// 類型:方法 56 /// 名稱:Deserialize 57 /// 作者:taixihuase 58 /// 作用:將一個二進(jìn)制序列化數(shù)據(jù)流反序列化為一個對象 59 /// 編寫日期:2015/7/14 60 /// </summary> 61 /// <param name="serializedArray"></param> 62 /// <returns></returns> 63 public static object Deserialize(object serializedArray) 64 { 65 MemoryStream stream = new MemoryStream((byte[])serializedArray); 66 IFormatter formatter = new BinaryFormatter(); 67 stream.Seek(0, SeekOrigin.Begin); 68 object unSerializedObj = formatter.Deserialize(stream); 69 return unSerializedObj; 70 } 71 } 72 }重新回到OnOperationRequest方法,當(dāng)判別了操作碼類型后,則Peer會調(diào)用特定類的一個靜態(tài)方法,這樣便可不需要實例化這些類型的對象后再使用。例如:
1 // 賬號登陸 2 case (byte) OperationCode.Login: 3 Login.OnRequest(operationRequest, sendParameters, this); 4 break;當(dāng)識別為Login操作后,則調(diào)用Login里的OnRequest方法,并把參數(shù)原封不動傳過去,在另外的文件里進(jìn)行處理,這樣子方便操作。同時OnRequest方法還要求第三個參數(shù),為ServerPeer類型的對象,這樣通過把this傳過去,便能在其他地方引用到該P(yáng)eer,而Peer又存放著ServerApplication的引用,方法調(diào)用及數(shù)據(jù)傳輸便暢通無阻。需要注意的是,不管操作碼是什么,都是直接調(diào)用相應(yīng)的OnRequest方法,如上面的代碼所示。
接下來是對請求的處理邏輯。
當(dāng)前實現(xiàn)了對三個不同請求的處理,在此用Login操作講解。每個邏輯處理文件都包含OnRequest方法,除此之外,還有一個以“Try”開頭命名的方法,該方法參數(shù)與OnRequest相同,即OnRequest再次將數(shù)據(jù)傳給Try方法,而Try方法則真正進(jìn)行處理,完成后將回應(yīng)發(fā)生請求的客戶端,或者向特定客戶端發(fā)送廣播。
1 //----------------------------------------------------------------------------------------------------------- 2 // Copyright (C) 2015-2016 SiegeOnline 3 // 版權(quán)所有 4 // 5 // 文件名:Login.cs 6 // 7 // 文件功能描述: 8 // 9 // 登錄用戶賬號,響應(yīng)客戶端登錄賬號請求 10 // 11 // 創(chuàng)建標(biāo)識:taixihuase 20150714 12 // 13 // 修改標(biāo)識: 14 // 修改描述: 15 // 16 // 17 // 修改標(biāo)識: 18 // 修改描述: 19 // 20 //----------------------------------------------------------------------------------------------------------- 21 22 using System; 23 using System.Collections.Generic; 24 using Photon.SocketServer; 25 using SiegeOnlineServer.Collection; 26 using SiegeOnlineServer.Protocol; 27 using SiegeOnlineServer.Protocol.Common.Character; 28 using SiegeOnlineServer.Protocol.Common.User; 29 30 namespace SiegeOnlineServer.ServerLogic 31 { 32 /// <summary> 33 /// 類型:類 34 /// 名稱:Login 35 /// 作者:taixihuase 36 /// 作用:響應(yīng)登錄請求 37 /// 編寫日期:2015/7/14 38 /// </summary> 39 public class Login 40 { 41 /// <summary> 42 /// 類型:方法 43 /// 名稱:OnRequest 44 /// 作者:taixihuase 45 /// 作用:當(dāng)收到請求時,進(jìn)行處理 46 /// 編寫日期:2015/7/14 47 /// </summary> 48 /// <param name="operationRequest"></param> 49 /// <param name="sendParameters"></param> 50 /// <param name="peer"></param> 51 public static void OnRequest(OperationRequest operationRequest, SendParameters sendParameters, ServerPeer peer) 52 { 53 TryLogin(operationRequest, sendParameters, peer); 54 } 55 56 /// <summary> 57 /// 類型:方法 58 /// 名稱:TryLogin 59 /// 作者:taixihuase 60 /// 作用:通過登錄數(shù)據(jù)嘗試登錄 61 /// 編寫日期:2015/7/14 62 /// </summary> 63 /// <param name="operationRequest"></param> 64 /// <param name="sendParameters"></param> 65 /// <param name="peer"></param> 66 private static void TryLogin(OperationRequest operationRequest, SendParameters sendParameters, ServerPeer peer) 67 { 68 ServerPeer.Log.Debug("Logining..."); 69 70 LoginInfo login = (LoginInfo) 71 Serialization.Deserialize(operationRequest.Parameters[(byte) ParameterCode.Login]); 72 73 #region 對賬號密碼進(jìn)行判斷 74 75 ServerPeer.Log.Debug(DateTime.Now + " : Loginning..."); 76 ServerPeer.Log.Debug(login.Account); 77 ServerPeer.Log.Debug(login.Password); 78 79 // 獲取用戶資料 80 UserBase user = new UserBase(peer.PeerGuid, login.Account); 81 UserCollection.UserReturn userReturn = peer.Server.Users.UserOnline(ref user, login.Password); 82 83 // 若成功取得用戶資料 84 if (userReturn.ReturnCode == (byte) UserCollection.UserReturn.ReturnCodeTypes.Success) 85 { 86 ServerPeer.Log.Debug(user.LoginTime + " :User " + user.Nickname + " loginning..."); 87 88 // 用于選擇的數(shù)據(jù)返回參數(shù) 89 var parameter = new Dictionary<byte, object>(); 90 91 // 用于選擇的字符串信息 92 string message = ""; 93 94 // 用于選擇的返回值 95 short returnCode = -1; 96 97 #region 獲取角色資料 98 99 Character character = new Character(user); 100 PlayerCollection.CharacterReturn characterReturn = 101 peer.Server.Players.SearchCharacter(ref character); 102 103 // 若取得角色資料 104 if (characterReturn.ReturnCode == (byte) PlayerCollection.CharacterReturn.ReturnCodeTypes.Success) 105 { 106 byte[] playerBytes = Serialization.Serialize(character); 107 parameter.Add((byte) ParameterCode.Login, playerBytes); 108 returnCode = (short) ErrorCode.Ok; 109 message = ""; 110 111 ServerPeer.Log.Debug(character.Occupation.Name); 112 } 113 else if (characterReturn.ReturnCode == 114 (byte) PlayerCollection.CharacterReturn.ReturnCodeTypes.CharacterNotFound) 115 { 116 byte[] userBytes = Serialization.Serialize(user); 117 parameter.Add((byte) ParameterCode.Login, userBytes); 118 returnCode = (short) ErrorCode.CharacterNotFound; 119 message = characterReturn.DebugMessage.ToString(); 120 } 121 122 #endregion 123 124 OperationResponse response = new OperationResponse((byte) OperationCode.Login, parameter) 125 { 126 ReturnCode = returnCode, 127 DebugMessage = message 128 }; 129 peer.SendOperationResponse(response, sendParameters); 130 ServerPeer.Log.Debug(user.LoginTime + " : User " + user.Account + " logins successfully"); 131 } 132 // 若重復(fù)登錄 133 else if (userReturn.ReturnCode == (byte) UserCollection.UserReturn.ReturnCodeTypes.RepeatedLogin) 134 { 135 OperationResponse response = new OperationResponse((byte) OperationCode.Login) 136 { 137 ReturnCode = (short) ErrorCode.RepeatedOperation, 138 DebugMessage = "賬號已登錄!" 139 }; 140 peer.SendOperationResponse(response, sendParameters); 141 ServerPeer.Log.Debug(DateTime.Now + " : Failed to login " + user.Account + " Because of " + 142 Enum.GetName(typeof (UserCollection.UserReturn.ReturnCodeTypes), 143 userReturn.ReturnCode)); 144 } 145 else 146 { 147 // 返回非法登錄錯誤 148 OperationResponse response = new OperationResponse((byte) OperationCode.Login) 149 { 150 ReturnCode = (short) ErrorCode.InvalidOperation, 151 DebugMessage = userReturn.DebugMessage.ToString() 152 }; 153 peer.SendOperationResponse(response, sendParameters); 154 ServerPeer.Log.Debug(DateTime.Now + " : Failed to login " + user.Account + " Because of " + 155 Enum.GetName(typeof (UserCollection.UserReturn.ReturnCodeTypes), 156 userReturn.ReturnCode)); 157 } 158 } 159 160 #endregion 161 } 162 }? TryLogin將OperationRequest中的數(shù)據(jù)反序列化后取出,然后通過ServerPeer對象作為中介,與ServerApplication關(guān)聯(lián),從而可以通過Application里的數(shù)據(jù)庫關(guān)聯(lián)來獲取賬號、角色信息等,并將結(jié)果和數(shù)據(jù)返回到Login中。之后需要將結(jié)果發(fā)送回給客戶端接收,ServerPeer通過繼承后,包含有一個SendOperationResponse方法,這是為什么需要傳給OnRequest和TryLogin方法第三個參數(shù)的原因,在這里便能直接調(diào)用了。SendOperationResponse方法需要一個OperationResponse類型的對象和一個SendParameters類型的對象,后者一般填入層層調(diào)用傳過來的那個sendParameters或者自己new一個即可。此處的重點是OperationResponse。
客戶端發(fā)送Request請求,服務(wù)端接收請求并處理后,就要給客戶端答應(yīng),這就是Response。OperationResponse跟OperationRequest長得相似,同樣帶一個操作碼參數(shù)和一個字典,操作碼跟Request一般填一樣,這樣客戶端接收后便能知道是發(fā)送了什么請求后得到的答應(yīng)。字典類型的Parameters也是以一個參數(shù)類型碼為鍵,以object對象為值,這里的值同樣用二進(jìn)制數(shù)據(jù),調(diào)用Serialization的Serialize方法即可。此外,還有一個short型的ReturnCode字段,該字段填入請求處理的情況碼,即操作正確或者某些不正確操作的錯誤碼,這里封進(jìn)枚舉里表示。最后需要填的是一個DebugMessage字符串,我們可以填入操作信息,支持中文,這樣在客戶端測試時便能打印出來,對整個操作的執(zhí)行情況一目了然。如果Response不需要回給客戶端數(shù)據(jù),則可以省略掉Parameters,但其他的還是要填。
服務(wù)端除了給發(fā)送請求的客戶端進(jìn)行回應(yīng)外,還能對其他客戶端進(jìn)行廣播。這里用角色進(jìn)入場景時,服務(wù)端給當(dāng)前正在進(jìn)行游戲的所有客戶端連接發(fā)送某個玩家上線的提示信息為例。
1 //----------------------------------------------------------------------------------------------------------- 2 // Copyright (C) 2015-2016 SiegeOnline 3 // 版權(quán)所有 4 // 5 // 文件名:WorldEnter.cs 6 // 7 // 文件功能描述: 8 // 9 // 進(jìn)入游戲場景,響應(yīng)客戶端進(jìn)入場景請求 10 // 11 // 創(chuàng)建標(biāo)識:taixihuase 20150722 12 // 13 // 修改標(biāo)識: 14 // 修改描述: 15 // 16 // 17 // 修改標(biāo)識: 18 // 修改描述: 19 // 20 //----------------------------------------------------------------------------------------------------------- 21 22 using System.Collections.Generic; 23 using Photon.SocketServer; 24 using SiegeOnlineServer.Protocol; 25 using SiegeOnlineServer.Protocol.Common.Character; 26 27 namespace SiegeOnlineServer.ServerLogic 28 { 29 /// <summary> 30 /// 類型:類 31 /// 名稱:WorldEnter 32 /// 作者:taixihuase 33 /// 作用:響應(yīng)進(jìn)入場景請求 34 /// 編寫日期:2015/7/22 35 /// </summary> 36 public class WorldEnter 37 { 38 /// <summary> 39 /// 類型:方法 40 /// 名稱:OnRequest 41 /// 作者:taixihuase 42 /// 作用:當(dāng)收到請求時,進(jìn)行處理 43 /// 編寫日期:2015/7/22 44 /// </summary> 45 /// <param name="operationRequest"></param> 46 /// <param name="sendParameters"></param> 47 /// <param name="peer"></param> 48 public static void OnRequest(OperationRequest operationRequest, SendParameters sendParameters, ServerPeer peer) 49 { 50 TryEnter(operationRequest, sendParameters, peer); 51 } 52 53 /// <summary> 54 /// 類型:方法 55 /// 名稱:TryEnter 56 /// 作者:taixihuase 57 /// 作用:通過角色數(shù)據(jù)嘗試進(jìn)入場景 58 /// 編寫日期:2015/7/22 59 /// </summary> 60 /// <param name="operationRequest"></param> 61 /// <param name="sendParameters"></param> 62 /// <param name="peer"></param> 63 private static void TryEnter(OperationRequest operationRequest, SendParameters sendParameters, ServerPeer peer) 64 { 65 ServerPeer.Log.Debug("Entering"); 66 67 Character character = (Character) 68 Serialization.Deserialize(operationRequest.Parameters[(byte) ParameterCode.WorldEnter]); 69 70 peer.Server.Players.CharacterEnter(ref character); 71 peer.Server.Data.CharacterData.GetCharacterPositionFromDatabase(ref character); 72 73 // 返回數(shù)據(jù)給客戶端 74 75 byte[] data = Serialization.Serialize(character); 76 77 var reponseData = new OperationResponse((byte) OperationCode.WorldEnter, new Dictionary<byte, object> 78 { 79 {(byte) ParameterCode.WorldEnter, data} 80 }); 81 peer.SendOperationResponse(reponseData, sendParameters); 82 83 var eventData = new EventData((byte)EventCode.WorldEnter, new Dictionary<byte, object> 84 { 85 {(byte) ParameterCode.WorldEnter, data} 86 }); 87 eventData.SendTo(peer.Server.Players.GamingClients, sendParameters); 88 } 89 } 90 }WorldEnter文件對這一操作進(jìn)行處理,主要邏輯在于TryEnter中。服務(wù)端試圖獲取角色數(shù)據(jù),然后通過SendOperationResponse返回給客戶端,并且實例化一個EventData對象,該類型需要填入一個byte類型的事件代碼,其實跟操作碼相似,然后是一個字典類型的對象,傳入給接收廣播的客戶端的所需數(shù)據(jù)。EventData有一個SendTo方法,第一個參數(shù)代表著要廣播的客戶端集合,此處可以用一個List<ServerPeer>類型的對象表示,該方法會自動遍歷每一個Peer,第二個參數(shù)沒特殊要求的話,照填sendParameters即可。這樣一旦某個角色進(jìn)入了游戲主場景,則所有在線玩家都會接收到提示。
下圖是服務(wù)端原型的組織結(jié)構(gòu)。
最下端的Protocol項目為協(xié)議內(nèi)容,由客戶端和服務(wù)端共用,會在后面文章詳細(xì)介紹。
服務(wù)端主體框架就這些,其余內(nèi)容還待詳細(xì)設(shè)計。
Photon Server有個英文的在線文檔,更多的用法可以參照以下網(wǎng)址:
http://doc-api.exitgames.com/en/onpremise/current/server/doc/index.html
轉(zhuǎn)載于:https://www.cnblogs.com/SiegeOL/p/4690954.html
總結(jié)
以上是生活随笔為你收集整理的《攻城Online》快速原型:服务端设计的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: “攻城狮” 需要了解的密码知识
- 下一篇: UE4 昵称修改后客户端的同步