关于重叠I/O,参考《WinSock重叠I/O模型》;关于完成端口的概念及内部机制,参考译文《深度探索I/O完成端口》。

完成端口对象取代了WSAAsyncSelect中的消息驱动和WSAEventSelect中的事件对象,当然完成端口模型的内部机制要比WSAAsyncSelect和WSAEventSelect模型复杂得多。

IOCP内部机制如下图所示:

在WinSock中编写完成端口程序,首先要调用CreateIoCompletionPort函数创建完成端口。其原型如下:

WINBASEAPI HANDLE WINAPI

CreateIoCompletionPort(

HANDLE FileHandle,

HANDLE ExistingCompletionPort,

DWORD CompletionKey,

DWORD NumberOfConcurrentThreads );

第一次调用此函数创建一个完成端口时,通常只关注NumberOfConcurrentThreads,它定义了在完成端口上同时允许执行的线程数量。一般设为0,表示系统内安装了多少个处理器,便允许同时运行多少个线程为完成端口提供服务。每个处理器各自负责一个线程的运行,避免了过于频繁的线程上下文切换。

hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)

这个类比重叠I/O事件通知模型中(WSA)CreateEvent。

然后再调用GetSystemInfo(&SystemInfo);取得系统安装的处理器的个数SystemInfo.dwNumberOfProcessors,根据CPU数创建线程池,在完成端口上,为已完成的I/O请求提供服务。一般线程池的规模,即线程数 = CPU数*2+2。

下面的代码片段演示了线程池的创建。

// 创建线程池,规模为CPU数的两倍

for(int i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)

{

HANDLE ThreadHandle;

// 创建一个工作线程,并将完成端口作为参数传递给它。

if ((ThreadHandle = CreateThread(NULL, 0, WorkerThread, hCompletionPort,

0, &ThreadID)) == NULL)

{

printf("CreateThread() failed with error %d/n", GetLastError());

return;

}

// 关闭线程句柄

CloseHandle(ThreadHandle);

}

然后需要将一个句柄与已经创建的完成端口关联起来,这里主要指套接字AcceptSocket,以后针对这个套接字的I/O完成状态交由完成端口通知,程序接到完成通知后做善后处理。

这需要再次调用CreateIoCompletionPort函数(囧)。参数四NumberOfConcurrentThreads依旧填0,参数一一般就是AcceptSocket,参数二为上面创建的完成端口hCompletionPort。参数三即“完成键”,一般存放套接字句柄的背景信息,也就是所谓的“单句柄数据”。之所以把它叫作“单句柄数据”,因为它是用来保存参数一套接字句柄的关联信息。一般可简单定义如下:

typedef struct {

SOCKET Socket;

} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;

下面的代码片段演示了每次Accept返回时,调用CreateIoCompletionPort使返回的AcceptSocket与完成端口关联,并传递一个PerHandleData。

AcceptSocket = WSAAccept(Listen, NULL, NULL, NULL, 0);

PerHandleData->Socket = AcceptSocket;

CreateIoCompletionPort((HANDLE) AcceptSocket, hCompletionPort, (DWORD)PerHandleData, 0)

这个类比重叠I/O事件通知模型中设置(WSA)OVERLAPPED结构中的hEvent字段,使一个事件对象句柄同一个文件/套接字关联起来。

将套接字句柄与一个完成端口关联在一起后,便可以套接字句柄为基础,投递发送与接收请求,开始对I/O请求的处理。接下来,可开始依赖完成端口,来接收有关I/O操作完成情况的通知。从本质上说,完成端口模型利用了Win32重叠I/O机制。在这种机制中,像WSARecv()和WSASend()这样的WinSock API调用会立即返回。此时,需要由我们的应用程序负责在以后的某个时间,通过一个OVERLAPPED结构,来接收调用的结果。在完成端口模型中,要想做到这一点,工作者线程WorkerThread需要调用GetQueuedCompletionStatus函数,在完成端口上等待。

GetQueuedCompletionStatus函数原型如下:

WINBASEAPI BOOL WINAPI

GetQueuedCompletionStatus(

HANDLE CompletionPort,

LPDWORD lpNumberOfBytesTransferred,

LPDWORD lpCompletionKey,

LPOVERLAPPED *lpOverlapped,

DWORD dwMilliseconds );

When you perform an input/output operation with a file handle that has an associated input/output completion port, the I/O system sends a completion notification packet to the completion port when the I/O operation completes. The completion port places the completion packet in a first-in-first-out queue. TheGetQueuedCompletionStatus function retrieves these queued completion packets. —MSDN

