程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 在c#使用IOCP(完成端口)的簡單示例(2)

在c#使用IOCP(完成端口)的簡單示例(2)

編輯:關於C語言

lpOverlapped參數,本意是一個win32的overlapped結構的指針,本示例中不用,所以不詳細講。它叫單IO數據,是相對單據並拘束CompletionKey來講的,前者是一個句柄的每次IO操作的上下文,比如單詞IO操作的數據、操作類型等,後者是整個句柄的上下文。但這裡我們表示你要投遞的數據,可以是任何類型的數據(誰讓它是個指針呢,所以傳啥都行),值得注意的一點就是,這個數據傳遞到工作線程的時候,中間這個數據走的是非托管代碼。所以不能直接傳一個引用進去,這裡要使用到GCHandle類。先大致介紹一下這個類吧。它有個靜態方法Alloc來給把一個對象在GC句柄表裡注冊,GC句柄表示CLR為沒個應用程序域提供的一個表,它允許你來監視和管理對象的生命周期,你可以往裡加一個對象的引用,也可以從裡面移除一個對象,往裡加對象的時候,還可以指定一個標記來表示我們希望如何監視和控制這個對象。而加入一個條目的操作就是GCHandle的Alloc對象,它有兩個參數,第一個參數是對象,第二參數是GCHandleType類型的枚舉,第二個參數表示我們如何來監視和控制這個對象的生命周期。當這個參數是GCHandleType.Normal時,表示我們告訴垃圾收集器,及時托管代碼裡沒有該對象的根,也不要回收該對象,但垃圾收集器可以移動它,一般我們向非托管代碼傳遞一個對象,而又從非托管代碼傳遞回來的時候用這個類型非常好,它不會讓垃圾收集器在非托管代碼返回托管代碼的時候回收掉該對象,還不怎麼影響GC的性能,因為GC還可以移動它。dwCompletionKey就是我們在托管-非托管-托管之間傳遞的一個很典型的場景。所以這裡用它,另外還有GCHandleType.Pinned,它和GCHandleType.Normal不同的一點就是GC除了在沒有根的時候不能回收這個對象外,還不能移動它,應用場景是給非托管代碼傳遞一個byte[]的buffer,讓托管代碼去填充,如果用GCHandleType.Normal有可能在非托管代碼返回托管代碼的時候寫錯內存位置,因為有可能GC移動了這個對象的內存地址。關於根、GC原理,大家可以參考相關資料。另外在你的數據從非托管代碼傳遞會托管代碼後,要調用GCHandle的實例方法free來在GC句柄表裡移除該對象,這時候你的托管代碼還有個該對象的引用,也就是根,GC也不會給你回收的,當你用完了後,GC就給你回收了。GCHandle的Target屬性用來訪問GCHandle指向的對象。其它兩個GCHandleType的成員是關於弱引用的,和本文關系不大,就不介紹了。

GetQueuedCompletionStatus原型如下 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]  public static extern bool GetQueuedCompletionStatus(SafeFileHandle CompletionPort,    out uint lpNumberOfBytesTransferred, out IntPtr lpCompletionKey,    out IntPtr lpOverlapped, uint dwMilliseconds); 前幾個參數和PostQueuedCompletionStatus差不多, CompletionPort表示在哪個完成端口上等待PostQueuedCompletionStatus發來的消息,或者IO操作完成的通知,

lpNumberOfBytesTransferred表示收到數據的大小,這個大小不是說CompletionKey的大小,而是在單次I/O操作完成後(WSASend或者WSAReceve),實際傳輸的字節數,我在這裡理解的不是很透徹,我覺得如果是接受PostQueuedCompletionStatus的消息的話,應該是收到lpOverlapped的大小,因為它才是單IO數據嘛。

lpCompletionKey用來接收單據並數據,我們沒傳遞啥,後來也沒用,在socket程序裡,一般接socket句柄。

lpOverlapped用來接收單IO數據,或者我們的自定義消息。

dwMilliseconds表示等待一個自定義消息或者IO完成通知消息在完成端口上出現的時間,傳遞INIFINITE(0xffffffff)表示無限等待下去。

