好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

JSON.NET与ProtoBuf在Socket下的应用

JSON.NET与ProtoBuf在Socket下的应用

JSON.NET与ProtoBuf在Socket下的应用

:前言

Socket通信中,客户端与服务器之间传递的是字节流。而在现实的应用中我们需要传递有一定含义的结构。如何传递有意义的结构那?别慌本文就从这里给您做个简单介绍。

首先我们来简单认识一下今天的主角:JSON.NET和ProtoBuf

[发现这篇博文写的还不错,特转载分享给大家!]

2:JSON.NET与ProtoBuf

这两个都是开源的项目,项目的地址如下

JSON.NET: http://json.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=29756

ProtoBuf: http://code.google.com/p/protobuf/

接下来我们看看两个项目在序列化对象时都是怎么做的。

先看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( " 写入耗时(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 {  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( " 写入耗时(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

以上表格中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) 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>
         ///   发送或接受操作
         ///   </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 {  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);

                 //  获取消息字符串实际应有的长度
                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

 

后记

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

作者:大兵

 

 

 

标签:  TCP/IP ,  Sockets、Json

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于JSON.NET与ProtoBuf在Socket下的应用的详细内容...

  阅读:55次