JSON.NET与ProtoBuf在Socket下的应用

1:前言

Socket通信中,客户端与服务器之间传递的是字节流。而在现实的应用中我们需要传递有一定含义的结构。如何传递有意义的结构那?别慌本文就从这里给您做个简单介绍。
首先我们来简单认识一下今天的主角:JSON.NET和 ProtoBuf
[发现这篇博文写的还不错,特转载分享给大家!]

2:JSON.NET与ProtoBuf

这两个都是开源的项目,项目的地址如下
接下来我们看看两个项目在序列化对象时都是怎么做的。
先看 JSON.NET

    [JsonObject]
    
public class Person {
        
public string userName { getset; }
        
public string pwd { getset; }

        
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("写入耗时(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("获取耗时(MS):" + watch.ElapsedMilliseconds);
            
return obj;

        }
        
#endregion

我们可以看到它对序列化的对象没有什么要求。(“[JsonObject]”可以去掉)
其实JSON的原理也很简单,底层通过反射获取对象的属性然后拼接出形如[{"userName":"dabing","pwd":"110110"},{"userName":"dabing","pwd":"110110"}]的字符串。
下面我们看 ProtoBuf

    [DataContract]
    
public class PBPerson {

        [ProtoMember(1)]
        
public string userName { getset; }

        [ProtoMember(2)]
        
public string pwd { getset; }

        
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("写入耗时(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("获取耗时(MS):" + watch.ElapsedMilliseconds);
            
return obj;
        }
        
#endregion
ProtoBuf对要序列化的对象要求首先要有特性来规定像[DataContract],[ProtoMember(1)]其次就是不能有带参的构造函数。

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个对象在JSON/ ProtoBuf下耗费的MS。
以上数据为三次得到的平均值。

从以上数据我们可以简单得出结论(仅供参考)
传递的对象越多两者耗费的时间越长。
传递单个对象的时候 JSON表现出更好的性能。传递多个对象的时候 ProtoBuf性能更快更稳定。
到这里我们已经把两种框架下序列化和反序列化对象的方法和性能进行了简单的说明,接下来我们再看看两个框架在Socket下是如何应用的。

4:JSON.NET与ProtoBuf在Socket下的写法

以JSON方式传递对象数组

    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)27false);
                
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>
        
///  发送或接受操作
        
/// </summary>
        private const Int32 ReceiveOperation = 1, SendOperation = 0;

        
/// <summary>
        
/// 客户端套接字
        
/// </summary>
        private Socket clientSocket;

        
/// <summary>
        
/// 是否链接到服务器
        
/// </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>
        
/// 初始化客户端
        
/// 链接到服务器后开始发送数据
        
/// </summary>
        
/// <param name="hostName">服务端地址{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>
        
/// 连接到服务器过程
        
/// </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>
        
/// 断开与服务器的链接
        
/// </summary>
        public void Disconnect() {
            clientSocket.Disconnect(false);
        }

        
private void OnConnect(object sender, SocketAsyncEventArgs e) {
            
// 通知连接已经完成
            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>
        
/// 发送
        
/// </summary>
        
/// <param name="sender"></param>
        
/// <param name="e"></param>
        private void OnSend(object sender, SocketAsyncEventArgs e) {
            
//发送完后置信号为接收
            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>
        
/// 关闭客户端
        
/// </summary>
        
/// <param name="e">SocketAsyncEventArg</param>
        private void ProcessError(SocketAsyncEventArgs e) {
            Socket s = e.UserToken as Socket;
            
if (s.Connected) {
                
//关闭一个独立的客户端连接
                try {
                    s.Shutdown(SocketShutdown.Both);
                }
                
catch (Exception) {
                    
//客户端已经关闭
                }
                
finally {
                    
if (s.Connected) {
                        s.Close();
                    }
                }
            }

            
throw new SocketException((Int32)e.SocketError);
        }

        
/// <summary>
        
/// 发送过程
        
/// </summary>
        
/// <param name="message">Message to send.</param>
        
/// <returns>Message sent by the host.</returns>
        public void Send(String message) {
            
if (this.connected) {

                
//将信息转化为协议
                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();

        }
还有实体类

    public class Person {
        
public string userName { getset; }
        
public string pwd { getset; }

        
public Person(string name, string code) {
            userName = name;
            pwd = code;
        }
    }

    [DataContract]
    
public class PBPerson {

        [ProtoMember(1)]
        
public string userName { getset; }

        [ProtoMember(2)]
        
public string pwd { getset; }

        
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);

                
// 获取消息字符串实际应有的长度
                length = Convert.ToInt32(m.Groups[0].Value);

                
// 获取需要进行截取的位置
                int startIndex = input.IndexOf(']'+ 1;

                
// 获取从此位置开始后所有字符的长度
                output = input.Substring(startIndex);

                
if (output.Length == length) {
                    
// 如果output的长度与消息字符串的应有长度相等
                    
// 说明刚好是完整的一条信息
                    outputList.Add(output);
                    temp = "";
                }
                
else if (output.Length < length) {
                    
// 如果之后的长度小于应有的长度,
                    
// 说明没有发完整,则应将整条信息,包括元数据,全部缓存
                    
// 与下一条数据合并起来再进行处理
                    temp = input;
                    
// 此时程序应该退出,因为需要等待下一条数据到来才能继续处理

                }
                
else if (output.Length > length) {
                    
// 如果之后的长度大于应有的长度,
                    
// 说明消息发完整了,但是有多余的数据
                    
// 多余的数据可能是截断消息,也可能是多条完整消息

                    
// 截取字符串
                    output = output.Substring(0, length);
                    outputList.Add(output);
                    temp = "";

                    
// 缩短input的长度
                    input = input.Substring(startIndex + length);

                    
// 递归调用
                    GetActualString(input, outputList);
                }
            }
            
else {    // 说明“[”,“]”就不完整
                temp = input;
            }

            
return outputList.ToArray();
        }
以ProtoBuf方式传递对象数组
服务端

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("获取到数据");
                    List<PBPerson> cust = Serializer.DeserializeWithLengthPrefix<List<PBPerson>>(stream, PrefixStyle.Base128);
                    Console.WriteLine("返回数据");
                    
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("获取连接发送数据");
                    watch.Start();
                    Serializer.SerializeWithLengthPrefix(stream, list, PrefixStyle.Base128);

                    
//Console.WriteLine("获取数据");
                    
                    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本身具有很多与通信相关的特性。
有了以上写法,我们再来看看两个框架再传递对象时的相率对比

5:JSON.NET与ProtoBuf在Socket下传递对象效率简单对比

我们就来看从发送开始到收完数据接收,两个框架传递不同数量对象所消耗的时间。
100(J/P) 1000(J/P) 10000(J/P)
json/proto 97/264 150/143 2202/366

后记
按照惯例,附上本文涉及的所有源代码

作者:大兵

你可能感兴趣的:(protobuf)