博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
IOCP在服务器开发中的应用[转自掰掰开发]
阅读量:5149 次
发布时间:2019-06-13

本文共 4790 字,大约阅读时间需要 15 分钟。

引言 

基于Socket的网络通信服务已经使用得相当普遍,然而一个服务器应用程序,假如不能够同时为多个客户端提供服务,那它就没有什么意义可言。针对一个服务器应用程序底层通信模块的设计,要使其在给定的时间内同时控制几个套接字,采用重叠的I/O机制是比较好的,但是要求服务器在任何给定时间内都会为海量I/O请求提供服务,Winsock 2.0中引入的内核级完成端口(Input/Output Completion Port,IOCP)是处理大量并发连接的最佳处理方案。相对于其他I/O模型,IOCP针对操作系统内部进行了优化,提供了较好的伸缩性和较高的数据吞吐率,满足服务器的高性能要求。文中针对基于IOCP通信模式的服务器程序的实现进行了探讨。

一、完成端口(IOCP) 

    IOCP模型是微软提供的用于Windows系统上高效处理各种设备I/O的一种机制,它提供了一个高效复杂的内核对象,该对象通过指定数量的线程。对重叠的I/O操作完成进行处理。它的核心思想简单概括如下:将所有用户的请求投递到一个消息队列中,利用事先创建好的若干个工作者线程逐一从消息队列中取出消息并加以处理。它可以为任何用户的任何I/O操作服务,只需少数几个线程就可以处理大量I/O请求,避免CPU花费时间在大量的线程调度上,提高了资源的利用率。

    1.1 重叠I/O 

    IOCP模型是基于重叠I/O技术的。重叠I/O(Overlapped I/O)是Win32的一项技术,它的基本原理是让应用程序使用一个重叠的数据结构(OVERLAPPED),一次投递—个或多个I/O请求。不论该请求是否已经完成,做投递动作的函数马上返回,I/O的实际工作则交由操作系统底层处理。

    1.2 工作者线程 

    IOCP使用多线程机制,它创建并管理若干个工作者线程[Worker Threads]。工作者线程服务于IOCP,用于处理到达的I/O完成通知。

   1.3 信息的传递 

工作者线程调用相应API函数接收到I/O完成通知。通过参数的传递,可获得与之相关的两方面重要的套接字数据;单I/O操作数据和单句柄数据。系统利用OVERLAPPED结构进行重叠I/O操作。这个结构后边跟着“单I/O操作数据”,通过构建一个新的类来组织它们。“单句柄数据”是指—个套接字句柄首次与IOCP相关联时所使用的一个数据结构,它存储与这个特定的套接字句柄相关联的上下文信息。

二、IOCP应用中存在的问题及解决办法 

在实际应用IOCP模型来开发服务器/客户端的过程中会遇到各种各样棘手的问题,详述如下。

    2.1 WSAENOBUFS出错 

伴随着每一次重叠发送和接收操作,其中指定的发送或接收缓冲区会被加锁。而操作系统会强行为能被锁定的内存的大小设定一个上限,当达到这个上限,重叠操作将失败,并发送WSAENOBUFS错误。假如服务器在每个连接上提供多个重叠接收,随着连接数量的增长,很快就会达到这个极限。因此,服务器可以在每个连接上循环投递使用零字节缓冲区的接收,这时的接收操作和内存无关,内存不需要被锁定。并且当带零缓冲区的接收操作完成并返回时,每一个套接字的底层缓冲区的数据被完整地保留而没有被读取到接收操作的缓冲区来。由于每个投递操作是按顺序返回的,总能保证有被锁定的内存被成功解锁,此时服务器又可以投递一个或多个带非零字节缓冲区的重叠接收操作,将存在于套接字缓冲区中的数据读出来。

    2.2 数据包的重排序 

尽管使用IOCP,可以使数据按照它们被发送的顺序进行可靠的处理,但是实际工作者线程的完成顺序是不确定的,例如,存在两个工作者线程,并且接收到了“数据包A、数据包B、数据包C”,就有可能按另一个顺序处理它们,如“数据包C、数据包A、数据包B”。这也意味着,当你通过投递发送请求到IOCP来发送数据时,待发送的数据也被重新排序了。针对该问题,对内存类增加顺序号,并按照顺序号来依次处理内存,而具有不正确顺序号的内存将被保存起来备用。

    2.3 异步阻塞读和字节块包处理 

    TCP协议是基于包的流协议,包头包含着一个完整包长度的信息,系统通过读取包头得到该长度数据,并将其与已读数据的长度作比较来判断是否读包完成。当系统只提供一个异步阻塞读操作(即一个重叠接收)时,该机制完美无缺;但是为了完全发挥IOCP的能力,采用多个异步读操作,在同一个时间段内,多个读操作等待的数据会同时到达。类同2.节所讨论,多个异步读的无序完成会导致返回字节块流的无序,进一步说,一个字节块流可能包含一个或多个包,或者包的一部分。

