I/O完成端口模型

         完成端口模型即Win32通过一个完成端口对象,统筹指定数量的工作线程对重叠I/O请求进行管理,以便为已经完成的重叠I/O请求提供服务的I/O模型。在使用这种模型之前,首先要创建一个I/O完成端口对象,用它面向任意数量的套接字句柄,管理多个I/O请求。将套接字句柄与一个完成端口关联在一起后,便可以套接字句柄为基础,投递发送或接收请求,开始I/O请求的处理。接下来,可开始依赖完成端口,接收有关I/O操作完成情况的通知。

       对完成端口来说,将一个套接字绑定到完成端口后,WSARecv和WSASend会立即返回,可以调用GetQueuedCompletionStatus来判断WSARecv和WSASend是否完成。这样主程序就可以完全等待接收新的连接,线程程序来等待WSARecv和WSASend是否完成。

   涉及的函数:

(1)

HANDLE CreateIoCompletionPort(
    HANDLE FileHandle,
    HANDLE ExistingCompletionPort,
    ULONG_PTR CompletionKey,
    DWORD NumberOfConcurrentThreads
);

    该函数实际用于两个明显有别的目的:
[1]. 用于创建一个完成端口对象。
    最开始创建一个完成端口时,唯一感兴趣的参数便是NumberOfConcurrentThreads(并发线程的数量);前面三个参数都会被忽略。NumberOfConcurrentThreads参数的特殊之处在于,它定义了在一个完成端口上,同时允许执行的线程数量。理想情况下,我们希望每个处理器各自负责一个线程的运行,为完成端口提供服务,避免过于频繁的线程“场景”切换。若将该参数设为0,表明系统内安装了多少个处理器,便允许同时运行多少个线程!可用下述代码创建一个I/O完成端口:  hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

[2]. 将一个句柄同完成端口关联到一起。

       NumberOfConcurrentThreads参数明确指示系统:在一个完成端口上,一次只允许n个工作者线程运行。 FileHandle参数指定一个要同完成端口关联在一起的套接字句柄。ExistingCompletionPort参数指定的是一个现有的完成端口。CompletionKey(完成键)参数则指定要与某个特定套接字句柄关联在一起的“单句柄数据”

(2)

BOOL GetQueuedCompletionStatus(
    HANDLE CompletionPort,
    LPDWORD lpNumberOfBytes,
    PULONG_PTR lpCompletionKey,
    LPOVERLAPPED* lpOverlapped,
    DWORD dwMilliseconds
);

其中,CompletionPort参数对应于要在上面等待的完成端口。lpNumberOfBytes参数负责在完成了一次I/O操作后(如WSASend或WSARecv),接收实际传输的字节数。lpCompletionKey参数为原先传递进入CreateIoCompletionPort函数的套接字返回“单句柄数据”。如我们早先所述,大家最好将套接字句柄保存在这个“键”(Key)中。lpOverlapped参数用于接收完成的I/O操作的重叠结果。这实际是一个相当重要的参数,因为可用它获取每个I/O操作的数据。而最后一个参数,dwMilliseconds,用于指定调用者希望等待一个完成数据包在完成端口上出现的时间。假如将其设为INFINITE,调用会无休止地等待下去。

(3)
BOOL PostQueuedCompletionStatus(
    HANDLE CompletionPort,
    DWORD dwNumberOfBytesTransferred,
    ULONG_PTR dwCompletionKey,
    LPOVERLAPPED lpOverlapped
);
CompletionPort参数指定想向其发送一个完成数据包的完成端口对象。而就dwNumberOfBytesTransferred、dwCompletionKey和lpOverlapped这三个参数来说,每一个都允许我们指定一个值,直接传递给GetQueuedCompletionStatus函数中对应的参数。这样一来,一个工作者线程收到传递过来的三个GetQueuedCompletionStatus函数参数后,便可根据由这三个参数的某一个设置的特殊值,决定何时应该退出。例如,可用dwCompletionPort参数传递0值,而一个工作者线程会将其解释成中止指令。一旦所有工作者线程都已关闭,便可使用CloseHandle函数,关闭完成端口,最终安全退出程序。


     在此模式下的编程步骤如下:

 【1】创建一个完成端口。第四个参数保持为0,指定在完成端口上,每个处理器一次只允许执行一个工作者线程。

 【2】 判断系统内到底安装了多少个处理器。

 【3】 创建工作者线程,根据步骤2)得到的处理器信息,在完成端口上,为已完成的I/O请求提供服务

 【4】准备好一个监听套接字,在端口5150上监听进入的连接请求。
 【5】 使用accept函数,接受进入的连接请求。
 【6】 创建一个数据结构,用于容纳“单句柄数据”,同时在结构中存入接受的套接字句柄。

 【7】 调用CreateIoCompletionPort,将自accept返回的新套接字句柄同完成端口关联到一起。通过完成键(CompletionKey)参数,将单句柄数据结构传递给CreateIoCompletionPort。
 【8】 开始在已接受的连接上进行I/O操作。在此,我们希望通过重叠I/O机制,在新建的套接字上投递一个或多个异步WSARecv或WSASend请求。这些I/O请求完成后,一个工作者线程会为I/O请求提供服务,同时继续处理未来的I/O请求,稍后便会在步骤3 )指定的工作者例程中,体验到这一点。
 【9】 重复步骤5 ) ~8 ),直至服务器中止。

具体实例代码:

#include <WINSOCK2.H>
#include <stdio.h>

#define PORT     5150
#define MSGSIZE 1024

#pragma comment(lib, "ws2_32.lib")

