DELPHI编写服务程序总结(在系统服务和桌面程序之间共享内存,在服务中使用COM组件)

DELPHI编写服务程序总结程序员

1、服务程序和桌面程序的区别数组

Windows 2000/XP/2003等支持一种叫作“系统服务程序”的进程,系统服务和桌面程序的区别是:
系统服务不用登录系统便可运行;系统服务是运行在System Idle Process/System/smss/winlogon/services下的,而桌面程序是运行在Explorer下的;系统服务拥有更高的权限,系统服务拥有Sytem的权限,而桌面程序只有Administrator权限;在Delphi中系统服务是对桌面程序进行了再一次的封装,既系统服务继承于桌面程序。于是拥有桌面程序所拥有的特性;系统服务对桌面程序的DoHandleException作了改进,会自动把异常信息写到NT服务日志中;普通应用程序启动只有一个线程,而服务启动至少含有三个线程。(服务含有三个线程:TServiceStartThread服务启动线程;TServiceThread服务运行线程;Application主线程,负责消息循环);
摘录代码:
procedure TServiceApplication.Run;
begin
.
.
.
StartThread := TServiceStartThread.Create(ServiceStartTable);
try
while not Forms.Application.Terminated do
Forms.Application.HandleMessage;
Forms.Application.Terminate;
if StartThread.ReturnValue <> 0 then
FEventLogger.LogMessage(SysErrorMessage(StartThread.ReturnValue));
finally
StartThread.Free;
end;
.
.
.
end;安全

procedure TService.DoStart;
begin
try
Status := csStartPending;
try
FServiceThread := TServiceThread.Create(Self);
FServiceThread.Resume;
FServiceThread.WaitFor;
FreeAndNil(FServiceThread);
finally
Status := csStopped;
end;
except
on E: Exception do
LogMessage(Format(SServiceFailed,[SExecute, E.Message]));
end;
end;
在系统服务中也可使用TTimer这些须要消息的定时器,由于系统服务在后台使用TApplication在分发消息;服务器

2、如何编写一个系统服务数据结构

打开Delphi编辑器,选择菜单中的File|New|Other...,在New Item中选择Service Application项,Delphi便自动为你创建一个基于TServiceApplication的新工程,TserviceApplication是一个封装NT服务程序的类,它包含一个TService1对象以及服务程序的装卸、注册、取消方法。
TService属性介绍:
AllowPause:是否容许暂停;
AllowStop:是否容许中止;
Dependencies:启动服务时所依赖的服务,若是依赖服务不存在则不能启动服务,并且启动本服务的时候会自动启动依赖服务;
DisplayName:服务显示名称;
ErrorSeverity:错误严重程度;
Interactive:是否容许和桌面交互;
LoadGroup:加载组;
Name:服务名称;
Password:服务密码;
ServiceStartName:服务启动名称;
ServiceType:服务类型;
StartType:启动类型;
事件介绍:
AfterInstall:安装服务以后调用的方法;
AfterUninstall:服务卸载以后调用的方法;
BeforeInstall:服务安装以前调用的方法;
BeforeUninstall:服务卸载以前调用的方法;
OnContinue:服务暂停继续调用的方法;
OnExecute:执行服务开始调用的方法;
OnPause:暂停服务调用的方法;
OnShutDown:关闭时调用的方法;
OnStart:启动服务调用的方法;
OnStop:中止服务调用的方法;多线程

3、编写一个两栖服务架构

采用下面的方法,能够实现一个两栖系统服务(既系统服务和桌面程序的两种模式)
工程代码:
program FleetReportSvr;并发

uses
SvcMgr,
Forms,
SysUtils,
Windows,
SvrMain in 'SvrMain.pas' {FleetReportService: TService},
AppMain in 'AppMain.pas' {FmFleetReport};app

{$R *.RES}编辑器