好了,API大概介紹這麼多,下面介紹代碼 1、主線程創建一個完成端口對象,不和任何句柄綁定,前幾個參數都寫0,NumberOfConcurrentThreads參數我們寫1,因為我們的示例就一個工作線程。 2、創建一個工作線程,把第一步創建的完成端口傳進去 3、創建兩個單IO數據,分別發投遞給第一步創建的完成端口 4、在工作線程裡執行一個死循環,循環在傳遞進來的完成端口上等待消息,沒有消息的時候GetQueuedCompletionStatus處於休息狀態,有消息來的時候把指針轉換成對象,然後輸出 5、如果收到退出指令,就退出循環,從而結束工作者線程。下面是完整代碼,需要打開不安全代碼的編譯選項。

using System;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32.SafeHandles;
[StructLayout(LayoutKind.Sequential)]
class PER_IO_DATA
{
public string Data;
}
public class IOCPApiTest
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern SafeFileHandle CreateIoCompletionPort(IntPtr FileHandle, IntPtr ExistingCompletionPort, IntPtr CompletionKey, uint NumberOfConcurrentThreads);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetQueuedCompletionStatus(SafeFileHandle CompletionPort,
out uint lpNumberOfBytesTransferred, out IntPtr lpCompletionKey,
out IntPtr lpOverlapped, uint dwMilliseconds);
[DllImport("Kernel32", CharSet = CharSet.Auto)]
private static extern bool PostQueuedCompletionStatus(SafeFileHandle CompletionPort, uint dwNumberOfBytesTransferred, IntPtr dwCompletionKey, IntPtr lpOverlapped);
public static unsafe void TestIOCPApi()
{
var CompletionPort = CreateIoCompletionPort(new IntPtr(-1), IntPtr.Zero, IntPtr.Zero, 1);
if(CompletionPort.IsInvalid)
{
Console.WriteLine("CreateIoCompletionPort 出錯:{0}",Marshal.GetLastWin32Error());
}
var thread = new Thread(ThreadProc);
thread.Start(CompletionPort);
var PerIOData = new PER_IO_DATA() ;
var gch = GCHandle.Alloc(PerIOData);
PerIOData.Data = "hi,我是蛙蛙王子,你是誰?";
Console.WriteLine("{0}-主線程發送數據",Thread.CurrentThread.GetHashCode());
PostQueuedCompletionStatus(CompletionPort, (uint)sizeof(IntPtr), IntPtr.Zero, (IntPtr)gch);
var PerIOData2 = new PER_IO_DATA();
var gch2 = GCHandle.Alloc(PerIOData2);
PerIOData2.Data = "關閉工作線程吧";
Console.WriteLine("{0}-主線程發送數據", Thread.CurrentThread.GetHashCode());
PostQueuedCompletionStatus(CompletionPort, 4, IntPtr.Zero, (IntPtr)gch2);
Console.WriteLine("主線程執行完畢");
Console.ReadKey();
}
static void ThreadProc(object CompletionPortID)
{
var CompletionPort = (SafeFileHandle)CompletionPortID;
while (true)
{
uint BytesTransferred;
IntPtr PerHandleData;
IntPtr lpOverlapped;
Console.WriteLine("{0}-工作線程准備接受數據",Thread.CurrentThread.GetHashCode());
GetQueuedCompletionStatus(CompletionPort, out BytesTransferred,
out PerHandleData, out lpOverlapped, 0xffffffff);
if(BytesTransferred <= 0)
continue;
GCHandle gch = GCHandle.FromIntPtr(lpOverlapped);
var per_HANDLE_DATA = (PER_IO_DATA)gch.Target;
Console.WriteLine("{0}-工作線程收到數據:{1}", Thread.CurrentThread.GetHashCode(), per_HANDLE_DATA.Data);
gch.Free();
if (per_HANDLE_DATA.Data != "關閉工作線程吧") continue;
Console.WriteLine("收到退出指令,正在退出");
CompletionPort.Dispose();
break;
}
}
public static int Main(String[] args)
{
TestIOCPApi();
return 0;
}
}
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved