首页 > 范文大全 > 正文

高性能I/O完成端口服务器的实现与优化

开篇:润墨网以专业的文秘视角,为您筛选了一篇高性能I/O完成端口服务器的实现与优化范文,如需获取更多写作素材,在线客服老师一对一协助。欢迎您的阅读与分享!

摘要:该文介绍了I/O完成端口机制,并提出了基于I/O完成端口机制的高性能服务器设计的一种方法。改文所设计的高性能服务器能够处理海量套接字连接请求,保证了数据通信的高效性,很好的解决了像网络游戏服务器、web服务器以及服务器这些爆发式客户端连接的问题。经过压力测试,该IOCP服务器达到了6500客户端同时在线的负载标准。

关键词:套接字;I/O完成端口;套接字I/O模型;重叠I/O;服务器

中图分类号:TP311文献标识码:A文章编号:1009-3044(2009)33-9607-04

Implementation and Optimization of High-performance I/O Completion Ports Server

LIN Qiang, LIU Tun-dong, CAI Jian-li, JIANG Hao

(Dept. of Automation, Information Science and Technology College, Xiamen University, Xiamen 361005, China)

Abstract: This paper mainly introduces the I/O completion ports mechanism and brings forward a method to design high-performance server which base on this mechanism. This kind of server has the ability to handling with massive connection request , the efficiency of data communication and deals with the outbreak client connection such as online game server, web server, proxy server and so on. After stress testing, this IOCP server has achieved the load requirements that 6500 client online at the same time.

Key words: socket; I/O completion ports; socket I/O models; overlapped I/O; server

在Winsock网络通信应用中,通信的两个进程间相互作用的主要模式是客户/服务器(C/S)模式,即客户端向服务器发起连接请求,服务器在收到连接请求后,与客户端建立相应的连接并收发数据和进行数据处理的服务。当你开发不同类型的软件时,总是需要进行c/s模型的开发。然而,完成一个完善的C/S模型代码对于编码人员来说并不是一件容易的事情。特别是当服务器需要处理大量客户端连接的时候,利用普通的Winsock模型来进行编码会显得相当棘手。

本文所研究的服务器采用了IOCP(I/O Completion Port)技术,该技术可以在不丧失系统整体性能的前提下,有效解决成百上千个客户端连接的问题。IOCP技术对于“一个客户端一个线程”的技术瓶颈问题提出了一个有效地解决方案:只需要用到少量的处理线程和异步I/O处理函数。与此同时,基于IOCP技术的服务器会随着cpu数量的增加而线性的提升系统的整体性能。本文主要阐述了IOCP技术的原理,并进一步探讨IOCP的特性、开发技术、难点和为性能的提高而提出若干解决芳案,从而有效地利用系统资源、为高性能服务器的开发提供全面的支持,并已在某系统的服务器的通信的实际应用项目中实现了海量客户服务需求。

1 IOCP技术原理

IOCP(I/O Completion Port)又称为I/O完成端口,是Microsoft提供的用于Windows上高效处理各种设备I/O的一种机制,设备可以是文件、命名管道、套接字、串口、并口等。I/O完成端口是基于这样一个理论基础:并行运行的线程数目必须有一个上限,这是因为一旦运行的线程数目超过cpu的数目,系统就必须花费时间来进行线程上下文的切换,这样会浪费cpu周期[1]。如果应用程序在初始化时创建了一个线程池,而且这些线程在应用程序执行期间是空闲的,应用程序的性能就能进一步提高。而完成端口本身就是使用了线程池技术。完成端口模型的原理图如图1所示。

完成端口要求创建一个win32完成端口对象来对重叠I/O请求进行管理,并通过创建一定量的工作线程来为已经完成的重叠I/O请求提供服务。我们可以把完成端口看成系统维护的一个队列,由于是“操作完成”的事件通知,故取名为“完成端口”[2]。

1.1 套接字I/O模型

共有五种类型的套接字I/O模型,可以让Winsock应用程序对I/O进行管理,包括:Select(选择)模型、WSAAsyncSelect(异步选择)模型、WSAEventSelect(事件选择)模型、Overlapped(重叠)模型以及Completion Port(完成端口)模型。这里主要介绍Completion Port模型,其它模型本文不再赘述。

