C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(二)

来源:互联网 发布:jpg格式软件下载 编辑:程序博客网 时间:2024/04/28 19:08
/// <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
  
 }
}