在C#中使用異步Socket編程實現TCP網絡服務的C/S的通訊構架(一)

 

在C#中使用異步Socket編程實現TCP網絡服務的C/S的通訊構架(一)----基礎類庫部分

當看到.NET中TcpListener和TcpClient的時候,我非常高興,那就是我想要的通訊模式
但是使用之後發現它們的力量太單薄了,我們需要一個更好的類庫來替代它們.

下面提供了一些類,可以很好的完成Tcp的C/S通訊模式.在本文的第二部分,我將爲大家介紹怎麼使用它們

主要通過事件來現實整個的功能:
服務器的事件包括:

服務器滿
新客戶端連接
客戶端關閉
接收到數據
   
客戶端使用的事件包括:

已連接服務器
接收到數據
連接關閉

另外爲了靈活的處理需求變化,還提供了編碼器和報文解析器的實現方法.
注意:該類庫沒有經過嚴格的測試,如出現Bug,請發送給我,我會覺得你的整個行爲是對我的鼓勵和支持.

*/
//////////////////////////////////////////////////////////////////////////////////////////

/// <summary>
/// (C)2003-2005 C2217 Studio
/// 保留所有權利
///
/// 文件名稱:  TcpCSFramework.cs
/// 文件ID:   
/// 編程語言:  C#
/// 文件說明:  提供TCP網絡服務的C/S的通訊構架基礎類
///     (使用異步Socket編程實現)
///     
/// 當前版本:  1.1
/// 替換版本:  1.0
///
/// 作者:   鄧楊均
/// EMail:   [email protected]
/// 創建日期:  2005-3-9
/// 最後修改日期: 2005-3-17
///
/// 歷史修改記錄:
///
/// 時間:   2005-3-14
/// 修改內容: 
///     1.創建Ibms.Net.TcpCSFramework命名空間和添加Session對象.
///     2.修改NetEventArgs類,以適應新添加對象.
///     3.添加了會話退出類型,更適合實際的情況.
///     注意:
///     * 強制退出類型是應用程序直接結束,比如通過任務管理器結束
///     程序或者程序異常退出等,沒有執行正常的退出方法而產生的.
///     * 正常的退出類型是應用程序執行正常的退出的方法關鍵在於
///     需要調用Socket.Shutdown( SocketShutdown.Both )後才調用
///     Socket.Close()方法,而不是直接的調用Socket.Close()方法,
///     如果那樣調用將產生強制退出類型.
///    
/// 時間:   2005-3-16
/// 修改內容:
///     1.創建TcpCli,Coder,DatagramResover對象,把抽象和實現部分分離
///     2.文件版本修改爲1.1,1.0版本仍然保留,更名爲:
///     TcpCSFramework_v1.0.cs
///     3.在TcpServer中修改自定義的hashtable爲系統Hashtable類型
///    
/// </summary>

using System;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Diagnostics;
using System.Collections;

namespace Ibms.Net.TcpCSFramework
{

 /// <summary>
 /// 網絡通訊事件模型委託
 /// </summary>
 public delegate void NetEvent(object sender, NetEventArgs e);
 
 /// <summary>
 /// 提供TCP連接服務的服務器類
 ///
 /// 版本:  1.1
 /// 替換版本: 1.0
 ///
 /// 特點:
 /// 1.使用hash表保存所有已連接客戶端的狀態,收到數據時能實現快速查找.每當
 /// 有一個新的客戶端連接就會產生一個新的會話(Session).該Session代表了客
 /// 戶端對象.
 /// 2.使用異步的Socket事件作爲基礎,完成網絡通訊功能.
 /// 3.支持帶標記的數據報文格式的識別,以完成大數據報文的傳輸和適應惡劣的網
 /// 絡環境.初步規定該類支持的最大數據報文爲640K(即一個數據包的大小不能大於
 /// 640K,否則服務器程序會自動刪除報文數據,認爲是非法數據),防止因爲數據報文
 /// 無限制的增長而倒是服務器崩潰
 /// 4.通訊格式默認使用Encoding.Default格式這樣就可以和以前32位程序的客戶端
 /// 通訊.也可以使用U-16和U-8的的通訊方式進行.可以在該DatagramResolver類的
 /// 繼承類中重載編碼和解碼函數,自定義加密格式進行通訊.總之確保客戶端與服務
 /// 器端使用相同的通訊格式
 /// 5.使用C# native code,將來出於效率的考慮可以將C++代碼寫成的32位dll來代替
 /// C#核心代碼, 但這樣做缺乏可移植性,而且是Unsafe代碼(該類的C++代碼也存在)
 /// 6.可以限制服務器的最大登陸客戶端數目
 /// 7.比使用TcpListener提供更加精細的控制和更加強大異步數據傳輸的功能,可作爲
 /// TcpListener的替代類
 /// 8.使用異步通訊模式,完全不用擔心通訊阻塞和線程問題,無須考慮通訊的細節
 ///
 /// 注意:
 /// 1.部分的代碼由Rational XDE生成,可能與編碼規範不符
 ///
 /// 原理:
 ///
 ///
 /// 使用用法:
 ///
 /// 例子:
 ///
 /// </summary>
 public class TcpSvr
 {
  #region 定義字段
 
