[转]windows completionPort (完成端口模型 )

参考了很多资料,组织这些教程,也是个复习和再学习的过程,目的是共享还有以备以后可以作为复习的纲要。这几天看了很多资料,发现这些资料的行文有2种表达发式,一种是最常见的:从技术的源头说起,直到未来发展的趋势。还有一种比较开门见山,可能功利了些:首先这种技术是为了解决什么问题,用在什么地方,有什么好处,怎么用,呵呵:)整个一个倒叙,要是再结合项目的实践就更好了,个人比较喜欢这种表达发式,所以这篇文章的结构也是这么组织的。

问题
现在网络游戏很流行,上万个玩家同时在线的情况很常见,网游服务器如何处理这么巨量的数据?!要是读过了"Winsock I/O方法"这篇文章,可以了解到套接字I/O模型中的:select,WSAAsyncSelect,WSAEventSelect,Overlapped I/O模型一次最多都只能支持6 4个套接字!这些模型显然不能胜任。而Winsock I/O模型中的"Completion port",可以同时管理数百乃至上千个套接字,辅以集群技术和提升硬件配置等措施,应付网游肯定可以了。
 
对IOCP的评价
I/O完成端口可能是Win32提供的最复杂的内核对象。
                                                               [Advanced Windows 3rd] Jeffrey Richter
这是[IOCP]实现高容量网络服务器的最佳方法。
[Windows Sockets2.0:Write Scalable Winsock Apps Using Completion Ports] 
Microsoft Corporation
 完成端口模型提供了最好的伸缩性。这个模型非常适用来处理数百乃至上千个套接字。
                                        [Windows网络编程2nd] Anthony Jones & Jim Ohlund
 I/O completion ports特别显得重要,因为它们是唯一适用于高负载服务器[必须同时维护许多连接线路]的一个技术。Completion ports利用一些线程,帮助平衡由I/O请求所引起的负载。这样的架构特别适合用在**P系统中产生的"scalable"服务器。
                                        [Win32多线程程序设计] Jim Beveridge & Robert Wiener      
IOCP的限制
该模型只适用于Windows NT和Windows 2000操作系统


什么是IOCP?

IOCP全称I/O Completion Port,中文译为I/O完成端口。所谓"完成端口",实际是Win32、Windows NT以及Windows 2000采用的一种I/O构造机制,除套接字句柄之外,实际上还可接受其他东西。IOCP是一个异步I/O的API,它可以高效地将I/O事件通知给应用程序。从本质上说,完成端口模型要求我们创建一个Win32完成端口对象,通过指定数量的线程,对重叠I/O请求进行管理,以便为已经完成的重叠I/O请求提供服务。IOCP只不过是用来进行读写操作,和文件I/O有些类似。

使用IOCP

Microsoft为IOCP提供了相应的API函数,主要的就两个: CreateIoCompletionPort( ... ), GetQueuedCompletionStatus( ... )。
 
1.首先要创建一个I/O完成端口对象,用它面向任意数量的套接字句柄,管理多个I/O请求。
HANDLE CreateIoCompletionPort(
    HANDLE FileHandle,//既然要处理网络事件,那也就是将客户的socket作为HANDLE传进去
    HANDLE ExistingCompletionPort,//是一个现有的完成端口
    DWORD CompletionKey,
    DWORD NumberOfConcurrentThreads//并发线程的数量
);
*NumberOfConcurrentThreads告诉系统一个完成端口上同时允许运行的线程最大数。在默认情况下,所开线程数和CPU数量相同,经验公式:  线程数 = CPU数 * 2 + 2。若将该参数设为0,表明系统内安装了多少个处理器,便允许同时运行多少个线程!
此函数有2种用法,不同的作用:
1.创建一个完成端口对象
hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
2.将一个句柄同完成端口关联到一起
CreateIoCompletionPort((HANDLE)hAccept,hCompletionPort,(DWORD)PerHandleData,0);
 
2. 接收有关I / O操作完成情况的通知
BOOL GetQueuedCompletionStatus(
  HANDLE CompletionPort,//线程要监视哪一个完成端口
  LPDWORD lpNumberOfBytes,//接收实际传输的字节数
  PULONG_PTR lpCompletionKey,//为原先传递进入CreateCompletionPort函数的套接字返回"单句柄数据"
  LPOVERLAPPED *lpOverlapped, //接收完成的I/O操作的重叠结果
  DWORD dwMilliseconds         //指定等待一个完成数据包在完成端口上出现的时间。
);
一个工作者线程从G e tQueuedCompletionStatus这个API调用接收到I/O完成通知后,在lpCompletionKey和lpOverlapped参数中,会包含一些必要的套接字信息。利用这些信息,可通过完成端口,继续在一个套接字上的I/O处理。
lpCompletionKey参数包含了"单句柄数据",因为在一个套接字首次与完成端口关联到一起的时候,那些数据便与一个特定的套接字句柄对应起来了。通常情况下,应用程序会将与I / O请求有关的套接字句柄保存在这里。
lpOverlapped参数则包含了一个OVERLAPPED结构,在它后面跟随"单I / O操作数据"。单I / O操作数据可以是追加到一个OVERLAPPED结构末尾的、任意数量的字节。要想做到这一点,一个简单的方法是定义一个结构,然后将OVERLAPPED结构作为新结构的第一个元素使用。
 
