1:前言 Socket通信中,客戶端與服務(wù)器之間傳遞的是字節(jié)流。而在現(xiàn)實(shí)的應(yīng)用中我們需要傳遞有一定含義的結(jié)構(gòu)。如何傳遞有意義的結(jié)構(gòu)那?別慌本文就從這里給您做個(gè)簡單介紹。
首先我們來簡單認(rèn)識一下今天的主角:JSON.NET和ProtoBuf
2:JSON.NET與ProtoBuf 這兩個(gè)都是開源的項(xiàng)目,項(xiàng)目的地址如下
ProtoBuf:http://code.google.com/p/protobuf/
接下來我們看看兩個(gè)項(xiàng)目在序列化對象時(shí)都是怎么做的。
先看JSON.NET
[JsonObject] public class Person { public string userName { get; set; } public string pwd { get; set; } public Person(string name, string code) { userName = name; pwd = code; } public void Write() { Console.WriteLine(string.Format("用戶名:" + userName + "密碼:" + pwd)); } } public class Json { private string jsonStr; private List<Person> list; public Json(int num) { list = new List<Person>(); Person p = new Person("dabing", "110110"); for (int i = 0; i < num;i++ ) list.Add(p); } #region json public void Set() { //jsonStr = JsonConvert.SerializeObject(list, Formatting.Indented, new JsonSerializerSettings() { // TypeNameHandling = TypeNameHandling.Objects //}); Stopwatch watch = new Stopwatch(); watch.Start(); jsonStr = JsonConvert.SerializeObject(list); watch.Stop(); Console.WriteLine("寫入耗時(shí)(MS):" + watch.ElapsedMilliseconds); } public List<Person> Get() { //object person = JsonConvert.DeserializeObject(jsonStr, null, new JsonSerializerSettings { // TypeNameHandling = TypeNameHandling.Objects //}); Stopwatch watch = new Stopwatch(); watch.Start(); List<Person> obj = JsonConvert.DeserializeObject<List<Person>>(jsonStr); watch.Stop(); Console.WriteLine("獲取耗時(shí)(MS):" + watch.ElapsedMilliseconds); return obj; } #endregion 我們可以看到它對序列化的對象沒有什么要求。(“[JsonObject]”可以去掉)
其實(shí)JSON的原理也很簡單,底層通過反射獲取對象的屬性然后拼接出形如[{"userName":"dabing","pwd":"110110"},{"userName":"dabing","pwd":"110110"}]的字符串。
下面我們看ProtoBuf
[DataContract] public class PBPerson { [ProtoMember(1)] public string userName { get; set; } [ProtoMember(2)] public string pwd { get; set; } public void Write() { Console.WriteLine(string.Format("用戶名:" + userName + "密碼:" + pwd)); } } public class Protobuf { MemoryStream ms; List<PBPerson> list; public Protobuf(int num) { ms = new MemoryStream(); list = new List<PBPerson>(); PBPerson p = new PBPerson(); p.userName = "fengyun"; p.pwd = "110110"; for (int i = 0; i < num; i++) { list.Add(p); } } #region ProtoBuf public void Set() { Stopwatch watch = new Stopwatch(); watch.Start(); Serializer.Serialize(ms,list); watch.Stop(); Console.WriteLine("寫入耗時(shí)(MS):" + watch.ElapsedMilliseconds); } public List<PBPerson> Get() { ms.Position = 0; Stopwatch watch = new Stopwatch(); watch.Start(); List<PBPerson> obj=Serializer.Deserialize<List<PBPerson>>(ms); watch.Stop(); Console.WriteLine("獲取耗時(shí)(MS):" + watch.ElapsedMilliseconds); return obj; } #endregion ProtoBuf對要序列化的對象要求首先要有特性來規(guī)定像[DataContract],[ProtoMember(1)]其次就是不能有帶參的構(gòu)造函數(shù)。
3:JSON.NET與ProtoBuf性能的簡單對比 100(J/P) 1000(J/P) 10000(J/P) 100000(J/P)
寫 53/100 64/104 162/114 1139/239
讀 29/13 64/16 382/42 3561/322
以上表格中100(J/P)表示100個(gè)對象在JSON/ProtoBuf下耗費(fèi)的MS。
以上數(shù)據(jù)為三次得到的平均值。
從以上數(shù)據(jù)我們可以簡單得出結(jié)論(僅供參考):
傳遞的對象越多兩者耗費(fèi)的時(shí)間越長。
傳遞單個(gè)對象的時(shí)候JSON表現(xiàn)出更好的性能。傳遞多個(gè)對象的時(shí)候ProtoBuf性能更快更穩(wěn)定。
到這里我們已經(jīng)把兩種框架下序列化和反序列化對象的方法和性能進(jìn)行了簡單的說明,接下來我們再看看兩個(gè)框架在Socket下是如何應(yīng)用的。
4:JSON.NET與ProtoBuf在Socket下的寫法 以JSON方式傳遞對象數(shù)組
public class SocketServer { RequestHandler handler; Socket listenSocket; public void Start() { IPAddress[] addressList = Dns.GetHostEntry(Environment.MachineName).AddressList; IPEndPoint localEndPoint = new IPEndPoint(addressList[addressList.Length - 1], 12345); this.listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); if (localEndPoint.AddressFamily == AddressFamily.InterNetworkV6) { this.listenSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false); this.listenSocket.Bind(new IPEndPoint(IPAddress.IPv6Any, localEndPoint.Port)); } else { this.listenSocket.Bind(localEndPoint); } this.listenSocket.Listen(100); this.accept_async(); handler = new RequestHandler(); } private void accept_async() { SocketAsyncEventArgs accept = new SocketAsyncEventArgs(); accept.Completed += accept_Completed; listenSocket.AcceptAsync(accept); } void accept_Completed(object sender, SocketAsyncEventArgs e) { accept_async(); var client = e.AcceptSocket; e.Completed -= accept_Completed; e.Completed += receive_Completed; var buffer = new byte[1024]; e.SetBuffer(buffer, 0, buffer.Length); client.ReceiveAsync(e); } void receive_Completed(object sender, SocketAsyncEventArgs e) { var client = sender as Socket; if (e.BytesTransferred == 0) { client.Close(); e.Dispose(); } else { String received = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred); string[] msgArray = handler.GetActualString(received); foreach (string m in msgArray) { List<Entitly.Person> obj = JsonConvert.DeserializeObject<List<Entitly.Person>>(m); foreach (Entitly.Person p in obj) { p.userName = "fengyun"; } received = JsonConvert.SerializeObject(obj); received = String.Format("[length={0}]{1}", received.Length, received); byte[] buffer = Encoding.UTF8.GetBytes(received); client.Send(buffer); } client.ReceiveAsync(e); } } } class Program { static void Main(string[] args) { SocketServer server = new SocketServer(); server.Start(); Console.ReadLine(); } 客戶端
public sealed class SocketClient : IDisposable { RequestHandler handler; /// <summary> /// 發(fā)送或接受操作 /// </summary> private const Int32 ReceiveOperation = 1, SendOperation = 0; /// <summary> /// 客戶端套接字 /// </summary> private Socket clientSocket; /// <summary> /// 是否鏈接到服務(wù)器 /// </summary> private Boolean connected = false; /// <summary> /// 接收端口{本地} /// </summary> private IPEndPoint hostEndPoint; /// <summary> /// 連接信號量 /// </summary> private AutoResetEvent autoConnectEvent = new AutoResetEvent(false); /// <summary> /// 操作信號量 /// </summary> private AutoResetEvent[] autoSendReceiveEvents = new AutoResetEvent[] { new AutoResetEvent(false), new AutoResetEvent(false) }; public static object ConnLock = new object(); /// <summary> /// 初始化客戶端 /// 鏈接到服務(wù)器后開始發(fā)送數(shù)據(jù) /// </summary> /// <param name="hostName">服務(wù)端地址{IP地址}</param> /// <param name="port">端口</param> public SocketClient(String hostName, Int32 port) { IPHostEntry host = Dns.GetHostEntry(hostName); IPAddress[] addressList = host.AddressList; this.hostEndPoint = new IPEndPoint(addressList[addressList.Length - 1], port); this.clientSocket = new Socket(this.hostEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); handler = new RequestHandler(); } /// <summary> /// 連接到服務(wù)器過程 /// </summary> /// <returns>連接上為True否則為False</returns> public void Connect() { lock (ConnLock) { try { clientSocket.Connect(this.hostEndPoint); this.connected = true; } catch (Exception ex) { this.connected = false; } } } /// <summary> /// 斷開與服務(wù)器的鏈接 /// </summary> public void Disconnect() { clientSocket.Disconnect(false); } private void OnConnect(object sender, SocketAsyncEventArgs e) { // 通知連接已經(jīng)完成 autoConnectEvent.Set(); this.connected = (e.SocketError == SocketError.Success); } /// <summary> /// 接收 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnReceive(object sender, SocketAsyncEventArgs e) { string msg = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred); string[] msgArray = handler.GetActualString(msg); foreach (string m in msgArray) { List<Entitly.Person> obj = JsonConvert.DeserializeObject<List<Entitly.Person>>(m); foreach (Entitly.Person p in obj) { Console.WriteLine(p.userName); } } autoSendReceiveEvents[SendOperation].Set(); (e.UserToken as Socket).ReceiveAsync(e); } /// <summary> /// 發(fā)送 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnSend(object sender, SocketAsyncEventArgs e) { //發(fā)送完后置信號為接收 autoSendReceiveEvents[ReceiveOperation].Set(); if (e.SocketError == SocketError.Success) { if (e.LastOperation == SocketAsyncOperation.Send) { Socket s = e.UserToken as Socket; byte[] receiveBuffer = new byte[255]; e.SetBuffer(receiveBuffer, 0, receiveBuffer.Length); e.Completed += new EventHandler<SocketAsyncEventArgs>(OnReceive); s.ReceiveAsync(e); } } else { this.ProcessError(e); } } /// <summary> /// 關(guān)閉客戶端 /// </summary> /// <param name="e">SocketAsyncEventArg</param> private void ProcessError(SocketAsyncEventArgs e) { Socket s = e.UserToken as Socket; if (s.Connected) { //關(guān)閉一個(gè)獨(dú)立的客戶端連接 try { s.Shutdown(SocketShutdown.Both); } catch (Exception) { //客戶端已經(jīng)關(guān)閉 } finally { if (s.Connected) { s.Close(); } } } throw new SocketException((Int32)e.SocketError); } /// <summary> /// 發(fā)送過程 /// </summary> /// <param name="message">Message to send.</param> /// <returns>Message sent by the host.</returns> public void Send(String message) { if (this.connected) { //將信息轉(zhuǎn)化為協(xié)議 message = String.Format("[length={0}]{1}", message.Length, message); Byte[] sendBuffer = Encoding.UTF8.GetBytes(message); SocketAsyncEventArgs completeArgs = new SocketAsyncEventArgs(); completeArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length); completeArgs.UserToken = this.clientSocket; completeArgs.RemoteEndPoint = this.hostEndPoint; completeArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSend); clientSocket.SendAsync(completeArgs); AutoResetEvent.WaitAll(autoSendReceiveEvents); } else { throw new SocketException((Int32)SocketError.NotConnected); } } #region IDisposable Members /// <summary> /// 銷毀客戶端 /// </summary> public void Dispose() { this.connected = false; autoConnectEvent.Reset(); autoSendReceiveEvents[SendOperation].Reset(); autoSendReceiveEvents[ReceiveOperation].Reset(); if (this.clientSocket.Connected) { this.clientSocket.Close(); } } #endregion } class Program { static void Main(string[] args) { String host = "192.168.65.35"; Int32 port = 12345; int num = 100; Entitly.Person person = new Entitly.Person("dabing", "110110"); List<Entitly.Person> list = new List<Entitly.Person>(); for (int i = 0; i < num; i++) { list.Add(person); } string msg = JsonConvert.SerializeObject(list); using (SocketClient sa = new SocketClient(host, port)) { sa.Connect(); sa.Send(msg); sa.Disconnect(); } Console.ReadLine(); } 還有實(shí)體類
public class Person { public string userName { get; set; } public string pwd { get; set; } public Person(string name, string code) { userName = name; pwd = code; } } [DataContract] public class PBPerson { [ProtoMember(1)] public string userName { get; set; } [ProtoMember(2)] public string pwd { get; set; } public void Write() { Console.WriteLine(string.Format("用戶名:" + userName + "密碼:" + pwd)); } } public class RequestHandler { /// <summary> /// 存放沒有接受完的部分消息 /// </summary> private string temp = string.Empty; /// <summary> /// 獲取消息 /// </summary> /// <param name="input"></param> /// <returns></returns> public string[] GetActualString(string input) { return GetActualString(input, null); } private string[] GetActualString(string input, List<string> outputList) { if (outputList == null) outputList = new List<string>(); if (!String.IsNullOrEmpty(temp)) input = temp + input; string output = ""; string pattern = @"(?<=^\[length=)(\d+)(?=\])"; int length; if (Regex.IsMatch(input, pattern)) { Match m = Regex.Match(input, pattern); // 獲取消息字符串實(shí)際應(yīng)有的長度 length = Convert.ToInt32(m.Groups[0].Value); // 獲取需要進(jìn)行截取的位置 int startIndex = input.IndexOf(']') + 1; // 獲取從此位置開始后所有字符的長度 output = input.Substring(startIndex); if (output.Length == length) { // 如果output的長度與消息字符串的應(yīng)有長度相等 // 說明剛好是完整的一條信息 outputList.Add(output); temp = ""; } else if (output.Length < length) { // 如果之后的長度小于應(yīng)有的長度, // 說明沒有發(fā)完整,則應(yīng)將整條信息,包括元數(shù)據(jù),全部緩存 // 與下一條數(shù)據(jù)合并起來再進(jìn)行處理 temp = input; // 此時(shí)程序應(yīng)該退出,因?yàn)樾枰却乱粭l數(shù)據(jù)到來才能繼續(xù)處理 } else if (output.Length > length) { // 如果之后的長度大于應(yīng)有的長度, // 說明消息發(fā)完整了,但是有多余的數(shù)據(jù) // 多余的數(shù)據(jù)可能是截?cái)嘞ⅲ部赡苁嵌鄺l完整消息 // 截取字符串 output = output.Substring(0, length); outputList.Add(output); temp = ""; // 縮短input的長度 input = input.Substring(startIndex + length); // 遞歸調(diào)用 GetActualString(input, outputList); } } else { // 說明“[”,“]”就不完整 temp = input; } return outputList.ToArray(); } 以ProtoBuf方式傳遞對象數(shù)組
服務(wù)端
public class Service { public void Start() { TcpListener listener = new TcpListener(IPAddress.Parse("192.168.65.35"), 12345); listener.Start(); while (true) { TcpClient client = listener.AcceptTcpClient(); ClientConnected(client); } } void ClientConnected(TcpClient client) { try { using (NetworkStream stream = client.GetStream()) { Console.WriteLine("獲取到數(shù)據(jù)"); List<PBPerson> cust = Serializer.DeserializeWithLengthPrefix<List<PBPerson>>(stream, PrefixStyle.Base128); Console.WriteLine("返回?cái)?shù)據(jù)"); foreach (PBPerson p in cust) { p.userName = "fengyun"; } Serializer.SerializeWithLengthPrefix(stream, cust, PrefixStyle.Base128); int final = stream.ReadByte(); if (final == 123) { Console.WriteLine("SERVER: Got client-happy marker"); } else { Console.WriteLine("SERVER: OOPS! Something went wrong"); } Console.WriteLine("SERVER: Closing connection"); stream.Close(); client.Close(); } } finally { } } } ----------------我是猥瑣的分割線--------------------------- Service ser = new Service(); 客戶端
public class Client { public void Send(int num) { Stopwatch watch = new Stopwatch(); PBPerson p = new PBPerson(); p.userName = "dabing"; p.pwd = "110110"; List<PBPerson> list = new List<PBPerson>(); for (int i = 0; i < num; i++) { list.Add(p); } using (TcpClient client = new TcpClient()) { client.Connect(new IPEndPoint(IPAddress.Parse("192.168.65.35"), 12345)); using (NetworkStream stream = client.GetStream()) { //Console.WriteLine("獲取連接發(fā)送數(shù)據(jù)"); watch.Start(); Serializer.SerializeWithLengthPrefix(stream, list, PrefixStyle.Base128); //Console.WriteLine("獲取數(shù)據(jù)"); List<PBPerson> newCust = Serializer.DeserializeWithLengthPrefix<List<PBPerson>>(stream, PrefixStyle.Base128); watch.Stop(); Console.WriteLine(watch.ElapsedMilliseconds); //foreach (PBPerson per in newCust) { // Console.WriteLine(per.userName); //} stream.WriteByte(123); // just to show all bidirectional comms are OK stream.Close(); } client.Close(); } } } ----------------------我是猥瑣的分割線---------------------- Client c = new Client(); c.Send(10000 我們從代碼中可以看到,ProtoBuf本身具有很多與通信相關(guān)的特性。
有了以上寫法,我們再來看看兩個(gè)框架再傳遞對象時(shí)的相率對比
5:JSON.NET與ProtoBuf在Socket下傳遞對象效率簡單對比 我們就來看從發(fā)送開始到收完數(shù)據(jù)接收,兩個(gè)框架傳遞不同數(shù)量對象所消耗的時(shí)間。
100(J/P) 1000(J/P) 10000(J/P)
json/proto 97/264 150/143 2202/366
后記
按照慣例,附上本文涉及的所有源代碼。
(###) |
|