  /// <summary>
  /// 默認的服務器最大連接客戶端端數據
  /// </summary>
  public const int DefaultMaxClient=100;

  /// <summary>
  /// 接收數據緩衝區大小64K
  /// </summary>
  public const int DefaultBufferSize = 64*1024;

  /// <summary>
  /// 最大數據報文大小
  /// </summary>
  public const int MaxDatagramSize = 640*1024;

  /// <summary>
  /// 報文解析器
  /// </summary>
  private DatagramResolver _resolver;

  /// <summary>
  /// 通訊格式編碼解碼器
  /// </summary>
  private Coder _coder;

  /// <summary>
  /// 服務器程序使用的端口
  /// </summary>
  private ushort _port;

  /// <summary>
  /// 服務器程序允許的最大客戶端連接數
  /// </summary>
  private ushort _maxClient;

  /// <summary>
  /// 服務器的運行狀態
  /// </summary>
  private bool _isRun;

  /// <summary>
  /// 接收數據緩衝區
  /// </summary>
  private byte[] _recvDataBuffer;

  /// <summary>
  /// 服務器使用的異步Socket類,
  /// </summary>
  private Socket _svrSock;

  /// <summary>
  /// 保存所有客戶端會話的哈希表
  /// </summary>
  private Hashtable _sessionTable;

  /// <summary>
  /// 當前的連接的客戶端數
  /// </summary>
  private ushort _clientCount;

  #endregion

  #region 事件定義
 
  /// <summary>
  /// 客戶端建立連接事件
  /// </summary>
  public event  NetEvent ClientConn;

  /// <summary>
  /// 客戶端關閉事件
  /// </summary>
  public event  NetEvent ClientClose;

  /// <summary>
  /// 服務器已經滿事件
  /// </summary>
  public event  NetEvent ServerFull;

  /// <summary>
  /// 服務器接收到數據事件
  /// </summary>
  public event  NetEvent RecvData;

  #endregion
 
  #region 構造函數

  /// <summary>
  /// 構造函數
  /// </summary>
  /// <param name="port">服務器端監聽的端口號</param>
  /// <param name="maxClient">服務器能容納客戶端的最大能力</param>
  /// <param name="encodingMothord">通訊的編碼方式</param>
  public TcpSvr( ushort port,ushort maxClient, Coder coder)
  {
   _port = port;
   _maxClient = maxClient;
   _coder = coder;
  }


  /// <summary>
  /// 構造函數(默認使用Default編碼方式)
  /// </summary>
  /// <param name="port">服務器端監聽的端口號</param>
  /// <param name="maxClient">服務器能容納客戶端的最大能力</param>
  public TcpSvr( ushort port,ushort maxClient)
  {
   _port = port;
   _maxClient = maxClient;  
   _coder = new Coder(Coder.EncodingMothord.Default);
  }
 

  // <summary>
  /// 構造函數(默認使用Default編碼方式和DefaultMaxClient(100)個客戶端的容量)
  /// </summary>
  /// <param name="port">服務器端監聽的端口號</param>
  public TcpSvr( ushort port):this( port, DefaultMaxClient)
  {
  }

  #endregion

  #region 屬性

  /// <summary>
  /// 服務器的Socket對象
  /// </summary>
  public Socket ServerSocket
  {
   get
   {
    return _svrSock;
   }
  }

  /// <summary>
  /// 數據報文分析器
  /// </summary>
  public DatagramResolver Resovlver
  {
   get
   {
    return _resolver;
   }
   set
   {
    _resolver = value;
   }
  }

  /// <summary>
  /// 客戶端會話數組,保存所有的客戶端,不允許對該數組的內容進行修改
  /// </summary>
  public Hashtable SessionTable
  {
   get
   {
    return _sessionTable;
   }
  }

  /// <summary>
  /// 服務器可以容納客戶端的最大能力
  /// </summary>
  public int Capacity
  {
   get
   {
    return _maxClient;
   }
  }

  /// <summary>
  /// 當前的客戶端連接數
  /// </summary>
  public int SessionCount
  {
   get
   {
    return _clientCount;
   }
  }

  /// <summary>
  /// 服務器運行狀態
  /// </summary>
  public bool IsRun
  {
   get
   {
    return _isRun;
   }
  
  }

  #endregion
 
  #region 公有方法

