Beetle雖然提供了性能出色的二進制序列化功能,但畢竟需要用戶通過writer和reader的方法來手動描述過程;事實上計較這些性能的場景並不多,很多時候一個自動序列化功能對程序的編寫和維護都起到極其方便的作用。在設計的時候組件是通過接口的方式來描述消息讀寫操作,因此在擴展對Protobuf支持也是比較方便的。
Protobuf則Googler制定的一種對象序列化和反序列化方案,他在c++,java,net等不同語言平台都有相關的實現。而在.net下的實現分別有protobuf-net和protobuf-csharp-port;在這里選擇了protobuf-net。從測試來看protobuf-net序列化體積和性能都比較出色,詳細可以查看:http://www.servicestack.net/benchmarks/NorthwindDatabaseRowsSerialization.1000000-times.2010-02-06.html
由於組件只支持實現了IMessage的對象進行處理,所以必須對protobuf-net序列化進行一個IMessage的實現。protobuf-net序列化的信息緊緊是對象的內容不包括對象類型信息在內,簡單的說就是protobuf-net是無法根據序列化的信息來反序列化對象,在反序列化的過程必須明確指定這些數據是基於那個類型對象,因此用protobuf-net序列化對象進行網絡傳輸還需制定一個簡單協議。以下實現一個MessageAdapter來使用Protobuf.net序列化對象進行對象數據傳輸。
以下是實現一個基於protobuf-net的Message
public class ProtobufAdapter:IMessage { public object Message { get; set; } public static bool Send(Beetle.TcpChannel channel, object msg) { ProtobufAdapter adapter = new ProtobufAdapter(); adapter.Message = msg; return channel.Send(adapter); } public void Load(Beetle.BufferReader reader) { string type = reader.ReadString(); Beetle.ByteArraySegment segment = mArrayPool.Pop(); reader.ReadByteArray(segment); using (System.IO.Stream stream = new System.IO.MemoryStream(segment.Array,0,segment.Count)) { Message = ProtoBuf.Meta.RuntimeTypeModel.Default.Deserialize(stream, null, Type.GetType(type)); } mArrayPool.Push(segment); } public void Save(Beetle.BufferWriter writer) { writer.Write(Message.GetType().FullName); Beetle.ByteArraySegment segment = mArrayPool.Pop(); using(System.IO.Stream stream = new System.IO.MemoryStream(segment.Array)) { ProtoBuf.Meta.RuntimeTypeModel.Default.Serialize(stream, Message); segment.SetInfo(0, (int)stream.Position); } writer.Write(segment); mArrayPool.Push(segment); } private static ByteArrayPool mArrayPool = new ByteArrayPool(100, 1024 * 8); }
協議的封裝很簡單,在寫入 Protobuf序列化對象之前先寫入一個帶頭長度描述的消息類型名稱,然后再寫入一個帶頭長度描述的Protobuf對象序列信息流。而在讀取的時候則先把類型描述名稱讀取出來,然后構建相關名稱的類型結合信息流反序列化成具體的對象。
接下來是針對這個消息實現一個基於頭4字節描述大小的協義分包器
public class HeadSizePackage:Beetle.HeadSizeOfPackage { public HeadSizePackage(Beetle.TcpChannel channel) : base(channel) { } protected override Beetle.IMessage ReadMessageByType(Beetle.BufferReader reader, out object typeTag) { typeTag = "ProtobufAdapter"; return new ProtobufAdapter(); } protected override void WriteMessageType(Beetle.IMessage msg, Beetle.BufferWriter writer) { } }
通過以上協議封裝后,具體所產生持協議如下
協議制定后就可似使用Beetle搭建基於protobuf序列化的對象傳輸了。
首先是制定一個Tcp服務
TcpUtils.Setup(200, 1, 1); TcpServer server = new TcpServer(); server.ChannelConnected += OnConnected; server.ChannelDisposed += OnDisposed; server.Open(8340);
以上代碼很簡單初始化組件信息,構建一個TcpServer並綁定連接接入事件和連接斷開事件;然后在所有IP的8340端綁定tcp服務。在連接接入的時候我們需要做些事情。
static void OnConnected(object sender, ChannelEventArgs e) { e.Channel.SetPackage<Messages.HeadSizePackage>().ReceiveMessage = OnMessageReceive; e.Channel.ChannelError += OnError; e.Channel.BeginReceive(); Console.WriteLine("{0} connected!", e.Channel.EndPoint); }
在接入的事件里針對當前的Tcp通道設置一個協議分包器,並指定對應接收消息事件;同樣給Tcp通道綁定一件錯誤事件,在消息事件處理過程中只要你不開啟線程去處理邏輯,其過程發生的異常都會觸發這個事件;不必擔心異常會影響整個服這方面組件都做到足夠安全保證服務可以不間斷運行。Tcp通道相關信息設置完成后就可以調用BeginReceive()方法進入數據接收狀態。接下來是消息處理事件的代碼:
static void OnMessageReceive(PacketRecieveMessagerArgs e) { Messages.ProtobufAdapter adapter = (Messages.ProtobufAdapter)e.Message; if (adapter.Message is Messages.Register) { OnRegister(e.Channel, (Messages.Register)adapter.Message); } else if (adapter.Message is Messages.Query) { OnQuery(e.Channel, (Messages.Query)adapter.Message); } else{ } } static void OnRegister(Beetle.TcpChannel channel, Messages.Register e) { Console.WriteLine("{0} Register\t UserName:{1};PWD:{2};EMail:{3}", channel.EndPoint, e.UserName, e.PWD, e.EMail); } static void OnQuery(Beetle.TcpChannel channel, Messages.Query e) { Console.WriteLine("{0} Query \t Customer:{1}", channel.EndPoint, e.CustomerID); Messages.Order order = new Messages.Order(); order.OrderID = 10248; order.CustomerID = "WILMK"; order.EmployeeID = 5; order.OrderDate = 629720352000000000; order.RequiredDate = 629744544000000000; order.ShipAddress = "59 rue de l'Abbaye"; order.ShipCity = "Reims"; order.ShipCountry = "France"; order.ShipName = "Vins et alcools Chevalier"; order.ShipPostalCode = "51100"; order.ShipRegion = "RJ"; Messages.ProtobufAdapter.Send(channel, order); }
在這個例子中只處理了兩種消息對象,分別是Register和Query;接收到Register只做了一個簡單的輸出,而在接收到Query則會返回一個Order對象。
Client實現
Client要做的事件是先接入這個服務
private void cmdConnect_Click(object sender, EventArgs e) { try { mChannel = TcpServer.CreateClient(txtIPAddress.Text, 8340); mChannel.SetPackage<Messages.HeadSizePackage>().ReceiveMessage = ReceiveMessage; mChannel.ChannelDisposed += OnDisplsed; mChannel.BeginReceive(); cmdRegister.Enabled = true; cmdQuery.Enabled = true; cmdConnect.Enabled = false; } catch (Exception e_) { MessageBox.Show(e_.Message); } }
在連接創建成功后,同樣要做的事情就是設置分包對象和消息接收事件,完成后就調用BeginReceive()連接進行接收數據狀態。這個時候我們就可以向服務器發送相關對象。
private void cmdRegister_Click(object sender, EventArgs e) { Messages.Register reg = new Messages.Register(); reg.EMail = txtEMail.Text; reg.PWD = txtPWD.Text; reg.UserName = txtUserName.Text; Messages.ProtobufAdapter.Send(mChannel, reg); } private void cmdQuery_Click(object sender, EventArgs e) { Messages.Query query = new Messages.Query(); query.CustomerID = txtCustomerID.Text; Messages.ProtobufAdapter.Send(mChannel, query); }
這樣通過Beetle對Protobuf.net序列化對象進行傳輸的例子就完成了
C#游戲服務器MMRPG交流群:136485198