Completion Port模型是迄今为止最为复杂的一种I/O模型。从本质上说,完成端口模型要求我们创建一个Win32完成端口对象,通过指定数量的线程,对重叠I/O请求进行管理,以便为已经完成的重叠I/O请求提供服务。当你需要处理海量套接字连接,并且希望系统的系能随着cpu数量的增加而线性提升时,完成端口模型是你的最佳选择。

1.2 重叠I/O

完成端口的设计原理是让应用程序使用重叠的数据结构,一次投递一个或多个I/O请求,当这些请求完成后,应用程序可以为他们提供服务。这就要求我们在使用完成端口时必须要使用重叠I/O。重叠I/O,即当I/O功能调用时,不论I/O是否完成,函数马上返回,由操作系统底层处理I/O的实际工作,而应用程序(进程)可以继续做其它事情。因而,完成端口是处理完成重叠I/O的一种高效的机制。

1.3 完成端口

1.3.1 I/O完成通知

完成端口维护一个完成通知队列。重叠I/O操作完成后系统会把已完成的重叠I/O请求通知放入该队列队尾。

1.3.2 工作线程

成功创建一个完成端口后,便可以开始将套接字句柄与完成端口对象关联到一起。但在关联套接字之前,首先必须创建一个或多个工作线程,以便在I/O请求投递给完成端口对象后,为完成端口提供服务[3]。工作线程的个数取决于应用程序的总体设计情况。创建的工作线程由完成端口管理。当有I/O完成通知到来,则由完成端口唤醒一个工作线程接收I/O完成通知,并对其进行处理。完成端口自动对工作线程进行调度,唤醒哪个工作线程则由完成端口决定。若无I/O完成通知,则所有的工作线程都在等待。

需要注意的是,完成端口对工作线程的管理具有一定的原则:首先在创建完成端口时要指定最大并发线程数,一般情况是一个处理器对应一个线程,而工作线程的数量大于或等于最大并发线程数。考虑到线程会进入挂起的状态,为了让应有程序有足够的工作线程为I/O请求服务,一般创建工作线程的个数为cpu个数的两倍。

2 IOCP服务器难点及其实现

由于IOCP是以Windows NT为基础的操作系统推出的内核高级处理机制,并利用该机制对Winsock的通信进行管理,所以IOCP机制的实现与Microsoft系列的技术联系甚为紧密。笔者将在接下来的篇幅中剖析IOCP机制基于C#语言的实现方法,以及针对在实现IOCP服务器过程中可能会遇到的一些难点进行分析。

2.1 服务器实现

IOCP服务器的流程如图2所示。

I/O完成端口的主线程处理流程和代码大致如下:

1) 利用CreateIoCompletionPort函数创建一个完成端口。其中CreateIoCompletionPort函数的第四个参数设为0。

IntPtr CompletionPort=CreateIoCompletionPort(

INVALID_HANDLE_VALUE,

IntPtr.Zero,

IntPtr.Zero,

0);

2) 判断系统内安装的处理器个数。

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

for(int i=0;i

{

Thread thread=new Thread(ThreadProc);

thread.Start(CompletionPort);

}

4) 准备好一个监听套接字,并在制定端口上监听进入的连接请求。

5) 主线程循环调用accept函数等待客户端的连接请求。

6) 创建一个数据结构,用于容纳“单句柄数据”,同时在结构中存入接受的套接字句柄。

class PerHandleData

{

public SafeSocketHandle Socket;

}

……

PerHandleData perHandleData=new PerHandleData();

GCHandle gch_PerHandleData =

GCHandle.Alloc(PerHandleData);

PerHandleData.socket=Accept;

7) 调用CreateIoCompletionPort函数,将自accept函数返回的新套接字句柄同完成端口关联到一起。通过CreateIoCompletionPort函数的完成键(CompletionKey)参数,将单句柄数据结构传递给CreateIoCompletionPort函数。

IntPtr IOCP=CreateIoCompletionPort(

Accept.DangerousGetHandle(),

CompletionPort,

GCHandle.ToIntPtr(gch_PerHandleData),

processorCount);

8) 开始在已接受的连接上进行I/O操作。在此,我们希望通过重叠I/O机制,在新建的套接字上投递一个或多个异步WSARecv或WSASend请求。这些I/O请求完成后,一个工作线程会为I/O请求提供服务,同时继续处理未来的I/O请求。

重复步骤5)―8),直至服务器停止或中止。

2.2 IOCP服务器设计的技术难点

