Welcome

首页 / 软件开发 / C# / 在c#使用IOCP(完成端口)的简单示例

在c#使用IOCP(完成端口)的简单示例2011-05-03这次给大家演示一下利用IOCP的在线程间传递数据的例子,顺便打算讲一些细节和注意的地方。

概述:这里主要使用IOCP的三个API,CreateIoCompletionPort,PostQueuedCompletionStatus,GetQueuedCompletionStatus,第一个是用来创建一个完成端口对象,第二个是向一个端口发送数据,第三个是接受数据,基本上用着三个函数,就可以写一个使用IOCP的简单示例。

其中完成端口一个内核对象,所以创建的时候会耗费性能,CPU得切换到内核模式,而且一旦创建了内核对象,我们都要记着要不用的时候显式的释放它的句柄,释放非托管资源的最佳实践肯定是使用Dispose模式,这个博客园有人讲过N次了。而一般要获取一个内核对象的引用,最好用SafeHandle来引用它,这个类可以帮你管理引用计数,而且用它引用内核对象,代码更健壮,如果用指针引用内核对象,在创建成功内核对象并复制给指针这个时间段,如果抛了ThreadAbortException,这个内核对象就泄漏了,而用SafeHandle去应用内核对象就不会在赋值的时候发生ThreadAbortException。另外SafeHandle类继承自CriticalFinalizerObject类,并实现了IDispose接口,CLR对CriticalFinalizerObject及其子类有特殊照顾,比如说在编译的时候优先编译,在调用非CriticalFinalizerObject类的Finalize方法后再调用CriticalFinalizerObject类的Finalize类的Finalize方法等。在win32里,一般一个句柄是-1或者0的时候表示这个句柄是无效的,所以.net有一个SafeHandle的派生类SafeHandleZeroOrMinusOneIsInvalid ,但是这个类是一个抽象类,你要引用自己使用的内核对象或者非托管对象,要从这个类派生一个类并重写Relseas方法。另外在.net框架里它有两个实现几乎一模一样的子类,一个是SafeFileHandle一个是SafeWaitHandle,前者表示文件句柄,后者表示等待句柄,我们这里为了方便就直接用SafeFileHandle来引用完成端口对象了。

CreateIoCompletionPort函数的原型如下 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern SafeFileHandle CreateIoCompletionPort(IntPtr FileHandle, IntPtr ExistingCompletionPort, IntPtr CompletionKey, uint NumberOfConcurrentThreads);

FileHandle参数表示要绑定在完成端口上的句柄,比如说一个已经accept的socket句柄。

ExistingCompletionPort参数表示一个已有的完成端口句柄,第一次创建完成端口的时候显然随便传个值就行,所以这个参数直接定义成IntPtr类型了。当你创建了工作线程来为I/O请求服务的时候,才会把句柄和完成端口关联在一起,而之前第一次创建完成端口的时候这个参数传一个zero指针就O了,而FileHandle参数传一个-1的指针就行了。

CompletionKey是完成键的意思,它可以是任意想传递给工作线程的数据,学名叫做单句柄数据,就是说跟随FileHandle参数走的一些状态数据,一般在socket的iocp程序里是把socket传进去,以便在工作线程里拿到这个socket句柄,在收到异步操作完成的通知及处理后继续进行下一个异步操作的投递,如发送和接受数据等。

NumberOfConcurrentThreads参数表示在一个完成端口上同时允许执行的最大线程数量。如果传0,就是说你有几个CPU,就是允许最大有几个线程,这也是最理想情况,因为一个CPU一个线程可以防止线程上下文切换。关于这个值要和创建工作线程的数量的关系,大家要理解清楚,不一定CPU有多少个,你的工作线程就创建多少个。因为你的工作线程有时候会阻塞或者等待,而如果你正好创建了CPU个数个工作线程,有一个等待的话,因为你分配了同时最多有CPU个数多个最大IOCP线程,这时候就不能效率最大化了。所以一般工作线程创建的要比CPU个数多一些,除非你保证你的工作线程不会阻塞。

PostQueuedCompletionStatus函数原型如下 [DllImport("Kernel32", CharSet = CharSet.Auto)] private static extern bool PostQueuedCompletionStatus(SafeFileHandle CompletionPort, uint dwNumberOfBytesTransferred, IntPtr dwCompletionKey, IntPtr lpOverlapped); 该方法用于给完成端口投递自定义信息,一般情况下如果把某个句柄和完成端口绑定后,当有数据收发操作完成时会自动同时工作线程,工作线程里的GetQueuedCompletionStatus就不会阻塞,而继续往下走,来进行接收到IO操作完成通知的流程。而有时候我们需要手工向工作者线程投递一些消息,比如说我们主线程知道所有的socket句柄都关闭了,工作线程可以退出了,我们就可以给工作线程发一个自定义数据,工作线程收到后判断是否是退出指令,然后退出。

CompletionPort参数表示向哪个完成端口对象投递信息,在这个完成端口上等待消息的工作线程就会收到消息了。 dwNumberOfBytesTransferred表示你投递的数据有多大,我们一般投递的是一个对象的指针,在32位系统里,int指针就是4个字节了,直接写4就O了,要不就用sizeof你传的数据,如sizeof(IntPtr)。

dwCompletionKey同CreateIoCompletionPort的解释,是单句柄数据,本示例用不到,不细说,直接用IntPtr.Zero填充了事。