const
CSMutexName = 'Global\Services_Application_Mutex';
var
OneInstanceMutex: THandle;
SecMem: SECURITY_ATTRIBUTES;
aSD: SECURITY_DESCRIPTOR;
begin
InitializeSecurityDescriptor(@aSD, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(@aSD, True, nil, False);
SecMem.nLength := SizeOf(SECURITY_ATTRIBUTES);
SecMem.lpSecurityDescriptor := @aSD;
SecMem.bInheritHandle := False;
OneInstanceMutex := CreateMutex(@SecMem, False, CSMutexName);
if (GetLastError = ERROR_ALREADY_EXISTS)then
begin
DlgError('Error, Program or service already running!');
Exit;
end;
if FindCmdLineSwitch('svc', True) or
FindCmdLineSwitch('install', True) or
FindCmdLineSwitch('uninstall', True) then
begin
SvcMgr.Application.Initialize;
SvcMgr.Application.CreateForm(TSvSvrMain, SvSvrMain);
SvcMgr.Application.Run;
end
else
begin
Forms.Application.Initialize;
Forms.Application.CreateForm(TFmFmMain, FmMain);
Forms.Application.Run;
end;
end.
而后在SvrMain注册服务:
unit SvrMain;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs, MsgCenter;

type
TSvSvrMain = class(TService)
procedure ServiceStart(Sender: TService; var Started: Boolean);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
procedure ServiceBeforeInstall(Sender: TService);
procedure ServiceAfterInstall(Sender: TService);
private
{ Private declarations }
public
function GetServiceController: TServiceController; override;
{ Public declarations }
end;

var
SvSvrMain: TSvSvrMain;

implementation

const
CSRegServiceURL = 'SYSTEM\CurrentControlSet\Services\';
CSRegDescription = 'Description';
CSRegImagePath = 'ImagePath';
CSServiceDescription = 'Services Sample.';

{$R *.DFM}

procedure ServiceController(CtrlCode: DWord); stdcall;
begin
SvSvrMain.Controller(CtrlCode);
end;

function TSvSvrMain.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;

procedure TSvSvrMain.ServiceStart(Sender: TService;
var Started: Boolean);
begin
Started := dmPublic.Start;
end;

procedure TSvSvrMain.ServiceStop(Sender: TService;
var Stopped: Boolean);
begin
Stopped := dmPublic.Stop;
end;

procedure TSvSvrMain.ServiceBeforeInstall(Sender: TService);
begin
RegValueDelete(HKEY_LOCAL_MACHINE, CSRegServiceURL + Name, CSRegDescription);
end;

procedure TSvSvrMain.ServiceAfterInstall(Sender: TService);
begin
RegWriteString(HKEY_LOCAL_MACHINE, CSRegServiceURL + Name, CSRegDescription,
CSServiceDescription);
RegWriteString(HKEY_LOCAL_MACHINE, CSRegServiceURL + Name, CSRegImagePath,
ParamStr(0) + ' -svc');
end;

end.
这样,双击程序,则以普通程序方式运行,若用服务管理器来运行,则做为服务运行。
例如公共模块:
dmPublic,提供Start,Stop方法。

在主窗体中,调用dmPublic.Start,dmPublic.Stop方法。
一样在Service中,调用dmPublic.Start,dmPublic.Stop方法。

1、如何限制系统服务和桌面程序只运行一个

如何限制系统服务和桌面程序只运行一个

在工程加入下列代码能够设置系统服务和桌面程序只运行一个。
program FleetReportSvr;

uses
SvcMgr,
Forms,
SysUtils,
Windows,
SvrMain in 'SvrMain.pas' {FleetReportService: TService},
AppMain in 'AppMain.pas' {FmFleetReport};

{$R *.RES}

const
CSMutexName = 'Global\Services_Application_Mutex';
var
OneInstanceMutex: THandle;
SecMem: SECURITY_ATTRIBUTES;
aSD: SECURITY_DESCRIPTOR;
begin
InitializeSecurityDescriptor(@aSD, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(@aSD, True, nil, False);
SecMem.nLength := SizeOf(SECURITY_ATTRIBUTES);
SecMem.lpSecurityDescriptor := @aSD;
SecMem.bInheritHandle := False;
OneInstanceMutex := CreateMutex(@SecMem, False, CSMutexName);
if (GetLastError = ERROR_ALREADY_EXISTS)then
begin
DlgError('Error, Program or service already running!');
Exit;
end;
if FindCmdLineSwitch('svc', True) or
FindCmdLineSwitch('install', True) or
FindCmdLineSwitch('uninstall', True) then
begin
SvcMgr.Application.Initialize;
SvcMgr.Application.CreateForm(TSvSvrMain, SvSvrMain);
SvcMgr.Application.Run;
end
else
begin
Forms.Application.Initialize;
Forms.Application.CreateForm(TFmFmMain, FmMain);
Forms.Application.Run;
end;
end.

2、在系统服务和桌面程序之间共享内存

用于建立内核对象的函数几乎都有一个指向SECURITY_ATTRIBUTES结构的指针做为其参数,在使用CreateFileMapping函数的时候,一般只是为该参数传递NULL,这样就能够建立带有默认安全性的内核对象。
默认安全性意味着对象的管理小组的任何成员和对象的建立者都拥有对该对象的所有访问权,而其余全部人均无权访问该对象。能够指定一个ECURITY_ATTRIBUTES结构,对它进行初始化,并为该参数传递该结构的地址。
它包含的与安全性有关的成员实际上只有一个,即lpSecurityDescriptor。当你想要得到对相应的一个内核对象的访问权(而不是建立一个新对象)时,必须设定要对该对象执行什么操做。若是想要访问一个现有的文件映射内核对象,以便读取它的数据,那么调用OpenfileMapping函数:经过将FILE_MAP_READ做为第一个参数传递给OpenFileMapping,指明打算在得到对该文件映象的访问权后读取该文件, 该函数在返回一个有效的句柄值以前,首先
执行一次安全检查。若是(已登陆用户)被容许访问现有的文件映射内核对象,就返回一个有效的句柄。可是,若是被拒绝访问该对象,将返回NULL。

系统服务端核心代码:

constructor TPublicVars.Create(ANew: Boolean);
var
SecMem: SECURITY_ATTRIBUTES;
aSD: SECURITY_DESCRIPTOR;
begin
inherited Create;
{ 建立一个任何用户均可以访问的内核对象访问权 }
InitializeSecurityDescriptor(@aSD, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(@aSD, True, nil, False);
SecMem.nLength := SizeOf(SECURITY_ATTRIBUTES);
SecMem.lpSecurityDescriptor := @aSD;
SecMem.bInheritHandle := False;
FMapFile := CreateFileMapping($FFFFFFFF, @SecMem, PAGE_READWRITE, 0, CSharedMemSize, CSharedMemName);
FMapFile := OpenFileMapping(File_Map_All_Access, False, CSharedMemName);
if (FMapFile = 0) then
begin
raise Exception.Create(SysErrorMessage(GetLastError));
OutputDebugString(PChar(SysErrorMessage(GetLastError)));
end
else
begin // 成功
FShareMem := MapViewOfFile(FMapFile, File_Map_All_Access, 0, 0, CSharedMemSize);
OutputDebugString(PChar(SysErrorMessage(GetLastError) + ',Handle=' + IntToStr(Handle)));
end;
end;

destructor TPublicVars.Destroy;
begin
UnmapViewOfFile(FShareMem);
CloseHandle(FMapFile);
inherited;
end;

桌面程序核心源代码:

constructor TPublicVars.Create(ANew: Boolean);
var
SecMem: SECURITY_ATTRIBUTES;
aSD: SECURITY_DESCRIPTOR;
begin
inherited Create;
{ 建立一个任何用户均可以访问的内核对象访问权 }
InitializeSecurityDescriptor(@aSD, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(@aSD, True, nil, False);
SecMem.nLength := SizeOf(SECURITY_ATTRIBUTES);
SecMem.lpSecurityDescriptor := @aSD;
SecMem.bInheritHandle := False;
FMapFile := CreateFileMapping($FFFFFFFF, @SecMem, PAGE_READWRITE, 0, CSharedMemSize, CSharedMemName);
FMapFile := OpenFileMapping(File_Map_All_Access, False, CSharedMemName);
if (FMapFile = 0) then
begin
raise Exception.Create(SysErrorMessage(GetLastError));
OutputDebugString(PChar(SysErrorMessage(GetLastError)));
end
else
begin // 成功
FShareMem := MapViewOfFile(FMapFile, File_Map_All_Access, 0, 0, CSharedMemSize);
OutputDebugString(PChar(SysErrorMessage(GetLastError) + ',Handle=' + IntToStr(Handle)));
end;
end;

destructor TPublicVars.Destroy;
begin
UnmapViewOfFile(FShareMem);
CloseHandle(FMapFile);
inherited;
end;

详细源代码见报表服务和报表COM中的关于共享内存的源代码。须要注意建立共享内存须要放在:ServiceStart中初始化,不能放在initialization,不然还会出现权限不足的信息,由于initialization是在应用程序初始化以前执行的代码。

3、在服务中使用COM组件

在服务中调用COM组件不能像在桌面程序中直接建立,在每次建立以前先调用CoInitialize(nil),释放的时候调用CoUninitialize。例如:调用ADO组件
var
Qry: TADOQuery;
begin
CoInitialize(nil);
Qry := TADOQuery.Create(nil);
try
...
finally
Qry.Free;
CoUninitialize;

1、提升DELPHI程序的稳定性
软件质量是一个产品的生命线,也是关乎软件开发者的幸福关键所在,天天有不少程序员都在由于软件质量而通宵达旦的加班,常常遇到的状况是刚发布的程序不停的发布补丁包。软件质量就像一个噩梦同样,不停的在后面追赶着程序员,让他们疲于奔命,甚至于在程序员中流传着一句话:“生命不息,BUG不止”。
今天咱们要探究的不是哪些能够重现的BUG,咱们把哪些能够重现的BUG不定义为BUG,只有哪些不可重现的BUG,会让你茶饭不思、坐立不安。我曾在一家公司开发服务器软件,结果由于程序不稳定,并且都是一些不可重现的错误,致使咱们须要不停的派人盯着服务器运行。不稳定就像一个恶鬼同样终日萦绕在咱们心头,领导的不停催促,客户的不停投诉,让咱们项目组个个疲于奔命,叫苦不迭。我在查了无数个不可重现的BUG发现,主要是因为如下八种缘由引发的:
1. 变量没有初始化;
2. 函数返回值没有初始化;
3. 编译优化致使的错误;
4. 函数递归;
5. 消息重入;
6. 野指针;
7. 内存泄漏;
8. 并发;
你会发现都是一些细小问题,所以程序员在平常开发中必定要养成好的习惯。
2、变量没有初始化
DELPHI默认初始化的变量是:全局变量、类成员,其它在函数体的变量都不会初始化,所以一些用于判断或者循环的变量必定要记得初始化,另外枚举类型、申请的内存都须要初始化,PCHAR必定要在末尾加#0。例如:下面的返回结果有可能会出现乱码。
function TempPath: string;
begin
SetLength(Result, GetTempPath(0, PChar(Result)));
GetTempPath(Length(Result), PChar(Result));
Result := PChar(Result);
end;
正确的写法应该
function TempPath: string;
begin
SetLength(Result, GetTempPath(0, PChar(Result)));
ZeroMemory(PChar(Result), Length(Result));
GetTempPath(Length(Result), PChar(Result));
Result := PChar(Result);
end;
这个程序就是典型的在申请内存的时候,没有对PCHAR进行初始化,所以末尾有多是随机值,可是经过ZeroMemory就把末尾赋#0。
3、函数返回值没有初始化
在DELPHI中退出函数是使用Exit函数的,有不少函数在退出的时候,没有对函数返回值初始化,那么函数的返回值返回就是一个随机值,对程序运行形成不可重现错误。例如:下面程序的执行结果会让你大吃一惊。
procedure NotInitResult;
var
i: Integer;
function GetString(AValue: Integer): string;
begin
if AValue = 0 then
Result := 'True';
end;
begin
for i := -1 to 1 do
begin
ShowMessage(GetString(i));
end;
end;
你看到的运行结果是:‘’、‘True’、‘True’,正确的写法应该是:
procedure NotInitResult;
var
i: Integer;
function GetString(AValue: Integer): string;
begin
if AValue = 0 then
Result := 'True'
else
Result := ‘’;
end;
begin
for i := -1 to 1 do
begin
ShowMessage(GetString(i));
end;
end;
所以针对if或者Case语句必定要赋初始值,上面的函数的写法也能够写为:
function GetString(AValue: Integer): string;
begin
Result := ‘’;
if AValue = 0 then
Result := 'True';
end;
function GetString(AValue: Integer): string;
begin
case AValue of
0: Result := ‘True’;
else Result := ‘’;
end;
end;
4、编译优化致使的错误
如今的编译器在编译代码的时候会优化掉一些能够不执行的代码,例如:布尔类型优化是最多见的一种,下面的例子能很好的说明这个问题。
procedure TForm1.btn1Click(Sender: TObject);
var
s: string;
begin
if GetTrue or GetValue1(s) then
ShowMessage('Hello ' + s);
end;
procedure TForm1.btn2Click(Sender: TObject);
var
s: string;
begin
if GetTrue or GetValue2(s) then
ShowMessage('Hello ' + s);
end;
function TForm1.GetTrue: Boolean;
begin
Result := True;
end;
function TForm1.GetValue1(var s: string): Variant;
begin
Result := True;
s := 'World';
end;
function TForm1.GetValue2(var s: string): Boolean;
begin
Result := True;
s := 'World';
end;
你会发现单击btn1时出现的结果是:“Hello Word”,可是单击btn2的时候是:“Hello”,这个就是由于单击btn2的时候因为GetTrue返回的是真,因此第二句不执行,可是btn1因为还要进行Variant到Boolean类型的转换,所以确定会执行。

5、函数递归
若是存在递归函数,就须要特别注意,是否会正常退出函数执行,若是一直执行下去,会把程序调用堆栈所有吃完,致使程序异常终止,以下例:只要一点btn1,程序就会无声无息死掉,并且没有LOG,这类代码在以服务方式运行须要特别注意,由于你的服务是无人值守的状况下运行的,若是出现这种状况,你的服务会直接退出,并且没有任何提示,对于查找问题无从下手。
procedure TForm1.btn1Click(Sender: TObject);
procedure Recursive;
begin
Recursive;
end;
begin
Recursive;
end;
6、消息重入
消息重入的概念是:有一个消息执行过程尚未执行,相同的一个消息又进入相同的函数处理。消息重入很大缘由是在不少软件中调用Application.ProcessMessage来更新界面,若是是一个操做须要很长的时间,能够改成线程来执行,或者不调用Application.ProcessMessage函数。例如:下面的函数就很容易致使消息重入。
procedure TForm1.btn1Click(Sender: TObject);
var
i: Integer;
begin
for i := 0 to 10000000 do
begin
Application.ProcessMessages;
end;
end;
若是必需要用Application.ProcessMessage来更新界面,你应该确保在函数执行过程当中,这个消息不会第二次投递,如这个例子你能够经过把btn1的状态禁用来防止消息重入,正确的写法是:
procedure TForm1.btn1Click(Sender: TObject);
var
i: Integer;
begin
btn1.Enabled := False;
for i := 0 to 10000000 do
begin
Application.ProcessMessages;
end;
btn1.Enabled := True;
end;
另外在发送消息的时候,也须要特别注意SendMessage和PostMessage的区别,SendMessage是发送等待消息处理完成再返回,PostMessage是投递到消息缓冲池排队,当即返回(这时消息可能没有处理),消息须要等到轮到它的时候再处理。
7、野指针
野指针在编译时候是没法检测的,只有在运行时候才会出现,出现野指针最多见的错误就是Access violation错误(简称AV错误),出现这种错误是你指向的物理内存不可用。出现野指针主要是因为如下四种引发:一、指针变量没有初始化;二、指针被Free或Dispose以后再次使用;指针操做超越了变量的范围;四、取string的地址,没有判断string是否已经分配内存。
代码在判断指针是不是空指针是经过判断指针的值是否介于0x00000000和0x0000FFFF之间,若是在这之间用if语句是能够判断,若是不介于这之间,则认为指针是有效的。所以指针在申请以后或者释放以后,指向的地址是随机值,所以用if语句是没法判断。另外在DELPHI中,你把指针置为nil,翻译成汇编代码就是异或一下,能够打开CPU窗口查看,如:
Fm := nil;生成的汇编是:xor eax eax,即把指针置为0x00000000。

8、内存泄漏
内存泄漏指的是软件在运行过程当中对于申请的内存空间没有释放,致使内存占用愈来愈大,最后程序异常崩溃,并且此时也不会留下任何痕迹,没有任何系统日志可查。内存泄漏也分为两种,一种是程序一块儿动,而后占用了内存,不会随着程序运行增加;一种是随着程序运行不停增加的;若是是第一种能够放过,对二种必定要仔细检查,检查工具推荐用FastMM,而且把DELPHI的项目属性Compiler->Use Debug DCUs和Linker->Map file->Detailed选中,这样FastMM就能够把申请内存的调用堆栈和MAP地址打出来,很是利于查找内存泄漏。查找内存泄漏通常能够从如下几个方面考虑:
1. 使用Dispose释放内存的时候要加上定义信息,若是不加定义信息,对于一些指针或者string释放不了,对于结构体内部有指针的应先释放内部指针;
2. 使用FreeMem或FreeMemory释放内存的时候,能够不加大小信息,这是由于DELPHI内存管理器内部知道指针大小信息;
3. Override函数必定要inherited来释放父类申请的内存;
4. 申请的内存要确保释放,能够用Try … finally … end来确保内存的释放,可是应杜绝这种代码风格try …申请内存…finally …释放内存… end;
5. 系统内核对象要确保关闭;
6. 申请的指针若是在某些状况下分配空间,要记得初始化为nil,释放的时候要判断是否为空,由于释放空指针也会致使内存泄漏;
7. 另外PostMessage也有可能致使内存泄漏,这种状况是经过PostMessage发送结构体,释放内存放在消息处理函数中,这时若是频繁的调用PostMessage,消息处理循环忙不过来,就会丢掉一些消息,形成内存泄漏,默认的Windows消息队列长度是4000,若是说消息队列有4000个,你这时再用PostMessage投递消息,就会被丢掉,形成申请的结构体没法释放,形成内存泄漏;
9、并发
若是程序涉及多线程,并且线程之间有协做关系,若是这时线程挂死了,就要查线程同步,通常这类问题比较难查,并且须要对代码执行流程很是了解,属于比较难以处理的一类问题。能够借助一些三方工具,好比“procexp.exe”就是一个很是优秀的工具,用他能够看到每一个线程的状态,若是一个线程停在哪不动,你就能够经过MAP地址和调用堆栈找到问题点。如Excel的线程状态以下图:

 

10、一些有效的建议
针对以上的这些问题,咱们在平常的开发中,应该注意哪些问题呢,下面是我给出的一些建议:
1.探索需求,需求理解越深写出代码的质量、架构就越轻巧,可读性和维护性大大提升;
2. 测试驱动开发;
3. 良好的代码风格,良好的编码习惯对于软件质量有很是大的提升;
4. 变量(指针、数组)被建立以后应及时把他们初始化;
5. 检查变量的初始值、缺省值错误,或者精度不够;
6. 类型转换,必定要善用as和is;
7. 检查变量上溢或下溢,数组越界;
8. 检查I/O错误,I/O不是总返回真的;
9. 数据结构够用就好,不要设计面面俱到、很是灵活的数据结构;
10. 差劲的代码,不要想着改改又可用了,应当从新编写,由于极有可能致使按下葫芦浮起瓢;
11. 对程序编译出现的每个告警,都认真对待,要编写无警告的代码;
12. 对于不须要修改的参数带上const,不但能够提升效率,并且能够加强安全;

这个例子是我原来写的一个完成端口演示程序,没有通过严格的稳定性校验,只是作为如何编写的一个样本,仅供你们参考,下面是完成端口的简单介绍:

“完成端口”模型是迄今为止最为复杂的一种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个客服端,服务器性能还很好,因为我写的客服端占用资源比较多,最后直接重启了,具体见代码。演示程序主要的瓶颈在于发消息的这一块,在实际应用中应去掉。

代码下载地址:http://download.csdn.net/source/1737865

相关文章
相关标签/搜索