进行IOCP服务器设计的时经常会遇到一些问题,而其中的一些问题却不是那么直观的。这些难点主要包括WSAENOBUGS错误问题、数据包重排序问题以及对内存资源的释放问题这几个方面。

2.2.1 WSAENOBUGS错误问题

每次进行重叠发送或接收操作时,被提交的数据缓冲区都是要被锁住的。当内存锁住时,这个缓冲区就不能被换页到物理内存外。而一个操作系统限制了可以被锁住的内存大小,当超出了限制范围,重叠操作就会因为WSAENOBUGS错误而失败[4]。

如果一个服务器在每个连接上进行了许多重叠的receive操作,那么限制会随着连接数的增长而变化。如果一个服务器能够预先估计可能产生的最大并发连接数,服务器可以投递一个使用零缓冲区的receive操作到每一个连接上。因为当提交的操作没有缓冲区时,那么也就不会存在内存被锁定的问题。在使用这种办法后,当你的receive操作事件完成并返回时,该socket底层缓冲区的数据会原封不动的留在其中,而不会被读取到receive操作的缓冲区中。此时,服务器可以简单的调用非阻塞式接收将socket缓冲区中的数据全部读出来,直到返回 WSAEWOULDBLOCK 为止。 这种设计非常适合那些可以牺牲数据吞吐量而换取巨大并发连接数的服务器。当然,也需要意识到如何让客户端的行为尽量避免对服务器造成影响。因此,提出以下两种解决方法:

1) 投递使用空缓冲区的receive操作,当操作返回后,使用非阻塞式recv函数进行真实数据的读取。因此在完成端口的每一个连接中需要使用一个循环的操作来不断提交空缓冲区的receive操作。

2) 在投递一个普通含有缓冲区的receive操作后,紧接着开始循环投递一个空缓冲区的receive操作。这样保证他们按照投递顺序依次返回,这样我们就总能对被锁定的内存进行解锁。

2.2.2 数据包重排序问题

虽然I/O完成端口的提交操作总是按照它们被提交的顺序完成,但线程调度安排可能使绑定到完成端口的实际工作不按指定的顺序来处理[5]。例如,假设有两个IO工作线程,应该要接收到的数据应该是“字块1,字块2,字块3”,但可能接收到的数据顺序是“字块2,字块1,字块3”。这就意味着通过I/O完成端口进行数据发送时,数据可能会被以另外的顺序进行发送。

一个实际的解决方法是添加一个顺序号给buffer类,如果缓冲区顺序号正确,则处理缓冲区中的数据。而顺序号不正确的缓冲区必须保存下来以便以后用到,因为性能的原因,我们将这些缓冲区保存到一个hash map对象中。

2.2.3 内存资源的释放问题

在进行IOCP服务器开发时,经常会遇到的一个难题就是与socket相关的缓冲区释放不当带来的错误,这种错误通常是由于多次对同一个指针执行了删除操作引起的。例如在执行异步接收或者异步发送函数后返回了非IOPENDING的错误时,就需要对这种错误进行处理。通常情况下,我们会执行下面这两步操作:1) 释放此次操作所使用的缓冲区;2) 关闭当前操作所使用的socket资源。然而,我们完全有可能在工作线程中的GetQueuedCompletionStatus函数返回false时也进行了上述两部相同的操作。此时,系统就会对同一缓冲区进行重复释放,这就是错误产生的原因。针对前述情况,可以通过在clientsock的对象设计机制上使释放操作归一化。比如在进行异步发送或者异步接收操作时发生了非IOPENDING错误,此时并不释放资源,而是通过PostQueuedCompletionStatus函数向完成端口抛出一条特殊标志的信息,这个特殊标志的信息可以通过GetQueuedCompletionStatus函数的第二个参数:即传送字节来表示,我们可以选择任何一个不可能出现的值,比如说一个跟它的初始值不相等的负数。经过这样的机制处理后,便可以将各种需要释放内存资源的情况统一到GetQueuedCompletionStatus函数上来处理。因此,需要执行释放的逻辑有:

1) GetQueuedCompletionStatus函数返回值为FALSE;

2) 传送的字节数为0;

3) GetQueuedCompletionStatus函数接收到PostQueuedCompletionStatus函数投递的特殊标志信息。

3 服务器性能的提升

