1:前言
Socket通信中,客戶端與服務器之間傳遞的是字節流。而在現實的應用中我們需要傳遞有一定含義的結構。如何傳遞有意義的結構那?別慌本文就從這里給您做個簡單介紹。
首先我們來簡單認識一下今天的主角:JSON.NET和
ProtoBuf
[發現這篇博文寫的還不錯,特轉載分享給大家!]
2:JSON.NET與ProtoBuf
這兩個都是開源的項目,項目的地址如下
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
讀 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)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下傳遞對象效率簡單對比
我們就來看從發送開始到收完數據接收,兩個框架傳遞不同數量對象所消耗的時間。
作者:大兵
