天天看點

輕松定義自己的網絡通訊協定

每次編寫設計網絡通訊程式時,總面對一個問題,就是要自定義一組應用協定(即通訊協定),然後再寫相應的方法來解析協定,并提供相應的接口供上層調用。如果隻是簡單的文本資訊通訊還容易,但要交換一些控制資訊或結構複雜的資料時,比如做聯機遊戲,更是讓人頭疼。

最近突然想到一個點子,可以用對象串行化技術将對象直接轉換為二進制資料發送,然後接收時直接還原為對象。具體過程是,将要發送的資料放在一個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);

}