这个类比重叠I/O事件通知模型中的WSAWaitForMultipleEvents/WSAGetOverlappedResult获得I/O操作结果。

参数一为创建线程池时传递的参数hCompletionPort,参数二提供一个DWORD指针,用来接收当I/O完成时实际传输的字节数。参数三即第二次调用CreateIoCompletionPort时传入的单句柄完成键,这里用于确定与CompletionPort绑定的具体哪个(套接字)句柄完成了I/O操作导致该函数返回。参数四即第二次调用CreateIoCompletionPort时传入的(套接字)句柄(AcceptSocket)投递重叠I/O请求(WSARecv/WSASend)时指定的(WSA)OVERLAPPED结构。实际操作中往往提供一个(WSA)OVERLAPPED扩展结构,这就是常说的“单I/O数据”。一种定义如下:

typedef struct{

OVERLAPPED Overlapped;

WSABUF DataBuf;

CHAR Buffer[DATA_BUFSIZE];

DWORD BytesSEND;

DWORD BytesRECV;

}OVERLAPPEDPLUS,PER_IO_OPERATION_DATA,*LPPER_IO_OPERATION_DATA;

这里的最后两个参数BytesSEND和BytesRECV与GetQueuedCompletionStatus函数返回时的ByteTransfered参数一起同步收发操作。

一般在调用CreateIoCompletionPort将套接字句柄与完成端口hCompletionPort关联后,还需要为AcceptSocket创建PerIOData,以便为后面调用WSARecv()/WSASend()提供(WSA)OVERLAPPED结构和缓冲区。为确保单I/O数据的生存周期延续到I/O完成,一般动态分配,待I/O完成再回收。对于I/O频繁的系统,则可以使用内存池,每次只是回收到空闲池,最后再真正释放。这样,可避免频繁的内存分配。可参考《MFC基于CPlex结构的内存池化管理》。

下面的是Accept返回,调用CreateIoCompletionPort之后的代码片段。

ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));

PerIoData->BytesSEND = 0;

PerIoData->BytesRECV = 0;

PerIoData->DataBuf.len = DATA_BUFSIZE;

PerIoData->DataBuf.buf = PerIoData->Buffer;

然后调用WSARecv,投递一个等待接收数据的I/O请求。

WSARecv(AcceptSocket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,  &(PerIoData->Overlapped), NULL)

注意参数一、参数二和参数六,实际上完成了每个AcceptSocket与PerIoData的捆绑。

由于调用CreateIoCompletionPort将套接字句柄与完成端口hCompletionPort关联起来了,所以针对AcceptSocket这个套接字句柄上的I/O请求(WSARecv)完成时,一个完成通知包将被投递到完成端口hCompletionPort消息队列中。GetQueuedCompletionStatus函数是用来获取排队完成状态,它使调用线程挂起,直到收到一个完成通知包才返回。

If the function dequeues a completion packet for a successful I/O operation from the completion port, the return value is nonzero. The function stores information in the variables pointed to by the lpNumberOfBytesTransferredlpCompletionKey, andlpOverlapped parameters.

If *lpOverlapped is NULL and the function does not dequeue a completion packet from the completion port, the return value is zero. The function does not store information in the variables pointed to by the lpNumberOfBytesTransferred andlpCompletionKey parameters. —MSDN

在工作者线程WorkerThread中调用GetQueuedCompletionStatus:

while(TRUE)

{

GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE)

if (BytesTransferred == 0) // 出错

{

printf("Closing socket %d/n", PerHandleData->Socket);

if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)

{

printf("closesocket() failed with error %d/n", WSAGetLastError());

return 0;

}

GlobalFree(PerHandleData);

GlobalFree(PerIoData);

continue;

}

// 根据lpNumberOfBytesTransferred, lpCompletionKey, and lpOverlapped参数进行处理

// ……

}

给GetQueuedCompletionStatus传递的参数三将PerIOData强制转换为(LPOVERLAPPED *) 结构,后面又要配合使用PerIOData的其他字段,这体现了“扩展”二字的用意。

若GetQueuedCompletionStatus返回FALSE,可以调用(WSA)GetOverlappedResult/(WSA)GetLastError获知具体错误。

如前面所言,完成端口模型利用了Win32重叠I/O机制,它是在利用完成端口队列对象来管理线程池。下面总结一下编写基于完成端口的Winsock服务器程序的要点。

(1)首先,当然要调用CreateIoCompletionPort创建一个完成端口,一般一个应用程序只创建一个完成端口。