  /// <summary>
  /// 啓動服務器程序,開始監聽客戶端請求
  /// </summary>
  public virtual void Start()
  {
   if( _isRun )
   {
    throw (new ApplicationException("TcpSvr已經在運行."));
   }
 
   _sessionTable = new Hashtable(53);
 
   _recvDataBuffer = new byte[DefaultBufferSize];

   //初始化socket
   _svrSock = new Socket( AddressFamily.InterNetwork,
    SocketType.Stream, ProtocolType.Tcp );

   //綁定端口
   IPEndPoint iep = new IPEndPoint( IPAddress.Any, _port);
   _svrSock.Bind(iep);

   //開始監聽
   _svrSock.Listen(5);

   //設置異步方法接受客戶端連接
   _svrSock.BeginAccept(new AsyncCallback( AcceptConn ), _svrSock);

   _isRun = true;

  }
 
  /// <summary>
  /// 停止服務器程序,所有與客戶端的連接將關閉
  /// </summary>
  public virtual void Stop()
  {
   if( !_isRun )
   {
    throw (new ApplicationException("TcpSvr已經停止"));
   }

   //這個條件語句,一定要在關閉所有客戶端以前調用
   //否則在EndConn會出現錯誤
   _isRun = false;

   //關閉數據連接,負責客戶端會認爲是強制關閉連接
   if( _svrSock.Connected )
   {
    _svrSock.Shutdown( SocketShutdown.Both );
   }

   CloseAllClient();

   //清理資源
   _svrSock.Close();
 
   _sessionTable = null;
  
  }
 

  /// <summary>
  /// 關閉所有的客戶端會話,與所有的客戶端連接會斷開
  /// </summary>
  public virtual void CloseAllClient()
  {
   foreach(Session client in _sessionTable.Values)
   {
    client.Close();
   }

   _sessionTable.Clear();
  }


  /// <summary>
  /// 關閉一個與客戶端之間的會話
  /// </summary>
  /// <param name="closeClient">需要關閉的客戶端會話對象</param>
  public virtual void CloseSession(Session closeClient)
  {
   Debug.Assert( closeClient !=null);

   if( closeClient !=null )
   {
   
    closeClient.Datagram =null;

    _sessionTable.Remove(closeClient.ID);

    _clientCount--;
   
    //客戶端強制關閉鏈接
    if( ClientClose != null )
    {
     ClientClose(this, new NetEventArgs( closeClient ));
    }

    closeClient.Close();
   }
  }


  /// <summary>
  /// 發送數據
  /// </summary>
  /// <param name="recvDataClient">接收數據的客戶端會話</param>
  /// <param name="datagram">數據報文</param>
  public virtual void Send( Session recvDataClient, string datagram )
  {
   //獲得數據編碼
   byte [] data = _coder.GetEncodingBytes(datagram);

   recvDataClient.ClientSocket.BeginSend( data, 0, data.Length, SocketFlags.None,
    new AsyncCallback( SendDataEnd ), recvDataClient.ClientSocket );

  }

  #endregion

  #region 受保護方法
  /// <summary>
  /// 關閉一個客戶端Socket,首先需要關閉Session
  /// </summary>
  /// <param name="client">目標Socket對象</param>
  /// <param name="exitType">客戶端退出的類型</param>
  protected virtual void CloseClient( Socket client, Session.ExitType exitType)
  {
   Debug.Assert ( client !=null);

   //查找該客戶端是否存在,如果不存在,拋出異常
   Session closeClient = FindSession(client);
  
   closeClient.TypeOfExit = exitType;

   if(closeClient!=null)
   {
    CloseSession(closeClient);
   }
   else
   {
    throw( new ApplicationException("需要關閉的Socket對象不存在"));
   }
  }


  /// <summary>
  /// 客戶端連接處理函數
  /// </summary>
  /// <param name="iar">欲建立服務器連接的Socket對象</param>
  protected virtual void AcceptConn(IAsyncResult iar)
  {
   //如果服務器停止了服務,就不能再接收新的客戶端
   if( !_isRun)
   {
    return;
   }

   //接受一個客戶端的連接請求
   Socket oldserver = ( Socket ) iar.AsyncState;

   Socket client = oldserver.EndAccept(iar);

   //檢查是否達到最大的允許的客戶端數目
   if( _clientCount == _maxClient )
   {
    //服務器已滿,發出通知
    if( ServerFull != null )
    {
     ServerFull(this, new NetEventArgs( new Session(client)));
    }
   
   }
   else
   {
   
    Session newSession = new Session( client );

    _sessionTable.Add(newSession.ID, newSession);
   
    //客戶端引用計數+1
    _clientCount ++;

    //開始接受來自該客戶端的數據
    client.BeginReceive( _recvDataBuffer,0 , _recvDataBuffer.Length, SocketFlags.None,
     new AsyncCallback(ReceiveData), client);

    //新的客戶段連接,發出通知
    if( ClientConn != null )
    {
     ClientConn(this, new NetEventArgs(newSession ) );
    }
   }

   //繼續接受客戶端
   _svrSock.BeginAccept(new AsyncCallback( AcceptConn ), _svrSock);
  }


