首先我们来简单认识一下今天的主角:JSON.NET和ProtoBuf
1:JSON.NET与ProtoBuf
这两个都是开源的项目,项目的地址如下
JSON.NET:http://json.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=29756
ProtoBuf:http://code.google.com/p/protobuf/
接下来我们看看两个项目在序列化对象时都是怎么做的。
先看JSON.NET
- Code
- [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 list;
- public Json(int num) {
- list = new List();
- 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() {
- // TypeNameHandlingTypeNameHandling = TypeNameHandling.Objects
- //});
- Stopwatch watch = new Stopwatch();
- watch.Start();
- jsonStr = JsonConvert.SerializeObject(list);
- watch.Stop();
- Console.WriteLine("写入耗时(MS):" + watch.ElapsedMilliseconds);
- }
- public List Get()
- {
- //object person = JsonConvert.DeserializeObject(jsonStr, null, new JsonSerializerSettings {
- // TypeNameHandlingTypeNameHandling = TypeNameHandling.Objects
- //});
- Stopwatch watch = new Stopwatch();
- watch.Start();
- List obj = JsonConvert.DeserializeObject>(jsonStr);
- watch.Stop();
- Console.WriteLine("获取耗时(MS):" + watch.ElapsedMilliseconds);
- return obj;
- }
- #endregion
我们可以看到它对序列化的对象没有什么要求。(“[JsonObject]”可以去掉)
其实JSON的原理也很简单,底层通过反射获取对象的属性然后拼接出形如[{"userName":"dabing","pwd":"110110"},{"userName":"dabing","pwd":"110110"}]的字符串。
下面我们看ProtoBuf
- 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 Protobuf {
- MemoryStream ms;
- List list;
- public Protobuf(int num) {
- ms = new MemoryStream();
- list = new List();
- 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 Get() {
- ms.Position = 0;
- Stopwatch watch = new Stopwatch();
- watch.Start();
- List obj=Serializer.Deserialize>(ms);
- watch.Stop();
- Console.WriteLine("获取耗时(MS):" + watch.ElapsedMilliseconds);
- return obj;
- }
- #endregion
ProtoBuf对要序列化的对象要求首先要有特性来规定像[DataContract],[ProtoMember(1)]其次就是不能有带参的构造函数。
2: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下是如何应用的。
3:JSON.NET与ProtoBuf在Socket下的写法
以JSON方式传递对象数组
- Code
- 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 obj = JsonConvert.DeserializeObject>(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();
- }
- 客户端
- Code
- public sealed class SocketClient : IDisposable {
- RequestHandler handler;
- ///
- /// 发送或接受操作
- ///
- private const Int32 ReceiveOperation = 1, SendOperation = 0;
- ///
- /// 客户端套接字
- ///
- private Socket clientSocket;
- ///
- /// 是否链接到服务器
- ///
- private Boolean connected = false;
- ///
- /// 接收端口{本地}
- ///
- private IPEndPoint hostEndPoint;
- ///
- /// 连接信号量
- ///
- private AutoResetEvent autoConnectEvent = new AutoResetEvent(false);
- ///
- /// 操作信号量
- ///
- private AutoResetEvent[] autoSendReceiveEvents = new AutoResetEvent[]
- {
- new AutoResetEvent(false),
- new AutoResetEvent(false)
- };
- public static object ConnLock = new object();
- ///
- /// 初始化客户端
- /// 链接到服务器后开始发送数据
- ///
- /// 服务端地址{IP地址}
- /// 端口
- 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();
- }
- ///
- /// 连接到服务器过程
- ///
- /// 连接上为True否则为False
- public void Connect() {
- lock (ConnLock) {
- try {
- clientSocket.Connect(this.hostEndPoint);
- this.connected = true;
- }
- catch (Exception ex) {
- this.connected = false;
- }
- }
- }
- ///
- /// 断开与服务器的链接
- ///
- public void Disconnect() {
- clientSocket.Disconnect(false);
- }
- private void OnConnect(object sender, SocketAsyncEventArgs e) {
- // 通知连接已经完成
- autoConnectEvent.Set();
- this.connected = (e.SocketError == SocketError.Success);
- }
- ///
- /// 接收
- ///
- ///
- ///
- 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 obj = JsonConvert.DeserializeObject>(m);
- foreach (Entitly.Person p in obj) {
- Console.WriteLine(p.userName);
- }
- }
- autoSendReceiveEvents[SendOperation].Set();
- (e.UserToken as Socket).ReceiveAsync(e);
- }
- ///
- /// 发送
- ///
- ///
- ///
- 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(OnReceive);
- s.ReceiveAsync(e);
- }
- }
- else {
- this.ProcessError(e);
- }
- }
- ///
- /// 关闭客户端
- ///
- /// SocketAsyncEventArg
- 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);
- }
- ///
- /// 发送过程
- ///
- /// Message to send.
- /// Message sent by the host.
- 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(OnSend);
- clientSocket.SendAsync(completeArgs);
- AutoResetEvent.WaitAll(autoSendReceiveEvents);
- }
- else {
- throw new SocketException((Int32)SocketError.NotConnected);
- }
- }
- #region IDisposable Members
- ///
- /// 销毁客户端
- ///
- 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 list = new List();
- 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();
- }
- 还有实体类
- Code
- 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 {
- ///
- /// 存放没有接受完的部分消息
- ///
- private string temp = string.Empty;
- ///
- /// 获取消息
- ///
- ///
- ///
- public string[] GetActualString(string input) {
- return GetActualString(input, null);
- }
- private string[] GetActualString(string input, List outputList) {
- if (outputList == null)
- outputList = new List();
- 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) {
- // 如果之后的长度大于应有的长度,
- // 说明消息发完整了,但是有多余的数据
- // 多余的数据可能是截断消息,也可能是多条完整消息
- // 截取字符串
- outputoutput = output.Substring(0, length);
- outputList.Add(output);
- temp = "";
- // 缩短input的长度
- inputinput = input.Substring(startIndex + length);
- // 递归调用
- GetActualString(input, outputList);
- }
- }
- else { // 说明“[”,“]”就不完整
- temp = input;
- }
- return outputList.ToArray();
- }
以ProtoBuf方式传递对象数组
服务端
- Code
- 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 cust = Serializer.DeserializeWithLengthPrefix>(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();
- 客户端
- Code
- public class Client {
- public void Send(int num) {
- Stopwatch watch = new Stopwatch();
- PBPerson p = new PBPerson();
- p.userName = "dabing";
- p.pwd = "110110";
- List list = new List();
- 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 newCust = Serializer.DeserializeWithLengthPrefix>(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本身具有很多与通信相关的特性。
有了以上写法,我们再来看看两个框架再传递对象时的相率对比
4:JSON.NET与ProtoBuf在Socket下传递对象效率简单对比
我们就来看从发送开始到收完数据接收,两个框架传递不同数量对象所消耗的时间。
100(J/P) 1000(J/P) 10000(J/P)
json/proto 97/264 150/143 2202/366
本文来自王辉的博客园博文《JSON.NET与ProtoBuf在Socket下的应用》
【编辑推荐】