同时运行了一个或多个线程,在几个不同的套接字上执行I/O操作的时候。要避免的一个重要问题是在进行重叠I/O操作的同时,强行释放一个OVERLAPPED结构。要想避免出现这种情况,最好的办法是针对每个套接字句柄,调用closesocket函数,任何尚未进行的重叠I/O操作都会完成。一旦所有套接字句柄都已关闭,便需在完成端口上, 终止所有工作者线程的运行。要想做到这一点,需要使用PostQueuedCompletionStatus函数,向每个工作者线程都发送一个特殊的完成数据包。该函数会指示每个线程都"立即结束并退出"。

大致的流程:

 1.初始化Winsock
 2.创建一个完成端口
 3.根据服务器线程数创建一定量的线程数
 4.准备好一个socket进行bind然后listen
 5.进入循环accept等待客户请求
 6.创建一个数据结构容纳socket和其他相关信息
 7.将连进来的socket同完成端口相关联
 8.投递一个准备接受的请求
 以后就不断的重复5至8的过程
 
示例


1.
HANDLE InitializeThreads()
...{
HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (hCompletionPort == NULL)  return NULL;
    for (i = 0; i < g_dwNumThreads; i++)
    ...{
        hThread = CreateThread(NULL, 0, WorkerThreadProc, hCompletionPort, CREATE_SUSPENDED, &dwThreadId);
        if (hThread == NULL)...{
            CloseHandle(hCompletionPort);
            return NULL;
        }
        SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL);
        ResumeThread(hThread);
        CloseHandle(hThread);
    }
    return hCompletionPort;
};
 
2.
g_hCompletionPort = InitializeThreads();
AcceptClients();
3.
BOOL AcceptClients()
...{
    ...
    g_nNumSocket = EnumNetworkAdapter();
    if (g_nNumSocket == 0)  return FALSE;
 
    for (int nIndex = 0; nIndex < g_nNumSocket ; nIndex++)
    ...{
        ...
        g_interface[nIndex].hSocket = socket(AF_INET, SOCK_STREAM, 0);
        if (g_interface[nIndex].hSocket == INVALID_SOCKET) return FALSE;
        nError = bind(g_interface[nIndex].hSocket, (LPSOCKADDR)&sin, sizeof(sin));
        nError = listen(g_interface[nIndex].hSocket, 5);
        ...
    }
    while ( TRUE )
    ...{
        for (i = 0; i < g_nNumSocket; i++)
        ...{
            FD_SET(g_interface[i].hSocket, &readfds);
        }
        nRet = select(0, &readfds, NULL, NULL, NULL);
        if ((nRet == 0) || (nRet == SOCKET_ERROR)) continue;
 
        for (i = 0; i < g_nNumSocket; i++)
        ...{
            if (!FD_ISSET(g_interface[i].hSocket, &readfds))
                continue;
            hSocket = accept(g_interface[i].hSocket, NULL, NULL);
            ...
            g_hCompletionPort = CreateIoCompletionPort(
                     (HANDLE)hSocket,
                     g_hCompletionPort,
                     lpClientContext->dwVerifyClientContext,
                     0);
            if (g_hCompletionPort == NULL)...{
              CloseClient(lpClientContext);
              continue;
           }
            ...
            nError = setsockopt(hSocket, SOL_SOCKET, SO_SNDBUF, (char *)&nZero, sizeof(nZero));
            if (nError == SOCKET_ERROR)
            ...{
            PostQueuedCompletionStatus(g_hCompletionPort, 0, (DWORD)lpClientContext->dwVerifyClientContext, NULL);
            continue;
            }
            ...
        }
    return TRUE;
}
4.
DWORD WINAPI WorkerThreadProc(LPVOID lpCmpltPort)
...{
    HANDLE hCompletionPort = (HANDLE)lpCmpltPort;
    ...
    while (TRUE)
    ...{
       GetQueuedCompletionStatus(hCompletionPort,dwIoSize,&dwClientNo,&lpOverlapped,INFINITE);
...
    }

 

如果原作者认为侵权,请与我联系。

  • 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、付费专栏及课程。

余额充值