  /// <summary>
  /// 通過Socket對象查找Session對象
  /// </summary>
  /// <param name="client"></param>
  /// <returns>找到的Session對象,如果爲null,說明並不存在該回話</returns>
  private Session FindSession( Socket client )
  {
   SessionId id = new  SessionId((int)client.Handle);

   return (Session)_sessionTable[id];
  }
 

  /// <summary>
  /// 接受數據完成處理函數,異步的特性就體現在這個函數中,
  /// 收到數據後,會自動解析爲字符串報文
  /// </summary>
  /// <param name="iar">目標客戶端Socket</param>
  protected virtual void ReceiveData(IAsyncResult iar)
  {
   Socket client = (Socket)iar.AsyncState;

   try
   {
    //如果兩次開始了異步的接收,所以當客戶端退出的時候
    //會兩次執行EndReceive
   
    int recv = client.EndReceive(iar);

    if( recv == 0 )
    {
     //正常的關閉
     CloseClient(client, Session.ExitType.NormalExit);
     return;
    }

    string receivedData = _coder.GetEncodingString( _recvDataBuffer, recv );

    //發佈收到數據的事件
    if(RecvData!=null)
    {
     Session sendDataSession= FindSession(client);
   
     Debug.Assert( sendDataSession!=null );

     //如果定義了報文的尾標記,需要處理報文的多種情況
     if(_resolver != null)
     {
      if( sendDataSession.Datagram !=null &&
       sendDataSession.Datagram.Length !=0)
      {
       //加上最後一次通訊剩餘的報文片斷
       receivedData= sendDataSession.Datagram + receivedData ;
      }

      string [] recvDatagrams = _resolver.Resolve(ref receivedData);
      

      foreach(string newDatagram in recvDatagrams)
      {
       //深拷貝,爲了保持Datagram的對立性
       ICloneable copySession = (ICloneable)sendDataSession;

       Session clientSession = (Session)copySession.Clone();

       clientSession.Datagram = newDatagram;
       //發佈一個報文消息
       RecvData(this,new NetEventArgs( clientSession ));
      }

      //剩餘的代碼片斷,下次接收的時候使用
      sendDataSession.Datagram = receivedData;

      if( sendDataSession.Datagram.Length > MaxDatagramSize )
      {
       sendDataSession.Datagram = null;
      }
     
     }
     //沒有定義報文的尾標記,直接交給消息訂閱者使用
     else
     {
      ICloneable copySession = (ICloneable)sendDataSession;

      Session clientSession = (Session)copySession.Clone();

      clientSession.Datagram = receivedData;

      RecvData(this,new NetEventArgs( clientSession ));
     }
    
    }//end of if(RecvData!=null)

    //繼續接收來自來客戶端的數據
    client.BeginReceive( _recvDataBuffer, 0, _recvDataBuffer.Length , SocketFlags.None,
     new AsyncCallback( ReceiveData ), client);

   }
   catch(SocketException ex)
   {
    //客戶端退出
    if( 10054 == ex.ErrorCode )
    {
     //客戶端強制關閉
     CloseClient(client, Session.ExitType.ExceptionExit);
    }
   
   }
   catch(ObjectDisposedException ex)
   {
    //這裏的實現不夠優雅
    //當調用CloseSession()時,會結束數據接收,但是數據接收
    //處理中會調用int recv = client.EndReceive(iar);
    //就訪問了CloseSession()已經處置的對象
    //我想這樣的實現方法也是無傷大雅的.
    if(ex!=null)
    {
     ex=null;
     //DoNothing;
    }
   }
  
  }


  /// <summary>
  /// 發送數據完成處理函數
  /// </summary>
  /// <param name="iar">目標客戶端Socket</param>
  protected virtual void SendDataEnd(IAsyncResult iar)
  {
   Socket client = (Socket)iar.AsyncState;

   int sent = client.EndSend(iar);
  }

  #endregion

 }


 /// <summary>
 /// 提供Tcp網絡連接服務的客戶端類
 ///
 /// 版本:  1.0
 /// 替換版本:
 ///
 /// 特徵:
 /// 原理:
 /// 1.使用異步Socket通訊與服務器按照一定的通訊格式通訊,請注意與服務器的通
 /// 訊格式一定要一致,否則可能造成服務器程序崩潰,整個問題沒有克服,怎麼從byte[]
 /// 判斷它的編碼格式
 /// 2.支持帶標記的數據報文格式的識別,以完成大數據報文的傳輸和適應惡劣的網
 /// 絡環境.
 /// 用法:
 /// 注意:
 /// </summary>
 public class TcpCli
 {
  #region 字段

