在开发大量Socket并发服务器,完成端口加重叠I/O是迄今为止最好的一种解决方案,下面是简单的介绍:
“完成端口”模型是迄今为止最为复杂的一种I/O模型,特别适合需要同时管理为数众多的套接字,采用这种模型,往往可以达到最佳的系统性能。但是只适合Windows NT和Windows 2000及以上操作系统。因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千套接字的时候,而且希望随着系统内安装的CPU数量增多,应用程序的性能也可以线性提升,才考虑采用“完成端口”模型。
重叠I/O(Overlapped I/O)模型使应用程序达到更佳的系统性能。重叠模型的基本设计原理便是让应用程序使用一个重叠的数据结构,一次投递一个或多个Winsock I/O请求。针对哪些提交的请求,在它们完成之后,应用程序可为它们提供服务。该模型适用于除Windows CE之外的各种Windows平台。
开发完成端口最具有挑战是线程个数和管理内存,创建一个完成端口后,就需要创建一个或多个“工作者线程”,以便在I/O请求投递给完成端口对象后,为完成端口提供服务。但是到底应创建多少个线程,这实际正是完成端口最为复杂的一个方面,一般采用的是为每一个CPU分配一个线程(有的是CPU个数加1,有的是CPU*2的线程个数)。内存分配效率低是因为应用程序在分配内存的时候,系统内核需要不停的Lock/UnLock,而且在多CPU的情况下,会成为整个程序性能的瓶颈,不能随CPU的个数增加而性能提高,一种比较好的做法一个一次分配多块内存。
下面是我写一个的完成端口的演示程序,在我的电脑上测试可以达到链接5100个客服端,服务器性能还很好,由于我写的客服端占用资源比较的,最后直接重启了,具体见代码。演示程序主要的瓶颈在于发消息的这一块,在实际应用中应去掉。
(配置:操作系统 Microsoft Windows XP Professional 操作系统 Service Pack 版本 Service Pack 2;CPU:Intel(R) Pentium(R)4 2.40GHz 2.40GHz;内存:2G;主板:华硕P4P800)。
主要源代码:(Delphi 7编写),下载地址:http://download.csdn.net/source/818039
{*******************************************************}
{ }
{ 高性能服务器,这个是一个演示DEMO }
{ }
{ 联系邮箱:fansheng_hx@163.com }
{ }
{*******************************************************}
unit IOCPSvr;
interface
uses
Windows, Messages, WinSock2, Classes, SysUtils, SyncObjs;
const
{* 每一次发送和接收的数据缓冲池大小 *}
MAX_BUFSIZE = 4096;
{* 关闭客户端通知消息 *}
WM_CLIENTSOCKET = WM_USER + $2000;
type
{* Windows Socket 消息 *}
TCMSocketMessage = packed record
Msg: Cardinal;
Socket: TSocket;
SelectEvent: Word;
SelectError: Word;
Result: Longint;
end;
{* IOCP服务器运行轨迹 *}
TSocketEvent = (seInitIOPort, seUninitIOPort, seInitThread, seUninitThread,
seInitSocket, seUninitSocket, seConnect, seDisconnect, seListen, seAccept, seWrite, seRead);
const
CSSocketEvent: array[TSocketEvent] of string = ('InitIOPort', 'UninitIOPort', 'InitThread', 'UninitThread',
'InitSocket', 'UninitSocket', 'Connect', 'Disconnect', 'Listen', 'Accept', 'Write', 'Read');
type
{* 产生错误类型 *}
TErrorEvent = (eeGeneral, eeSend, eeReceive, eeConnect, eeDisconnect, eeAccept);
{* 完成端口传递的结构体 *}
TIOCPStruct = packed record
Overlapped: OVERLAPPED;
wsaBuffer: TWSABUF;
Event: TSocketEvent; //读或写
Buffer: array [0..MAX_BUFSIZE - 1] of Char;
Assigned: Boolean; //表示已经分配给某个客户端
Active: Boolean; //客服端内部使用,表示是否正在使用
end;
PIOCPStruct = ^TIOCPStruct;
EMemoryBuffer = class(Exception);
ESocketError = class(Exception);
TMemoryManager = class;
TServerSocket = class;
TSymmetricalSocket = class;
TMemoryManager = class
private
{* 管理内存使用 *}
FList: TList;
{* 分配和释放时候使用的锁 *}
FLock: TCriticalSection;
{* 服务器 *}
FServerSocket: TServerSocket;
function GetCount: Integer;
function GetIOCPStruct(AIndex: Integer): PIOCPStruct;
public
constructor Create(AServerSocket: TServerSocket; ACount: Integer); overload;
constructor Create(AServerSocket: TServerSocket); overload;
destructor Destroy; override;
{* 分配内存使用权 *}
function Allocate: PIOCPStruct;
{* 释放内存使用权 *}
procedure Release(AValue: PIOCPStruct);
property Server: TServerSocket read FServerSocket;
property Count: Integer read GetCount;
property Item[AIndex: Integer]: PIOCPStruct read GetIOCPStruct;
end;
{* 客服端链接服务器触发此事件,如果要拒绝链接,把AConnect := False *}
TOnBeforeConnect = procedure(ASymmIP: string; AConnect: Boolean) of object;
{* 链接完成之后触发此事件 *}
TOnAfterConnect = procedure(ASymmetricalSocket: TSymmetricalSocket) of object;
{* 断开连接触发事件 *}
TOnAfterDisconnect = procedure(ASymmetricalSocket: TSymmetricalSocket) of object;
{* 收到数据会触发此事件 *}
TOnDataEvent = procedure(ASymmetricalSocket: TSymmetricalSocket; AData: Pointer;
ACount: Integer) of object;
{* 错误触发事件 *}
TOnErrorEvent = procedure(AError: Integer; AErrorString: string; AInfo: string; var AHandleError: Boolean) of object;
{* 服务器运行LOG *}
TOnLog = procedure (ASocketEvent: TSocketEvent; AInfo: string) of object;
{* 服务器,负责建立完成端口,管理内存和管理客服端,及Socket消息循环 *}
TServerSocket = class
private
{* 内存管理 *}
FMemory: TMemoryManager;
{* 端口 *}
FPort: Integer;
{* 套接字 *}
FSocket: TSocket;
{* 完成端口句柄 *}
FIOCPHandle: THandle;
{* 消息循环句柄 *}
FHandle: THandle;
{* 对等的客服端 *}
FClients: TList;
{* 服务器运行线程 *}
FThreads: TList;
{* 监听线程 *}
FAcceptThread: TThread;
{* 表示是否激活 *}
FActive: Boolean;
{* 锁 *}
FLock: TCriticalSection;
{* 错误触发事件 *}
FOnError: TOnErrorEvent;