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

相關文章
相關標籤/搜索