  /// <summary>
  /// 客戶端與服務器之間的會話類
  /// </summary>
  private Session _session;

  /// <summary>
  /// 客戶端是否已經連接服務器
  /// </summary>
  private bool _isConnected = false;

  /// <summary>
  /// 接收數據緩衝區大小64K
  /// </summary>
  public const int DefaultBufferSize = 64*1024;

  /// <summary>
  /// 報文解析器
  /// </summary>
  private DatagramResolver _resolver;

  /// <summary>
  /// 通訊格式編碼解碼器
  /// </summary>
  private Coder _coder;

  /// <summary>
  /// 接收數據緩衝區
  /// </summary>
  private byte[] _recvDataBuffer = new byte[DefaultBufferSize];

  #endregion

  #region 事件定義

  //需要訂閱事件才能收到事件的通知,如果訂閱者退出,必須取消訂閱
 
  /// <summary>
  /// 已經連接服務器事件
  /// </summary>
  public event NetEvent ConnectedServer;

  /// <summary>
  /// 接收到數據報文事件
  /// </summary>
  public event NetEvent ReceivedDatagram;

  /// <summary>
  /// 連接斷開事件
  /// </summary>
  public event NetEvent DisConnectedServer;
  #endregion

  #region 屬性

  /// <summary>
  /// 返回客戶端與服務器之間的會話對象
  /// </summary>
  public Session ClientSession
  {
   get
   {
    return _session;
   }
  }

  /// <summary>
  /// 返回客戶端與服務器之間的連接狀態
  /// </summary>
  public bool IsConnected
  {
   get
   {
    return _isConnected;
   }
  }

  /// <summary>
  /// 數據報文分析器
  /// </summary>
  public DatagramResolver Resovlver
  {
   get
   {
    return _resolver;
   }
   set
   {
    _resolver = value;
   }
  }

  /// <summary>
  /// 編碼解碼器
  /// </summary>
  public Coder ServerCoder
  {
   get
   {
    return _coder;
   }
  }

  #endregion
 
  #region 公有方法

  /// <summary>
  /// 默認構造函數,使用默認的編碼格式
  /// </summary>
  public TcpCli()
  {
   _coder = new Coder( Coder.EncodingMothord.Default );
  }

  /// <summary>
  /// 構造函數,使用一個特定的編碼器來初始化
  /// </summary>
  /// <param name="_coder">報文編碼器</param>
  public TcpCli( Coder coder )
  {
   _coder = coder;
  }

  /// <summary>
  /// 連接服務器
  /// </summary>
  /// <param name="ip">服務器IP地址</param>
  /// <param name="port">服務器端口</param>
  public virtual void Connect( string ip, int port)
  {
   if(IsConnected)
   {
    //重新連接
    Debug.Assert( _session !=null);

    Close();
   }

   Socket newsock= new Socket(AddressFamily.InterNetwork,
    SocketType.Stream, ProtocolType.Tcp);

   IPEndPoint iep = new IPEndPoint( IPAddress.Parse(ip), port);
   newsock.BeginConnect(iep, new AsyncCallback(Connected), newsock);

  }

  /// <summary>
  /// 發送數據報文
  /// </summary>
  /// <param name="datagram"></param>
  public virtual void Send( string datagram)
  {
   if(datagram.Length ==0 )
   {
    return;
   }

   if( !_isConnected )
   {
    throw (new  ApplicationException("沒有連接服務器,不能發送數據") );
   }

   //獲得報文的編碼字節
   byte [] data = _coder.GetEncodingBytes(datagram);

   _session.ClientSocket.BeginSend( data, 0, data.Length, SocketFlags.None,
    new AsyncCallback( SendDataEnd ), _session.ClientSocket);
  }

  /// <summary>
  /// 關閉連接
  /// </summary>
  public virtual void Close()
  {
   if(!_isConnected)
   {
    return;
   }

   _session.Close();

   _session = null;

   _isConnected = false;
  }

  #endregion

  #region 受保護方法

  /// <summary>
  /// 數據發送完成處理函數
  /// </summary>
  /// <param name="iar"></param>
  protected virtual void SendDataEnd(IAsyncResult iar)
  {
   Socket remote = (Socket)iar.AsyncState;
   int sent = remote.EndSend(iar);
   Debug.Assert(sent !=0);

  }

