Unity網絡通信系統設計
Buffer報文
BufferEntity類作爲報文基類的作用包括:
-
封裝數據:BufferEntity類可以用來封裝網絡通信中的數據,方便在網絡傳輸中進行處理和管理。
-
提供數據緩衝區:BufferEntity類通常會包含一個數據緩衝區,用來存儲待發送或接收的數據,以便進行網絡通信操作。
-
抽象報文結構:通過BufferEntity類作爲報文基類,可以定義報文的通用結構和屬性,便於派生出具體的報文類型來處理不同的通信需求。
-
支持報文序列化和反序列化:BufferEntity類可以提供方法來將報文數據序列化爲字節流或將字節流反序列化爲報文數據,實現數據在網絡傳輸和解析的轉換。
BufferEntity類作爲報文類扮演着封裝、管理和處理網絡通信數據的重要角色,有助於簡化網絡通信操作並提高代碼的可維護性和擴展性。
BufferEntity
客戶端網絡系統
-
UClient:
- 角色:UClient通常代表客戶端,用於與服務器進行通信和交互。
- 功能:主要負責處理客戶端與服務器之間的網絡連接、數據傳輸和通信邏輯。UClient可能包含與服務器通信的各種方法,處理接收到的數據並向服務器發送請求等操作。
-
USocket:
- 角色:USocket通常用於在Unity C#中封裝底層的Socket通信功能,提供更高級別的網絡操作接口。
- 功能:主要負責處理底層的網絡通信細節,如建立連接、發送和接收數據等。USocket可能包含Socket的初始化、連接、發送和接收數據等方法,同時處理網絡異常和錯誤。
總的來說,UClient作爲客戶端主要負責處理業務邏輯和與服務器的交互,而USocket作爲網絡通信的底層封裝則負責處理網絡連接和數據傳輸的細節。
序列化與反序列化——ProtoBuf
在網絡編程中,序列化和反序列化是爲了在不同的系統之間傳輸對象或數據時,將其轉換爲字節流或其他格式,以便能夠在網絡上傳輸。序列化是將對象轉換爲字節流的過程,而反序列化則是將字節流還原爲對象的過程。
通過序列化,可以將對象轉換爲字節流,然後通過網絡傳輸到另一臺計算機,再通過反序列化將字節流轉換回對象,實現跨網絡的數據傳輸。這樣可以方便地在不同系統之間進行通信和數據交換,保證數據的完整性和準確性。
常用的數據類型
int32—int,int64—long,bool,string,message-class,package-命名空間
import 引入某個類,類之間可以引用,實體數據 - rootpb.proto文件裏面定義
角色模板示例
其對應的C#模型類可藉助工具自動生成
syntax ="proto3";
package ProtoMsg;
import "RootPB.proto";
message UserRegisterC2S{
UserInfo UserInfo=1;//填寫帳號 密碼
}
message UserRegisterS2C{
int32 Result=1;//0註冊成功 1存在敏感詞 2賬號或者密碼長度不足 3賬號已被註冊
}
message UserLoginC2S{
UserInfo UserInfo=1;//填寫帳號 密碼
}
message UserLoginS2C{
int32 Result=1;//登錄結果:0成功 1帳號不存在 2帳號密碼不匹配 3保護凍結中 4封禁限制登錄
UserInfo UserInfo=2;//如果登錄成功,則返回該實體
RolesInfo RolesInfo=3;//角色信息
}
message UserQuitC2S{
UserInfo UserInfo=1;//如果登錄成功,則返回該實體
}
message UserQuitS2C{
int32 Result=1;//結果:0成功
}
報文創建
示例
/// <summary>
/// 創建並且發送報文
/// </summary>
/// <param name="messageID"></param>
/// <param name="message"></param>
/// <returns></returns>
public static BufferEntity CreateAndSendPackage(int messageID,IMessage message) {
JsonHelper.Log(messageID, message);
//Debug.Log($"報文ID:{messageID}\n包體{JsonHelper.SerializeObject(message)}");
BufferEntity buffer = new BufferEntity(USocket.local.endPoint,USocket.local.sessionID,0,0, MessageType.Login.GetHashCode(),
messageID,ProtobufHelper.ToBytes(message));
USocket.local.Send(buffer);
return buffer;
}
服務端網絡系統
- 連接池:建立連接池及連接使用、分配、管理的策略,不必每次新建連接都要創建連接實例。
- 超時重發:爲了保證數據可靠性,當發送方在規定時間內未收到對方的確認或響應時,會重新發送數據。進一步如果超過相應的重發次數,則清除連接。
- 心跳機制:爲了防止死連接,規定客戶端每隔一段時間要給服務端發送一個特定的信號。服務端通過定時器每隔一段時間遍歷所有連接。如果某個連接的最後一次信號的時間相隔太近,則認爲客戶端已斷開,於是主動斷開連接。
協議
客戶端與服務端創建協議的作用包括以下幾點:
-
數據交換規範:協議定義了客戶端與服務端之間的數據交換規範,包括數據格式、通信流程、消息格式等。通過定義協議,可以確保通信雙方能夠正確地解析和處理數據,從而實現有效的通信。
-
消息解析:協議規定了消息的結構和格式,包括消息頭、消息體、校驗位等信息。客戶端和服務端在通信時需要按照協議規定的格式解析和處理消息,以確保數據的正確性和完整性。
-
通信安全:協議可以定義數據加密、身份驗證等安全機制,確保通信的安全性。通過協議規定的安全策略,可以防止數據被竊取、篡改或僞造。
-
錯誤處理:協議可以定義錯誤碼、錯誤處理方式等信息,用於處理通信中可能出現的錯誤情況。通過協議規定的錯誤處理機制,可以及時發現並處理通信中的異常情況。
-
擴展性:通過定義靈活的協議,可以方便地擴展和修改通信協議,以適應不同的需求和場景。良好設計的協議可以提高系統的可維護性和擴展性。
總的來說,客戶端與服務端創建協議的作用是爲了規範數據交換、確保通信安全、處理錯誤情況、提高系統擴展性等,從而實現有效、安全和可靠的網絡通信。
示例
Excel協議配置表:
ProtoBuf模板:
syntax ="proto3";
package ProtoMsg;
import "RootPB.proto";
message BattleUserInputC2S{
int32 RolesID=1;//角色ID
int32 RoomID=2;//房間ID
int32 key=3;//鍵碼
V3Info MousePosition=4;//鼠標位置
string LockTag=5;//鎖定的標籤 int32 LockID=6;//鎖定的ID
}
message BattleUserInputS2C{
BattleUserInputC2S CMD=1;//用戶的輸入
}
網絡事件分發系統的
藉助客戶端和服務端的協議約束,現在就可以根據相應的標準,如上述協議中的協議ID,將對應的數據派送分發到不同的事件接收端處理。
BaseEvent
- 添加監聽
- 移除監聽
- 事件派發
//事件基類
public class EventBase<T,P,X> where T:new () where P:class
{
//1.子類 什麼類型
//2.
//3.
private static T instance;
public static T Instance {
get {
if (instance==null)
{
instance = new T();
}
return instance;
}
}
//存儲事件ID 還有方法(委託)
//使用線程安全的字典 避免以後多線程環境下出現問題
public ConcurrentDictionary<X, List<Action<P>>> dic = new ConcurrentDictionary<X, List<Action<P>>>();
//添加事件
public void AddEventListener(X key,Action<P> handle) {
if (dic.ContainsKey(key))
{
dic[key].Add(handle);
}
else
{
List<Action<P>> actions = new List<Action<P>>();
actions.Add(handle);
dic[key] = actions;
}
}
//移除事件
public void RemoveEventListener(X key, Action<P> handle) {
if (dic.ContainsKey(key))
{
List<Action<P>> actions = dic[key];
actions.Remove(handle);
if (actions.Count==0)
{
List<Action<P>> removeActions;
dic.TryRemove(key,out removeActions);
}
}
}
//派發事件的接口-帶有參數
public void Dispatch(X key,P p) {
if (dic.ContainsKey(key))
{
List<Action<P>> actions = dic[key];
if (actions!=null&&actions.Count>0)
{
for (int i = 0; i < actions.Count; i++)
{
if (actions[i]!=null)
{
actions[i](p);
}
}
}
}
}
//派發事件的接口-沒有參數的
public void Dispatch(X key) {
Dispatch(key, null);
}
}
//網絡事件類
public class NetEvent : EventBase<NetEvent,BufferEntity, int>{}
如此便可通過系統中的不同模塊通過事件系統來處理相應的數據。
網絡事件系統調用示例:
using System;
using System.Collections;
using System.Collections.Generic;
using Game.Net;
using ProtoMsg;
using UnityEngine;
/// <summary>
/// 戰鬥監聽器
/// </summary>
public class BattleListener : Singleton<BattleListener>
{
//初始化的方法 監聽戰鬥的網絡消息
public void Init() {
awaitHandle = new Queue<BattleUserInputS2C>();
NetEvent.Instance.AddEventListener(1500, HandleBattleUserInputS2C);
}
Queue<BattleUserInputS2C> awaitHandle;
//處理存儲網絡事件的方法
private void HandleBattleUserInputS2C(BufferEntity response)
{
BattleUserInputS2C s2cMSG = ProtobufHelper.FromBytes<BattleUserInputS2C>(response.proto);
awaitHandle.Enqueue(s2cMSG);
}
//釋放的方法 移除監聽網絡消息
public void Relese()
{
NetEvent.Instance.RemoveEventListener(1500, HandleBattleUserInputS2C);
awaitHandle.Clear();
}
//調度/播放網絡事件的方法
public void PlayerFrame(Action<BattleUserInputS2C> action) {
if (action!=null&& awaitHandle.Count>0)
{
action(awaitHandle.Dequeue());
}
}
}