zoukankan      html  css  js  c++  java
  • [转]C#.net同步异步SOCKET通讯和多线程总结

    //////////////////////////////////////////////////////////////////////////////////////////
      /*
      标题:在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: dyj057@gmail.com
      
    /// 创建日期: 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(
    thisnew 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
       {
       
    thrownew 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(
    thisnew 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(
    thisnew 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)
       {
       
    //客户端退出
       if10054 == 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(
    thisnew 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(
    thisnew 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( 
    thisnew NetEventArgs( clientSession ));
       }
       }
    //end of if(ReceivedDatagram != null)
       
    //继续接收数据
       _session.ClientSocket.BeginReceive(_recvDataBuffer, 0, DefaultBufferSize, SocketFlags.None,
       
    new AsyncCallback(RecvData), _session.ClientSocket);
       }
       
    catch(SocketException ex)
       {
       
    //客户端退出
       if10054 == ex.ErrorCode )
       {
       
    //服务器强制的关闭连接,强制退出
       _session.TypeOfExit = Session.ExitType.ExceptionExit;
       
    if(DisConnectedServer!=null)
       {
       DisConnectedServer(
    thisnew 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:
       {
       
    thrownew 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:
       {
       
    thrownew 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)
       {
       
    ifnull == client)
       {
       
    throw(new ArgumentNullException());
       }
       _client 
    = client;
       }
       
    #endregion 
       
    #region 属性
       
       
    /// <summary>
       
    /// 获得激发该事件的会话对象
       
    /// </summary>
       public Session Client
       {
       
    get
       {
       
    return _client;
       }
       
       }
       
    #endregion 
       
      }
      } 
  • 相关阅读:
    JS---案例:tab切换效果
    .net core 使用MD5加密解密字符串
    c#实战开发:用.net core开发一个简单的Web以太坊钱包 (六)
    c#实战开发:以太坊Geth 命令发布智能合约 (五)
    c#实战开发:以太坊Geth 常用命令 (四)
    c#实战开发:以太坊钱包快速同步区块和钱包卡死解决方案 (三)
    c#实战开发:以太坊钱包对接私链 (二)
    c# API接受图片文件以文件格式上传图片
    c# API接受图片文件以Base64格式上传图片
    命令查看当前电脑安装所有版本.NET Core SKD
  • 原文地址:https://www.cnblogs.com/llcto/p/1583663.html
Copyright © 2011-2022 走看看