  /// <summary>
  /// 建立Tcp連接後處理過程
  /// </summary>
  /// <param name="iar">異步Socket</param>
  protected virtual void Connected(IAsyncResult iar)
  {
   Socket socket = (Socket)iar.AsyncState;

   socket.EndConnect(iar);

   //創建新的會話
   _session = new Session(socket);
  
   _isConnected = true;

   //觸發連接建立事件
   if(ConnectedServer != null)
   {
    ConnectedServer(this, new NetEventArgs(_session));
   }

   //建立連接後應該立即接收數據
   _session.ClientSocket.BeginReceive(_recvDataBuffer, 0,
    DefaultBufferSize, SocketFlags.None,
    new AsyncCallback(RecvData), socket);
  }

  /// <summary>
  /// 數據接收處理函數
  /// </summary>
  /// <param name="iar">異步Socket</param>
  protected virtual void RecvData(IAsyncResult iar)
  {
   Socket remote = (Socket)iar.AsyncState;

   try
   {
    int recv = remote.EndReceive(iar);

    //正常的退出
    if(recv ==0 )
    {
     _session.TypeOfExit = Session.ExitType.NormalExit;

     if(DisConnectedServer!=null)
     {
      DisConnectedServer(this, new NetEventArgs(_session));
     }

     return;
    }

    string receivedData = _coder.GetEncodingString( _recvDataBuffer,recv );
   
    //通過事件發佈收到的報文
    if(ReceivedDatagram != null)
    {
     //通過報文解析器分析出報文
     //如果定義了報文的尾標記,需要處理報文的多種情況
     if(_resolver != null)
     {
      if( _session.Datagram !=null &&
       _session.Datagram.Length !=0)
      {
       //加上最後一次通訊剩餘的報文片斷
       receivedData= _session.Datagram + receivedData ;
      }

      string [] recvDatagrams = _resolver.Resolve(ref receivedData);
      

      foreach(string newDatagram in recvDatagrams)
      {
       //Need Deep Copy.因爲需要保證多個不同報文獨立存在
       ICloneable copySession = (ICloneable)_session;

       Session clientSession = (Session)copySession.Clone();

       clientSession.Datagram = newDatagram;

       //發佈一個報文消息
       ReceivedDatagram(this,new NetEventArgs( clientSession ));
      }

      //剩餘的代碼片斷,下次接收的時候使用
      _session.Datagram = receivedData;
     }
     //沒有定義報文的尾標記,直接交給消息訂閱者使用
     else
     {
      ICloneable copySession = (ICloneable)_session;

      Session clientSession = (Session)copySession.Clone();

      clientSession.Datagram = receivedData;

      ReceivedDatagram( this, new NetEventArgs( clientSession ));

     }


    }//end of if(ReceivedDatagram != null)

    //繼續接收數據
    _session.ClientSocket.BeginReceive(_recvDataBuffer, 0, DefaultBufferSize, SocketFlags.None,
     new AsyncCallback(RecvData), _session.ClientSocket);
   }
   catch(SocketException ex)
   {
    //客戶端退出
    if( 10054 == ex.ErrorCode )
    {
     //服務器強制的關閉連接,強制退出
     _session.TypeOfExit = Session.ExitType.ExceptionExit;

     if(DisConnectedServer!=null)
     {
      DisConnectedServer(this, new NetEventArgs(_session));
     }
    }
    else
    {
     throw( ex );
    }
   }
   catch(ObjectDisposedException ex)
   {
    //這裏的實現不夠優雅
    //當調用CloseSession()時,會結束數據接收,但是數據接收
    //處理中會調用int recv = client.EndReceive(iar);
    //就訪問了CloseSession()已經處置的對象
    //我想這樣的實現方法也是無傷大雅的.
    if(ex!=null)
    {
     ex =null;
     //DoNothing;
    }
   }

  }
 
  #endregion


 }

 /// <summary>
 /// 通訊編碼格式提供者,爲通訊服務提供編碼和解碼服務
 /// 你可以在繼承類中定製自己的編碼方式如:數據加密傳輸等
 /// </summary>
 public class Coder
 {
  /// <summary>
  /// 編碼方式
  /// </summary>
  private EncodingMothord _encodingMothord;

  protected Coder()
  {
  
  }
 
  public Coder(EncodingMothord encodingMothord)
  {
   _encodingMothord = encodingMothord;
  }

  public enum EncodingMothord
  {
   Default =0,
   Unicode,
   UTF8,
   ASCII,
  }

  /// <summary>
  /// 通訊數據解碼
  /// </summary>
  /// <param name="dataBytes">需要解碼的數據</param>
  /// <returns>編碼後的數據</returns>
  public virtual string GetEncodingString( byte [] dataBytes,int size)
  {
   switch( _encodingMothord )
   {
    case EncodingMothord.Default:
    {
     return Encoding.Default.GetString(dataBytes,0,size);
    }
    case EncodingMothord.Unicode:
    {
     return Encoding.Unicode.GetString(dataBytes,0,size);
    }
    case EncodingMothord.UTF8:
    {
     return Encoding.UTF8.GetString(dataBytes,0,size);
    }
    case EncodingMothord.ASCII:
    {
     return Encoding.ASCII.GetString(dataBytes,0,size);
    }
    default:
    {
     throw( new Exception("未定義的編碼格式"));
    }
   }

  }

