每次編寫設計網絡通訊程式時,總面對一個問題,就是要自定義一組應用協定(即通訊協定),然後再寫相應的方法來解析協定,并提供相應的接口供上層調用。如果隻是簡單的文本資訊通訊還容易,但要交換一些控制資訊或結構複雜的資料時,比如做聯機遊戲,更是讓人頭疼。
最近突然想到一個點子,可以用對象串行化技術将對象直接轉換為二進制資料發送,然後接收時直接還原為對象。具體過程是,将要發送的資料放在一個HashTable中,串行化後發送出去,在接收方接收到資料并還原為HashTable,根據預先約定好的Key和擷取自己關心的資料。在這種情況下,定義通訊協定的内容實質上也就隻是指定一組Key就行了。再也不用做那些規定第幾個字段是什麼類型有多長的煩躁的事情了。
可能很多人很善于用XML,也将之廣泛用于網絡通訊。XML有不可比拟的好處,因為它是同平台無關的,而且基本上任何開發語言都有現成庫來解析XML。這個和我的觀點并不沖突。對象串行化并不局限于二進制資料。C#裡有豐富的方法,可以将對象串行化為XML文檔,也支援用SOAP協定來串行化資料。是以隻要用公共的串行化标準來串行化對象,也可以達到跨平台、跨語言的目的。其實作在流行的Web Service其核心技術也就大概是這樣吧。
原理說完了,貼段代碼做個例子。ObjectTransferClient(簡稱OTC)是一個利用UDP協定及二進制對象串行化的包括對象發送和接收的庫。調用方法很簡單,用Send發送對象,響應ReceiveObject事件來處理接收的對象。至于具體細節就不多叙述了,相信有一定C#基礎的人能輕松看懂的。
這一原理的應用潛力是巨大的,我在這裡抛磚引玉,還請指教。
using System;
using System.Net.Sockets;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
namespace OTC
{
/// <summary>
/// 對象傳送器,使用UDP協定通過網路在不同主機間傳送對象。
/// </summary>
public class ObjectTransferClient : IDisposable
{
private Thread ListenThread;
private BinaryFormatter Serializer = new BinaryFormatter();
private int m_Port;
private UdpClient m_Client;
private bool m_IsStart;
private bool m_IsConnected;
/// <summary>
/// 接收到對象事件
/// </summary>
public event ReceiveObjectEventHandler ReceiveObject;
/// <summary>
/// 建構一個對象傳送器,預設端口7321
/// </summary>
public ObjectTransferClient() : this(7321)
{
//
// TODO: 在此處添加構造函數邏輯
//
}
/// <summary>
/// 指定端口号建構一個對象傳送器。
/// </summary>
/// <param name="port">端口号</param>
public ObjectTransferClient(int port)
{
this.m_Port = port;
this.m_IsConnected = false;
this.m_IsStart = false;
}
/// <summary>
/// 初始化傳送器并開始工作
/// </summary>
public void Start()
{
if (!this.m_IsStart)
{
this.m_Client = new UdpClient(this.m_Port);
ListenThread = new Thread(new ThreadStart(this.Listen));
ListenThread.Start();
this.m_IsStart = true;
}
}
/// <summary>
/// 使用指定的主機名和端口連接配接預設的遠端主機
/// </summary>
/// <param name="hostname">主機名</param>
/// <param name="port">端口</param>
public void Connect(string hostname, int port)
{
this.m_Client.Connect(hostname, port);
}
/// <summary>
/// 使用指定的IP位址和端口連接配接預設的遠端主機
/// </summary>
/// <param name="ipaddress">IP位址</param>
/// <param name="port">端口</param>
public void Connect(IPAddress ipaddress, int port)
{
this.m_Client.Connect(ipaddress, port);
}
/// <summary>
/// 使用網絡終結點連接配接預設的遠端主機
/// </summary>
/// <param name="iep">網絡端點</param>
public void Connect(IPEndPoint iep)
{
this.m_Client.Connect(iep);
}
private byte[] CreateArgPackage(object obj)
{
IPEndPoint local = new IPEndPoint(IPAddress.Any, this.m_Port);
System.IO.MemoryStream outStream = new System.IO.MemoryStream();
this.Serializer.Serialize(outStream, new ReceiveObjectEventArgs(obj, local));
return outStream.ToArray();
}
/// <summary>
/// 将對象發送到預設主機。調用此方法前必須先調用Connect方法連接配接預設主機。
/// </summary>
/// <param name="obj">要發送的對象</param>
public void Send(object obj)
{
if (this.IsConnected)
{
byte[] data = this.CreateArgPackage(obj);
this.m_Client.Send(data, data.Length);
}
else
{
throw new Exception("必須先連接配接預設主機");
}
}
/// <summary>
/// 将對象發送到指定的主機。若調用了Connect方法連接配接了預設主機,則此方法不可用。
/// </summary>
/// <param name="obj">要發送的對象</param>
/// <param name="remoteIEP">目标主機的網絡端點</param>
public void Send(object obj, IPEndPoint remoteIEP)
{
if (this.IsConnected)
{
throw new Exception("已經連接配接了預設主機");
}
else
{
byte[] data = this.CreateArgPackage(obj);
this.m_Client.Send(data, data.Length, remoteIEP);
}
}
/// <summary>
/// 監聽接收資料線程方法
/// </summary>
protected void Listen()
{
BinaryFormatter Serializer = new BinaryFormatter();
IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
while (true)
{
try
{
object revobj = Serializer.Deserialize(new System.IO.MemoryStream(m_Client.Receive(ref RemoteIpEndPoint)));
ReceiveObjectEventArgs revarg = (ReceiveObjectEventArgs)revobj;
RemoteIpEndPoint.Port = revarg.RemoteIEP.Port;
revarg.RemoteIEP = RemoteIpEndPoint;
if (this.ReceiveObject != null)
{
this.ReceiveObject(this, revarg);
}
}
catch
{
break;
}
}
}
#region 公共屬性區
/// <summary>
/// 傳回或設定接收對象的端口号
/// </summary>
public int Port
{
get
{
return this.m_Port;
}
set
{
this.m_Port = value;
}
}
/// <summary>
/// 傳回對象發送器是否已經初始化并開始工作
/// </summary>
public bool IsStart
{
get
{
return this.m_IsStart;
}
}
/// <summary>
/// 傳回對象發送器是否已經連接配接預設遠端主機
/// </summary>
public bool IsConnected
{
get
{
return this.m_IsConnected;
}
}
#endregion
#region IDisposable 成員
public void Dispose()
{
// TODO: 添加 ObjectTransferClient.Dispose 實作
this.m_Client.Close();
this.ListenThread.Abort();
}
#endregion
}
/// <summary>
/// 接收對象事件參數
/// </summary>
[Serializable]
public class ReceiveObjectEventArgs : EventArgs
{
private object _obj;
private System.Net.IPEndPoint _iep;
/// <summary>
/// 建構一個接收對象事件的參數
/// </summary>
/// <param name="obj">接收到的對象</param>
/// <param name="iep">發送者的網絡端點</param>
internal ReceiveObjectEventArgs(object obj, System.Net.IPEndPoint iep)
{
this._obj = obj;
this._iep = iep;
}
/// <summary>
/// 建構一個空的接收對象事件參數
/// </summary>
public ReceiveObjectEventArgs():this(null, null)
{
}
/// <summary>
/// 接收到的對象
/// </summary>
public object Obj
{
get
{
return this._obj;
}
}
/// <summary>
/// 發送方的網絡端點
/// </summary>
public System.Net.IPEndPoint RemoteIEP
{
get
{
return this._iep;
}
set
{
this._iep = value;
}
}
}
/// <summary>
/// 接收對象事件的委托
/// </summary>
public delegate void ReceiveObjectEventHandler(object sender, ReceiveObjectEventArgs e);
}