(2)然后,创建一个线程池,把完成端口作为参数传给线程参数,以使工作线程调用GetQueuedCompletionStatus在完成端口上等待I/O完成,收到完成通知后提供I/O数据处理服务。

(3)每当Accept(Ex)成功返回后,调用CreateIoCompletionPort将AcceptSocket与完成端口关联起来,并传递AcceptSocket的上下文信息(即“单句柄数据”)给完成键参数。同时为AcceptSocket创建一个I/O缓冲区(即“单I/O数据”,扩展OVERLAPPED结构)。

(4)接着,AcceptSocket调用异步I/O操作函数,如WSARecv和WSASend,抛出重叠的I/O请求。这时需要将单I/O数据的第一个字段—OVERLAPPED结构—传递给WSARecv和WSASend,以表示它们投递的是“重叠”的I/O请求,需要等待系统的I/O完成通知。

(5)至此,当上一步抛出的重叠I/O操作完成时,完成端口上会有一个完成通知包,工作线程收到完成通知,从GetQueuedCompletionStatus返回。通过完成键即单句柄数据提供的客户套接字上下文信息、重叠结构参数以及实际I/O的字节数,就可以正式提供I/O数据服务了。

简言之,涉及两个重要的数据结构:“单句柄数据”和“单I/O数据”(扩展的OVERLAPPED结构);涉及两个重要的API: CreateIoCompletionPort和GetQueuedCompletionStatus;当然,不要忘记重叠请求的投递者WSARecv和WSASend,它们是导火索—通信程序的本质工作就是“通信”。

因为完成端口模型本质上利用了Win32重叠I/O机制,故(扩展的)OVERLAPPED结构提供的沟通机制依然是数据通信重要的线索。另外,要理解完成端口内部机制和工作原理及其在通信中的作用。

下面补充完成端口的项目应用实例。

Windows下的IIS采用了完成端口模型,参考《完成端口与高性能服务器程序开发》、《I/O完成端口(Windows核心编程)》、《A simple application using I/O Completion Ports and WinSock》。

Apache Httpd/httpd/server/mpm/winnt/child.c中的winnt_accept()(AcceptEx)和winnt_get_connection()使用了完成端口ThreadDispatchIOCP,但并没有关联套接字,而是自己构造完成包(mpm_post_completion_context→PostQueuedCompletionStatus),完成键为枚举io_state_e,单句柄为PCOMP_CONTEXT。

// Apache Httpd/httpd/server/mpm/winnt/mpm_winnt.h

typedef enum {

IOCP_CONNECTION_ACCEPTED = 1,

IOCP_WAIT_FOR_RECEIVE = 2,

IOCP_WAIT_FOR_TRANSMITFILE = 3,

IOCP_SHUTDOWN = 4

} io_state_e;

Apache源码中只使用到IOCP_CONNECTION_ACCEPTED和IOCP_SHUTDOWN两种状态。除此之外,Apache里面没有真正的完成端口成分,ntmpm似乎依然是线程池加进程池来处理。具体I/O过程参考Apache源码Apache Httpd/httpd-2.2.15/srclib/apr/file_io/win32/readwrite.c和Apache Httpd/httpd/srclib/apr/network_io/win32/sendrecv.c。

Nginx是由Igor Sysoev为俄罗斯访问量第二的Rambler.ru站点开发的,其特点是占有内存少,并发能力强,Nginx的并发能力确实在同类型的网页伺服器中表现较好。新浪、网易、腾讯、迅雷、CSDN等大型网站都采用了Nginx。Nginx每一个客户端请求进来以后会通过事件处理机制,在Linux是Epoll,在FreeBSD下是KQueue放到空闲的连接里。相关源码参考nginx/src/event/modules下的ngx_epoll_module.c、ngx_kqueue_module.h(c)。参考《基于IO完成端口与WSAEventSelect的nginx事件处理模块:ngx_iocp_module》、《Windows下完成端口移植Linux下的epoll》。

 

参考:

Network Programming for Microsoft Windows》  Anthony Jones,Jim Ohlund

Windows Internals  Mark E. Russinovich,David A. Solomon

Windows 2000 Systems Programming Black Book》  Al Williams

Multithreading Applications in Win32》  Jim Beveridge,Robert Wiener.

《Windows网络与通信程序设计》  王艳平

《IOCP完成端口原理》

《Write Scalable Winsock Apps Using Completion Ports》

《Design Issues When Using IOCP in a Winsock Server》