  /// <summary>
  /// 數據編碼
  /// </summary>
  /// <param name="datagram">需要編碼的報文</param>
  /// <returns>編碼後的數據</returns>
  public virtual byte[] GetEncodingBytes(string datagram)
  {
   switch( _encodingMothord)
   {
    case EncodingMothord.Default:
    {
     return Encoding.Default.GetBytes(datagram);
    }
    case EncodingMothord.Unicode:
    {
     return Encoding.Unicode.GetBytes(datagram);
    }
    case EncodingMothord.UTF8:
    {
     return Encoding.UTF8.GetBytes(datagram);
    }
    case EncodingMothord.ASCII:
    {
     return Encoding.ASCII.GetBytes(datagram);
    }
    default:
    {
     throw( new Exception("未定義的編碼格式"));
    }
   }
  }

 }


 /// <summary>
 /// 數據報文分析器,通過分析接收到的原始數據,得到完整的數據報文.
 /// 繼承該類可以實現自己的報文解析方法.
 /// 通常的報文識別方法包括:固定長度,長度標記,標記符等方法
 /// 本類的現實的是標記符的方法,你可以在繼承類中實現其他的方法
 /// </summary>
 public class DatagramResolver
 {
  /// <summary>
  /// 報文結束標記
  /// </summary>
  private string endTag;

  /// <summary>
  /// 返回結束標記
  /// </summary>
  string EndTag
  {
   get
   {
    return endTag;
   }
  }

  /// <summary>
  /// 受保護的默認構造函數,提供給繼承類使用
  /// </summary>
  protected DatagramResolver()
  {

  }

  /// <summary>
  /// 構造函數
  /// </summary>
  /// <param name="endTag">報文結束標記</param>
  public DatagramResolver(string endTag)
  {
   if(endTag == null)
   {
    throw (new ArgumentNullException("結束標記不能爲null"));
   }

   if(endTag == "")
   {
     throw (new ArgumentException("結束標記符號不能爲空字符串"));
   }

   this.endTag = endTag;
  }

  /// <summary>
  /// 解析報文
  /// </summary>
  /// <param name="rawDatagram">原始數據,返回未使用的報文片斷,
  /// 該片斷會保存在Session的Datagram對象中</param>
  /// <returns>報文數組,原始數據可能包含多個報文</returns>
  public virtual string [] Resolve(ref string rawDatagram)
  {
   ArrayList datagrams  = new ArrayList();

   //末尾標記位置索引
   int tagIndex =-1;

   while(true)
   {
    tagIndex = rawDatagram.IndexOf(endTag,tagIndex+1);
   
    if( tagIndex == -1 )
    {
     break;
    }
    else
    {
     //按照末尾標記把字符串分爲左右兩個部分
     string newDatagram = rawDatagram.Substring(
      0, tagIndex+endTag.Length);

     datagrams.Add(newDatagram);
    
     if(tagIndex+endTag.Length >= rawDatagram.Length)
     {
      rawDatagram="";

      break;
     }

     rawDatagram = rawDatagram.Substring(tagIndex+endTag.Length,
      rawDatagram.Length - newDatagram.Length);

     //從開始位置開始查找
     tagIndex=0;
    }
   }
 
   string [] results= new string[datagrams.Count];

   datagrams.CopyTo(results);

   return results;
  }

 }


 /// <summary>
 /// 客戶端與服務器之間的會話類
 ///
 /// 版本:  1.1
 /// 替換版本: 1.0
 ///
 /// 說明:
 ///    會話類包含遠程通訊端的狀態,這些狀態包括Socket,報文內容,
 ///    客戶端退出的類型(正常關閉,強制退出兩種類型)
 /// </summary>
 public class Session:ICloneable
 {
  #region 字段

  /// <summary>
  /// 會話ID
  /// </summary>
  private SessionId _id;

  /// <summary>
  /// 客戶端發送到服務器的報文
  /// 注意:在有些情況下報文可能只是報文的片斷而不完整
  /// </summary>
  private string _datagram;
 
  /// <summary>
  /// 客戶端的Socket
  /// </summary>
  private Socket _cliSock;

  /// <summary>
  /// 客戶端的退出類型
  /// </summary>
  private ExitType _exitType;

  /// <summary>
  /// 退出類型枚舉
  /// </summary>
  public enum ExitType
  {
   NormalExit ,
   ExceptionExit
  };

  #endregion

  #region 屬性

