C# 完成端口组件实现

前面用C++实现了windows平台上的网络完成端口组件,那么到C#中如何做了?起初我打算通过PInvoke来调用win底层API来仿照C++实现,但问题很快就出来了--C#中的Unsafe指针无法稳定的指向一块缓冲区的首地址,也就是说当垃圾回收进行的时候,我们的unsafe指针的值可能已经无效了。用pin?我也想过,可是锁住所有的TCP接收缓冲区,会极大的降低运行时的效率。难道没有办法了吗?想想完成端口模型的本质思想是将"启动异步操作的线程"和"提供服务的线程"(即工作者线程)拆伙。做到这一点不就够了吗,这是本质的东西。

        分析一下我们需要几种类型的线程,首先我们需要一个线程来接收TCP连接请求,这就是所谓监听线程,当成功的接收到一个连接后,就向连接发送一个异步接收数据的请求,由于是异步操作,所以会立即返回,然后再去接收新的连接请求,如此监听线程就循环运作起来了。值得提出的是,在异步接收的回调函数中,应该对接收到的数据进行处理,完成端口模型所做的就是将接收到的数据放在了完成端口队列中,注意,是一个队列。
        第二种线程类型,就是工作者线程。工作者线程的个数有个经验值是( Cpu个数×2 + 2),当然具体取多少,还要取决于你的应用的要求。工作者线程的任务就是不断地从完成端口队列中取出数据,并处理它,然后如果有回复,再将回复写入对应的连接。

刚才已经提到,我打算在C#中不使用Pinvoke来实现完成端口,而在.NET平台中是没有特定的完成端口队列这个组件或类的,所以我们首先要实现一个这样的队列。

         好,让我们来定义接口IRequestQueueManager,用于模拟完成端口的队列。

下面给出其定义和默认实现。

using System;
using System.Collections ;

namespace EnterpriseServerBase.Network
{
 /// <summary>
 /// IRequestQueueManager 用于模拟完成端口的队列。
 /// </summary>
 public interface IRequestQueueManager :IRequestPusher
 {
  //void Push(object package) ;
  object Pop() ;
  void Clear() ;
  int Length {get ;}
 }

 //向队列中的Push一个请求包
 public interface IRequestPusher
 {
  void Push(object package) ;
 }
 
 /// <summary>
 /// IRequestQueueManager 的默认实现
 /// </summary>
 public class RequestQueueManager :IRequestQueueManager
 {
  private Queue queue = null ;

  public RequestQueueManager()
  {
   this.queue = new Queue() ; 
  }

  public void Push(object package)
  {
   lock(this.queue)
   {
    this.queue.Enqueue(package) ;
   }
  }

  public object Pop()
  {
   object package = null ;

   lock(this.queue)
   {
    if(this.queue.Count > 0)
    {    
     package = this.queue.Dequeue() ;
    }
   }

   return package ;
  }

  public void Clear()
  {
   lock(this.queue)
   {
    this.queue.Clear() ;
   }
  }

  public int Length
  {
   get
   {
    return this.queue.Count ;
   }
  }

 } 
}

在IRequestQueueManager的基础上,可以将工作者线程和启动异步操作的线程拆开了。由于工作者线程只与端口队列相关,所以我决定将它们一起封装起来--IIOCPManager

来看看完成端口类如何实现:

using System;
using System.Threading ;

namespace EnterpriseServerBase.Network
{
 /// <summary>
 /// IIOCPManager 完成端口管理者,主要管理工作者线程和完成端口队列。
 /// 2005.05.23
 /// </summary>
 public interface IIOCPManager : IRequestPusher
 {
  void Initialize(IOCPPackageHandler i_packageHandler ,int threadCount) ;
  void Start() ; //启动工作者线程
  void Stop() ;  //退出工作者线程 

  int WorkThreadCount{get ;}

  event CallBackPackageHandled PackageHandled ;
 }

 //IOCPPackageHandler 用于处理从完成端口队列中取出的package
 public interface IOCPPackageHandler
 {
   void HandlerPackage(object package) ; //一般以同步实现
 }

 public delegate void CallBackPackageHandled(object package) ;

 /// <summary>
 /// IOCPManager 是IIOCPManager的默认实现
 /// </summary>
 public class IOCPManager :IIOCPManager
 {
  private IRequestQueueManager requestQueueMgr = null ;
  private IOCPPackageHandler packageHandler ;
  private int workThreadCount = 0 ; //实际的工作者线程数
  private int MaxThreadCount  = 0 ;

  private bool stateIsStop = true ;
  
  public IOCPManager()
  {   
  }


  #region ICPWorkThreadManager 成员
  public event CallBackPackageHandled PackageHandled ;

  public void Initialize(IOCPPackageHandler i_packageHandler ,int threadCount )
  {
   this.requestQueueMgr = new RequestQueueManager() ;
   this.MaxThreadCount = threadCount ;
   this.packageHandler = i_packageHandler ;
  }

  public void Push(object package)
  {
   this.requestQueueMgr.Push(package) ;
  }