图1表明部分包(黑色块)和完整包(白色块)在字节块流中是如何异步到达的。这就意味着要想成功解读一个完整的应用层逻辑数据包,必须处理字节流数据块和部分包。在此需要编写专门的包处理函数来解决。其中,“把几个数据包合并成一个数据包”的拼包操作必然会涉及内存的复制拷贝。在此采用“环形缓冲区”思想:模块处理完一次从缓冲区里取走所有完整逻辑数据包后,把新接收的数据包直接复制到缓冲区内剩下数据的尾部,直接进行下一次逻辑解析,避免了频繁调用内存拷贝函数。

 

 2011050823171133.png

 

2.4 访问紊乱(Access Violation) 

这不是IOCP特有的问题,却是编码设计所带来的结果。在IOCP的API函数调用中,要为它的参数传递一个指向客户端特定数据的结构体(CItentContext)的指针。倘若一个客户端连接丢失,并且释放该客户端数据结构体所占用的内存,当该客户端之前所执行的一些I/O调用返回了错误码,此时需要试图去访问或删除这个客户端结构体时,一个访问紊乱就发生了。为了避免该现象的产生,为客户端结构体增加一个阻寒I/O调用的计数,只有当计数为零,即不存在阻塞I/O调用时,才删除这个结构体。

三、基于IOCP的服务器开发应用实例 

    3.1 IOCP的主要API函数介绍和数据结构的设计 

    3.1.1 CreateIocompletionPort函数 

该函数主要有两个作用:一是“创建”一个完成端口,二是将一个Socket句柄与已经创建的完成端口句柄“绑定”,绑定之后,基于该Socket的收、发、断开等事件都可以被完成端口感知。

    3.1.2 GetQueuedCompletionstatus函数 

若干的工作者线程通过循环调用该函数从IOCP消息队列取得完成通知,并通过其参数来传递“单I/O操作数据”和“单句柄数据”等信息。

    3.1.3 PostQueuedcompletionstatus函数 

该函数允许应用程序将自定义的专用I/O完成包进行排队,作用相当于一个异步I/O调用:一般情况下是通过直接调用WSASEND/WSARECV函数来进行实际的异步调用。但为了公平地分配CPU,当应用程序想要执行一个I/O调用操作时,它并不直接去做,而是将其发送到IOCP,这些操作被I/O工作者线程执行。该函数也可用于线程的终止退出:在主线程中调用它给IOCP的每个线程都发送一个特殊的I/O完成包通知线程结束,那么关联在该IOCP上的工作者线程可以获得此包,并结束自己。

    3.1.4 OVERLAPPED结构 

该结构和其他与I/O操作关联在一起的某些营要数据元素封装在一个类中。要重点强调的是,这个数据要被加锁,并且不可超出物理内存页,具体定义如下:

 

 

3.1.5 ClientContext结构 

监听线程中,一旦有新的客户连接发生时,将给其分配一个描述客户上下文信息的结构,一般包括客户端的套接字以及与该套接字相关的读写缓冲区等信息。具体定义如下:

3.2 IOCP的工作流程介绍 

采用IOCP模型为服务器端应用程序的网络接口部分提供服务,主要有两种类型的线程:主线程和工作线程。主线程负责创建并监听套接字,创建工作线程,等待并接受到来的连接,将其关联到IOCP等。而工作线程则负责等待并处理在IOCP对象上完成的事件。对于工作者线程,它循环调用API函数从IOCP消息队列中取I/O完成包:IOCP释放一个等待中的线程来进行数据处理。如果目前没有线程正在等待,它不会产生新的线程,当执行中的线程处理完毕之前的I/O请求后才返回到完成端口进行等待。这样就保证了工作者线程总是保持一个稳定的数量。数据处理完毕后,根据需要发出异步I/O请求操作,然后继续下一次循环,处理未来的I/O请求。