  /// <summary>
  /// 返回會話的ID
  /// </summary>
  public SessionId ID
  {
   get
   {
    return _id;
   }
  }

  /// <summary>
  /// 存取會話的報文
  /// </summary>
  public string Datagram
  {
   get
   {
    return _datagram;
   }
   set
   {
    _datagram = value;
   }
  }
 
  /// <summary>
  /// 獲得與客戶端會話關聯的Socket對象
  /// </summary>
  public Socket ClientSocket
  {
   get
   {
    return _cliSock;
   }
  }

  /// <summary>
  /// 存取客戶端的退出方式
  /// </summary>
  public ExitType TypeOfExit
  {
   get
   {
    return _exitType;
   }

   set
   {
    _exitType = value;
   }
  }

  #endregion

  #region 方法

  /// <summary>
  /// 使用Socket對象的Handle值作爲HashCode,它具有良好的線性特徵.
  /// </summary>
  /// <returns></returns>
  public override int GetHashCode()
  {
   return (int)_cliSock.Handle;
  }

  /// <summary>
  /// 返回兩個Session是否代表同一個客戶端
  /// </summary>
  /// <param name="obj"></param>
  /// <returns></returns>
  public override bool Equals(object obj)
  {
   Session rightObj = (Session)obj;
  
   return (int)_cliSock.Handle == (int)rightObj.ClientSocket.Handle;

  }

  /// <summary>
  /// 重載ToString()方法,返回Session對象的特徵
  /// </summary>
  /// <returns></returns>
  public override string ToString()
  {
   string result = string.Format("Session:{0},IP:{1}",
    _id,_cliSock.RemoteEndPoint.ToString());

   //result.C
   return result;
  }

  /// <summary>
  /// 構造函數
  /// </summary>
  /// <param name="cliSock">會話使用的Socket連接</param>
  public Session( Socket cliSock)
  {
   Debug.Assert( cliSock !=null );

   _cliSock = cliSock;

   _id = new SessionId( (int)cliSock.Handle);
  }

  /// <summary>
  /// 關閉會話
  /// </summary>
  public void Close()
  {
   Debug.Assert( _cliSock !=null );

   //關閉數據的接受和發送
   _cliSock.Shutdown( SocketShutdown.Both );

   //清理資源
   _cliSock.Close();
  }

  #endregion

  #region ICloneable 成員

  object System.ICloneable.Clone()
  {
   Session newSession = new Session(_cliSock);
   newSession.Datagram = _datagram;
   newSession.TypeOfExit = _exitType;

   return newSession;
  }

  #endregion
 }


 /// <summary>
 /// 唯一的標誌一個Session,輔助Session對象在Hash表中完成特定功能
 /// </summary>
 public class SessionId
 {
  /// <summary>
  /// 與Session對象的Socket對象的Handle值相同,必須用這個值來初始化它
  /// </summary>
  private int _id;

  /// <summary>
  /// 返回ID值
  /// </summary>
  public int ID
  {
   get
   {
    return _id;
   }
  }

  /// <summary>
  /// 構造函數
  /// </summary>
  /// <param name="id">Socket的Handle值</param>
  public SessionId(int id)
  {
   _id = id;
  }

  /// <summary>
  /// 重載.爲了符合Hashtable鍵值特徵
  /// </summary>
  /// <param name="obj"></param>
  /// <returns></returns>
  public override bool Equals(object obj)
  {
   if(obj != null )
   {
    SessionId right = (SessionId) obj;

    return _id == right._id;
   }
   else if(this == null)
   {
    return true;
   }
   else
   {
    return false;
   }
 
  }

  /// <summary>
  /// 重載.爲了符合Hashtable鍵值特徵
  /// </summary>
  /// <returns></returns>
  public override int GetHashCode()
  {
   return _id;
  }

  /// <summary>
  /// 重載,爲了方便顯示輸出
  /// </summary>
  /// <returns></returns>
  public override string ToString()
  {
   return _id.ToString ();
  }

 }


 /// <summary>
 /// 服務器程序的事件參數,包含了激發該事件的會話對象
 /// </summary>
 public class NetEventArgs:EventArgs
 {

  #region 字段

  /// <summary>
  /// 客戶端與服務器之間的會話
  /// </summary>
  private Session _client;

  #endregion

  #region 構造函數
  /// <summary>
  /// 構造函數
  /// </summary>
  /// <param name="client">客戶端會話</param>
  public NetEventArgs(Session client)
  {
   if( null == client)
   {
    throw(new ArgumentNullException());
   }

   _client = client;
  }
  #endregion

  #region 屬性
 
  /// <summary>
  /// 獲得激發該事件的會話對象
  /// </summary>
  public Session Client
  {
   get
   {
    return _client;
   }
  
  }

  #endregion
 
 }
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章