typedef enum
{
   RECV_POSTED
}OPERATION_TYPE;

typedef struct
{
WSAOVERLAPPED   overlap;
WSABUF          Buffer;
   char            szMessage[MSGSIZE];
DWORD           NumberOfBytesRecvd;
DWORD           Flags;
OPERATION_TYPE OperationType;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

DWORD WINAPI WorkerThread(LPVOID);

int main()
{
   WSADATA                  wsaData;
   SOCKET                   sListen, sClient;
   SOCKADDR_IN              local, client;
   DWORD                    i, dwThreadId;
   int                      iaddrSize = sizeof(SOCKADDR_IN);
   HANDLE                   CompletionPort = INVALID_HANDLE_VALUE;
   SYSTEM_INFO              systeminfo;
   LPPER_IO_OPERATION_DATA lpPerIOData = NULL;

   // Initialize Windows Socket library
   WSAStartup(0x0202, &wsaData);

   // Create completion port
   CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

   // Create worker thread
   GetSystemInfo(&systeminfo);
   for (i = 0; i < systeminfo.dwNumberOfProcessors; i++)
   {
     CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &dwThreadId);
   }

   // Create listening socket
   sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

   // Bind
   local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
   bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

   // Listen
   listen(sListen, 3);

   while (TRUE)
   {
     // Accept a connection
     sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
     printf("Accepted client:%s:%d/n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

     // Associate the newly arrived client socket with completion port
     CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)sClient, 0);
   
     // Launch an asynchronous operation for new arrived connection
     lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
       GetProcessHeap(),
       HEAP_ZERO_MEMORY,
       sizeof(PER_IO_OPERATION_DATA));
     lpPerIOData->Buffer.len = MSGSIZE;
     lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
     lpPerIOData->OperationType = RECV_POSTED;
     WSARecv(sClient,
       &lpPerIOData->Buffer,
       1,
       &lpPerIOData->NumberOfBytesRecvd,
       &lpPerIOData->Flags,
       &lpPerIOData->overlap,
       NULL);
   }

   PostQueuedCompletionStatus(CompletionPort, 0xFFFFFFFF, 0, NULL);
CloseHandle(CompletionPort);
closesocket(sListen);
WSACleanup();
return 0;
}

DWORD WINAPI WorkerThread(LPVOID CompletionPortID)
{
   HANDLE                   CompletionPort=(HANDLE)CompletionPortID;
   DWORD                    dwBytesTransferred;
   SOCKET                   sClient;
   LPPER_IO_OPERATION_DATA lpPerIOData = NULL;

   while (TRUE)
   {
     GetQueuedCompletionStatus(
       CompletionPort,
       &dwBytesTransferred,
       &sClient,
       (LPOVERLAPPED *)&lpPerIOData,
       INFINITE);
     if (dwBytesTransferred == 0xFFFFFFFF)
     {
       return 0;
     }
   
     if (lpPerIOData->OperationType == RECV_POSTED)
     {
       if (dwBytesTransferred == 0)
       {
         // Connection was closed by client
         closesocket(sClient);
         HeapFree(GetProcessHeap(), 0, lpPerIOData);       
       }
       else
       {
         lpPerIOData->szMessage[dwBytesTransferred] = '/0';
         send(sClient, lpPerIOData->szMessage, dwBytesTransferred, 0);
       
         // Launch another asynchronous operation for sClient
         memset(lpPerIOData, 0, sizeof(PER_IO_OPERATION_DATA));
         lpPerIOData->Buffer.len = MSGSIZE;
         lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
         lpPerIOData->OperationType = RECV_POSTED;
         WSARecv(sClient,
           &lpPerIOData->Buffer,
           1,
           &lpPerIOData->NumberOfBytesRecvd,
           &lpPerIOData->Flags,
           &lpPerIOData->overlap,
           NULL);
       }
     }
   }

return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完成端口(Completion Port)是一种高效的 I/O 模型,它充分利用了操作系统 I/O 处理的异步特性,可以在一个线程池中处理大量的 I/O 操作,提高系统的并发处理能力。下面是一个使用完成端口模型的简单程序设计示例: 1. 初始化完成端口 ```csharp HANDLE completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); ``` 2. 创建一组工作线程 ```csharp for (int i = 0; i < numThreads; i++) { HANDLE threadHandle = CreateThread(NULL, 0, WorkerThread, completionPort, 0, NULL); CloseHandle(threadHandle); } ``` 3. 向完成端口投递异步 I/O 请求 ```csharp // 打开文件 HANDLE fileHandle = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); // 创建 OVERLAPPED 结构体 OVERLAPPED *overlapped = new OVERLAPPED; ZeroMemory(overlapped, sizeof(OVERLAPPED)); // 向完成端口投递异步读取请求 ReadFileEx(fileHandle, buffer, bufferSize, overlapped, CompletionRoutine); ``` 4. 工作线程处理完成端口完成通知 ```csharp DWORD WorkerThread(LPVOID lpParam) { HANDLE completionPort = (HANDLE) lpParam; DWORD numBytes; ULONG_PTR completionKey; LPOVERLAPPED overlapped; while (GetQueuedCompletionStatus(completionPort, &numBytes, &completionKey, &overlapped, INFINITE)) { // 处理完成通知 CompletionRoutine(numBytes, completionKey, overlapped); } return 0; } ``` 5. 完成例程处理异步 I/O 请求的完成 ```csharp VOID CALLBACK CompletionRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) { // 处理异步 I/O 请求的完成 } ``` 以上是一个简单的完成端口模型程序设计示例,具体实现需要根据实际需求进行调整和优化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值