  public void Start()
  {
   if(! this.stateIsStop)
   {
    return ;
   }

   this.stateIsStop = false ;
   this.CreateWorkThreads() ;

  }

  public void Stop()
  {
   if(this.stateIsStop)
   {
    return ;
   }

   this.stateIsStop = true ;

   //等待所有工作者线程结束
   int count = 0 ;
   while(this.workThreadCount != 0)
   { 
    if(count < 10)
    {
     Thread.Sleep(200) ;
    }
    else
    {    
     throw new Exception("WorkThread Not Terminated !") ;
    }

    ++ count ;
   }

   this.requestQueueMgr.Clear() ;
  }   

  public int WorkThreadCount
  {
   get
   {
    return this.workThreadCount ;
   }
  }
  #endregion

  #region CreateWorkThreads
  private void CreateWorkThreads()
  {
   for(int i= 0 ;i< this.MaxThreadCount ;i++)
   {
    Thread t = new Thread(new ThreadStart(this.ServeOverLap)) ;
    Interlocked.Increment(ref this.workThreadCount) ;
    t.Start() ;
   }
  }
  #endregion

  #region ServeOverLap 工作者线程
  private void ServeOverLap()
  { 
   while(! this.stateIsStop)
   {
    object package = this.requestQueueMgr.Pop() ;
    if(package == null)
    {
     Thread.Sleep(200) ;
     continue ;
    }
    
    this.packageHandler.HandlerPackage(package) ;
    
    if(! this.stateIsStop)
    {
     if(this.PackageHandled != null)
     {
      this.PackageHandled(package) ;
     }
    }
   }
   
   //工作者线程安全退出
   Interlocked.Decrement(ref this.workThreadCount) ;
  }
  #endregion 

 }

}

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

        现在,与工作者线程相关的所有元素都封装在IOCPManager了,它可以单独的作为一个组件给外部使用,接下来的实现完成端口组件就非常easy了,我们只要提供一个监听者线程接收连接,并将从连接接收到的数据通过IRequestPusher接口放入端口队列就可以了。当然,为了处理接收到的数据,我们需要提供一个实现了IOCPPackageHandler接口的对象给IOCPManager。值得提出的是,你可以在数据处理并发送了回复数据后,再次投递一个异步接收请求,以保证能源源不断的从对应的TCP连接接收数据。

        我以插件的方式实现了完成端口组件,如果你想了解,可以email给我索取源码。当然,在了解了上面的思想的基础上,自己实现一个也是很简单的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 21
    评论
最近有项目要做一个高性能网络服务器,去网络上搜到到的都是C++版本而且是英文或者简单的DEMO,所以自己动手写了C# 的DEMO。 网络上只写接收到的数据,没有说怎么处理缓冲区数据,本DEMO简单的介绍如何处理接收到的数据。简单易用,希望对大家有用. 1、在C#中,不用去面对完成端口的操作系统内核对象,Microsoft已经为我们提供了SocketAsyncEventArgs类,它封装了IOCP的使用。请参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx?cs-save-lang=1&cs-lang=cpp#code-snippet-1。 2、我的SocketAsyncEventArgsPool类使用List对象来存储对客户端来通信的SocketAsyncEventArgs对象,它相当于直接使用内核对象时的IoContext。我这样设计比用堆栈来实现的好处理是,我可以在SocketAsyncEventArgsPool池中找到任何一个与服务器连接的客户,主动向它发信息。而用堆栈来实现的话,要主动给客户发信息,则还要设计一个结构来存储已连接上服务器的客户。 3、对每一个客户端不管还发送还是接收,我使用同一个SocketAsyncEventArgs对象,对每一个客户端来说,通信是同步进行的,也就是说服务器高度保证同一个客户连接上要么在投递发送请求,并等待;或者是在投递接收请求,等待中。本例只做echo服务器,还未考虑由服务器主动向客户发送信息。 4、SocketAsyncEventArgs的UserToken被直接设定为被接受的客户端Socket。 5、没有使用BufferManager 类,因为我在初始化时给每一个SocketAsyncEventArgsPool中的对象分配一个缓冲区,发送时使用Arrary.Copy来进行字符拷贝,不去改变缓冲区的位置,只改变使用的长度,因此在下次投递接收请求时恢复缓冲区长度就可以了!如果要主动给客户发信息的话,可以new一个SocketAsyncEventArgs对象,或者在初始化中建立几个来专门用于主动发送信息,因为这种需求一般是进行信息群发,建立一个对象可以用于很多次信息发送,总体来看,这种花销不大,还减去了字符拷贝和消耗。 6、测试结果:(在我的笔记本上时行的,我的本本是T420 I7 8G内存) 100客户 100,000(十万次)不间断的发送接收数据(发送和接收之间没有Sleep,就一个一循环,不断的发送与接收) 耗时3004.6325 秒完成 总共 10,000,000 一千万次访问 平均每分完成 199,691.6 次发送与接收 平均每秒完成 3,328.2 次发送与接收 整个运行过程中,内存消耗在开始两三分种后就保持稳定不再增涨。 看了一下对每个客户端的延迟最多不超过2秒。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值