WinSock完成端口I/O模型相关推荐

  1. 一个对Winsock完成端口模型封装的类

    转载请按如下方式显示标明原创作者及出处,以示尊重!! 原创作者:elssann 联系方式:PPP elssann@hotmail.com 在Windows下进行网络服务端程序开发,毫无疑问,Winso ...

  2. Winsock 完成端口模型简介

    摘自<Networking Programming for Microsoft Windows>第八章 "完成端口"模型是迄今为止最为复杂的一种I/O模型.然而,假若一 ...

  3. Winsock异步模式I/O模型WSAEventSelect的使用

    1.Winsock同步阻塞方式的问题 在异步非阻塞模式下,像accept(WSAAccept),recv(recv,WSARecv,WSARecvFrom)等这样的winsock函数调用后马上返回,而 ...

  4. WinSock五种I/O模型的性能分析

    原文地址:http://club.topsage.com/thread-735498-1-1.html 五种I/O模型的性能分析 重叠I/O模型的另外几个优点在于,微软针对重叠I/O模型提供了一些特有 ...

  5. Win32多线程编程(6) — 多线程协作及线程的池化管理

    多线程级别的并行计算 写多线程应用程序最困难的地方在于如何使各线程的工作协调进行.Windows提供的用于线程间通信的各种机制是很容易掌握的,可是要把它们应用到工作中完成既定的功能时就会遇到这样.那样 ...

  6. C++学习笔记和面试备考(二, 转)

    简述局部作用域,全局作用域和类作用域的异同 一个定义于某模块中的函数的全局作用域是该模块的命名空间,而不是该函数的别名被定义或调用的位置 虽然作用域是静态定义的,在使用时作用域是动态的.在任何运行时刻 ...

  7. Winsock—I/O模型之选择模型(一)

    Winsock中提供了一些I/O模型帮助应用程序以异步方式在一个或多个套接字上管理I/O. 这样的I/O模型有六种:阻塞(blocking)模型,选择(select)模型,WSAAsyncSelect ...

  8. 模电里的二端口等效模型

    二端口线性等效模型的图解 选取 i1i_1i1​ 和 v2v_2v2​ 作为输出量,由于是线性模型,可以用另外两个变量表示出来 i1=f(v1,i2)v2=g(v1,i2)i_1=f(v_1,i_2) ...

  9. WinSock的I/O模型

    目录 一. 套接字的非阻塞工作模式 1.阻塞与非阻塞模式的概念 2.阻塞模式下能引起阻塞的套接字函数 3.两种模式的比较 2. 套接字非阻塞模式的设置方法--ioctlsocket 函数 3. 非阻塞 ...

最新文章

  1. 铺铜过孔不要十字_铺植草砖施工工艺
  2. b2b b2c o2o电子商务微服务云平台
  3. codelite14中文语言包_Windows下CodeLite完美支持中文的正确设置方法
  4. DL之DeepLabv3:DeepLab v3和DeepLab v3+算法的简介(论文介绍)、架构详解、案例应用等配图集合之详细攻略
  5. python性能测试模块_python模块介绍- multi-mechanize 通用的性能测试工具
  6. 【转载】SAP 计划策略组40、50对比分析
  7. ABAP实例之ALV
  8. 安装Was liberty之步骤
  9. freeredius3.0 mysql_EDIUS非线性编辑系统价格,4k视频编辑系统
  10. SQL Server Always On可用性组中的数据同步
  11. R2: 相关系数、复相关系数及半偏相关系数之间的联系
  12. vue-router的hash(地址栏中带#号)模式与History模式
  13. vc 获得桌面文件坐标_为何 Elementary OS 中使用 Pantheon 桌面
  14. 【iOS】屏幕适配之NSLayoutConstraint
  15. php的敏感词过滤类库,敏感词过滤的php类库
  16. %3c大自然的语言%3e竺可桢题目,大自然的语言竺可桢阅读答案
  17. 计算机主机后面辐射大,电脑背面辐射最大吗
  18. allegro 尺寸标注操作未到板边的处理
  19. PTA-寻找孪生素数
  20. java 读取.xlsx_java 读取xlsx

热门文章

  1. springboot的自动配置原理
  2. React是什么及特点
  3. 数据库-优化-数据库结构的优化-拆分优化
  4. 数组的定义格式三_省略的静态初始化
  5. SpringCloud导学
  6. SpringMVC+Spring+mybatis
  7. python requests get请求_python+requests+new——get请求各种情况
  8. python字典有什么用_在Python中使用范围作为字典键,我有什么选...
  9. 如何反复读取同一个 InputStream 对象
  10. Command ‘ifconfig‘ not found