前面有提到完成端口的最大优点在于它管理海量连接时的处理效率,这里需要注意的是完成端口使用在需要管理的连接量巨大,并且每个连接上收发的数据包比较小的情况下最为合适。因此,我们对完成端口的优化首先应该放在海量连接的相关管理上。为此,我们引入“池”的概念。

在完成端口的设计中。“池”几乎是必须采用的原则[6]。 “池”包含了多个方面,主要有线程池,内存池,连接池等等。我们知道,在大型在线系统中,数据空间的频繁创建和释放时相当占用系统资源的。为此,我们在数据空间的管理上引入内存池。根据完成端口的原理,在每次异步发送或者异步接收操作时,我们都要投递单IO操作数据。而单IO操作数据空间的创建和释放,可以有以下几种方式:

1) 每次执行异步发送和异步接收操作时都声明一个新的单IO操作数据空间,在完成端口处理完后在工作线程中销毁;

2) 只有在每出现一个新连接时,我们才随新连接建立一个新的单IO操作数据空间,将它与新的客户端socket绑定在一起,只有当客户端socket关闭时菜将它与客户端对象一起销毁;

3) 建立一定量的单IO操作数据空间,并将其统一放入一个空闲队列,不管何时需要单IO操作数据空间,都首先从空闲队列中取;如果此时空闲队列中元素为空,则新建立一个单IO操作数据空间。用完后再将其放回空闲队列。

从执行效率以及系统整体系能来考虑,采用方法3)来管理这些空闲单IO操作数据空间最为合适。

我们在使用传统的accept函数接收客户端的一个连接后,这个函数会返回一个创建成功的客户端socket。这就是说,每次连接请求到来后,系统才会去创建新的客户端socket。可以想象当有大量客户端连接到来时,服务器就必须逐一的为他们创建客户端socket,这样势必会占用比较多的系统资源。幸运的是,Windows给我们提供了一个acceptEx函数,它允许我们在接受连接之前就先创建好客户端socket,并在接受连接的时候把它和客户端关联在一起。我们就可以事先创建适量的socket,并作为链表来管理。当有客户端连接请求时,首先从链表中取个空闲的socket使之与客户端相关联,当客户端关闭的时候再把socket插入到链表末尾。这样一来,便可以对客户端连接进行高效的管理了。

4 结束语

在为安徽某公司设计的造气炉控制系统服务器的通信模块中,采用了I/O完成端口技术,经过压力测试,实现了6500人同时在线的项目需求。

此外,IOCP架构也有以下特点[7]:

1) 模型的普遍性。该模型还支持串行和无线的终端设备。此外,它也可以用于设计相似设备的平台软件。

2) 技术的通用性。基于IOCP机制的windows平台可以被基于Linux平台的EPOLL机制取代, ADO和ODBC技术同样可以被JDO和JDBC技术取代。

3) 平台的独立性。无论是EPOLL机制,JDO或者JDBC技术还是Web服务技术,都是独立于平台的技术,因此这种模型也是独立于平台的。

与其它套接字I/O模型相比,完成端口在管理海量并发用户连接请求方面具有巨大优势,这种优势随着系统CPU数量的增加而愈发明显。因此,对于开发大量客户端连接的网络应用服务器来说,基于完成端口技术的服务器设计是一个很好的解决方案。

参考文献:

[1] 盛利,刘旭.用完成端口管理Windows Socket 应用技术[J].现代计算机,2001(7):39-43.

[2] 史美林,向勇,杨光信.计算机支持的协同工作理论与应用[M].北京:电子工业出版社,2000:213.

[3] Jim Ohlund,Anthony Jones,and James Ohlund,Network Programming for Microsoft Windows[M].Microsoft Press, Feb 13,2002:172.

[4] Gyu-baek Kim, An Effective Processing Server for Various Database Operations of Large-scale On-line Games[C]. IaSTED International Conference on Information and Knowledge Sharing, Arizona, U.S.A, 2003,1:188-192

[5] Leland W E. On the self-similar nature of Ethernet traffic[C].IEEE/ACM Transactions on Networking,1994,2:1-15.

[6] 吴永明,何迪.基于完成端口的服务器底层通信模块设计[J].信息技术,2007(3):115-118.

[7] Zhaoge Qi, Wei Shi, and Zhaohui Wu. Software Architecture Design on Large-scale Network Traffic Signal Controllers System[C]. Beijing:Proceedings of the 11th International IEEE Conference on Intelligent Transportation Systems,2008.