因此,IOCP的总体执行过程为:主线程不断地循环接收客户端的请求,投递异步的I/O请求,操作系统完成实际的I/O处理后,把结果送到IOCP消息队列上,而等待着的工作者线程调用函数从IOCP不断地取出I/O操作结果,进行数据处理,然后根据需要再发出异步I/O请求,循环往复直至应用程序退出,具体的程序执行流程如图2所示。

 

图2:程序执行流程

    3.3 IOCP模型在服务器中应用的性能分析 

文中应用IOCP模型实现了一个基于Winsock 2.0的集文本消息传递与文件传输于一体的服务器/客户端双向交互的应用体系。采用IOCP模型开发服务器最大的优点就是可以支持大并发量的Socket连接。在此,从服务器的客户端并发量、响应情况和资源消耗程度3个方面对服务器系统性能进行测试和分析。相对应地,从最大支持的客户端Socket连接个数、客户端饥饿数(定义客户端饥饿数为在规定时间内得不到服务器响应的客户端个数)、单位时间内线程上下文切换次数3个指标进行度量。为了凸显IOCP模型的优势,在服务器响应情况和资源消耗程度两个方面,将其与基于线程的服务器模型,即为每个客户端连接创建一个线程的模型,作了一个简单的性能对比。随着客户端连接数(N)的增加,分别观察各自产生的客户端饥饿数(U)和单位时间内线程上下文切换次数(T/S)这两个指标的变化情况。

    3.3.1 测试环境 

局域网6台AMD64 2*CPU 2.31 GHz 1G内存的台式机。一台用于运行服务器,其他5台作为客户主机用于模拟客户端。每台客户主机上运行一个基于IOCP的客户端,它可以循环发起多达4000个Socket连接来模拟实际客户端的连接情况。

    3.3.2 测试结果和分析 

经测试验证,基于IOCP模型的网络通信服务器同时可以管理的客户端Socket连接数已经达到20000个,并可进行简单的并发通信,此时CPU占用率稳定在50%左右。客户端饥饿数对比如图3所示。由于IOCP维护了一个先进先出的消息队列来处理服务,没有出现客户端得不到服务的情况。而后者随着客户端数的增加,得不到服务的客户端数也随之增长。

 

图3:客户端饥饿状况对比

单位时间内线程上下文切换次数对比如图4所示。前者采用IOCP,只使用数量固定的有限个线程,线程的上下文切换次数基本与操作系统正常运行时的次数相当。相反,后者随着客户端连接数的增加,为之分配的线程数也相应增加,线程上下文频繁切换,导致大量的CPU和内存开销。综上所述,采用IOCP模型开发的通信服务器既可以支持管理大并发量的Socket连接,又可以为所有客户端提供及时的服务响应,同时在系统资源的消耗上面也相对较小。

 2011050823184290.png

 

图4:线程上下文切换次数对比

四、结语 

    IOCP提供了一种利用有限的资源(I/O工作者线程)公平地处理多客户端的输入/输出问题的解决办法。它与Winsock,重叠I/O技术一起使用,对于创建高扩展性和高健壮性的能够为大量客户服务的大规模网络通信服务器来说是一种强大的机制。当然,在IOCP的实际使用过程中,还必须考虑到网络通信安全等更多的内容。总之,IOCP模型给服务器端开发中底层通信模块的设计与实现提供了很好的解决方案。

文章摘自“掰掰开发”

 

 

 2011050823182587.png

 2011050823180997.png

 2011050823175257.png

 2011050823173613.png

转载于:https://www.cnblogs.com/fxh7622/archive/2011/05/08/2040696.html

你可能感兴趣的文章
dp练习(9)——最大乘积
查看>>
上传文件的大小
查看>>
本地模拟服务器CDN(静态HTML,CSS,JS)开发
查看>>
linux学习一
查看>>
Codeforces Round #484 (Div. 2)
查看>>
「北京」京东 JD.COM 招聘中/高级前端工程师
查看>>
Block Towers (思维实现)
查看>>
0911,练习题
查看>>
T- SQL性能优化详解
查看>>
javascript 操作 cookie 【转】
查看>>
数据库设计
查看>>
apicloud模块开发知识点
查看>>
C#3.0 语言基础扩充
查看>>
jQuery插件之-瀑布流插件
查看>>
代码详查中的自尊心
查看>>
[珠玑之椟]位向量/位图的定义和应用
查看>>
Root Pane Containers(一)
查看>>
php本地及远程文件包含漏洞
查看>>
[asp.net]网页与服务器的交互模型
查看>>
19-template转render写法
查看>>