DELPHI編寫服務程式總結

來源:互聯網
上載者:User

標籤:

DELPHI編寫服務程式總結

一、服務程式和傳統型程式的區別

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在分發訊息;

二、如何編寫一個系統服務

開啟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:停止服務調用的方法;

三、編寫一個兩棲服務

採用下面的方法,可以實現一個兩棲系統服務(既系統服務和傳統型程式的兩種模式)
工程代碼:
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.
然後在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方法。

一、如何限制系統服務和傳統型程式只運行一個

如何限制系統服務和傳統型程式只運行一個

在工程加入下列代碼可以設定系統服務和傳統型程式只運行一個。
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.

二、在系統服務和傳統型程式之間共用記憶體

用於建立核心對象的函數幾乎都有一個指向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是在應用程式初始化之前執行的代碼。

三、在服務中使用COM組件

在服務中調用COM組件不能像在傳統型程式中直接建立,在每次建立之前先調用CoInitialize(nil),釋放的時候調用CoUninitialize。例如:調用ADO組件
var
Qry: TADOQuery;
begin
CoInitialize(nil);
Qry := TADOQuery.Create(nil);
try
...
finally
Qry.Free;
CoUninitialize;

一、提高DELPHI程式的穩定性
軟體品質是一個產品的生命線,也是關乎軟體開發人員的幸福關鍵所在,每天有很多程式員都在因為軟體品質而通宵達旦的加班,經常遇到的情況是剛發布的程式不停的發布補丁包。軟體品質就像一個噩夢一樣,不停的在後面追趕著程式員,讓他們疲於奔命,甚至於在程式員中流傳著一句話:“生命不息,BUG不止”。
今天我們要探究的不是哪些可以重現的BUG,我們把哪些可以重現的BUG不定義為BUG,只有哪些不可重現的BUG,會讓你茶飯不思、坐立不安。我曾在一家公司程式開發伺服器軟體,結果因為程式不穩定,而且都是一些不可重現的錯誤,導致我們需要不停的派人盯著伺服器運行。不穩定就像一個惡鬼一樣終日縈繞在我們心頭,領導的不停催促,客戶的不停投訴,讓我們項目組個個疲於奔命,叫苦連天。我在查了無數個不可重現的BUG發現,主要是由於以下八種原因引起的:
1. 變數沒有初始化;
2. 函數傳回值沒有初始化;
3. 編譯最佳化導致的錯誤;
4. 函數遞迴;
5. 訊息重入;
6. 野指標;
7. 記憶體流失;
8. 並發;
你會發現都是一些細小問題,因此程式員在日常開發中一定要養成好的習慣。
二、變數沒有初始化
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。
三、函數傳回值沒有初始化
在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;
四、編譯最佳化導致的錯誤
現在的編譯器在編譯代碼的時候會最佳化掉一些可以不執行的代碼,例如:布爾類型最佳化是最常見的一種,下面的例子能很好的說明這個問題。
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類型的轉換,因此肯定會執行。

五、函數遞迴
如果存在遞迴函式,就需要特別注意,是否會正常退出函數執行,如果一直執行下去,會把程式呼叫堆疊全部吃完,導致程式異常終止,如下例:只要一點btn1,程式就會無聲無息死掉,而且沒有LOG,這類代碼在以服務方式運行需要特別注意,因為你的服務是無人值守的情況下啟動並執行,如果出現這種情況,你的服務會直接退出,而且沒有任何提示,對於尋找問題無從下手。
procedure TForm1.btn1Click(Sender: TObject);
procedure Recursive;
begin
Recursive;
end;
begin
Recursive;
end;
六、訊息重入
訊息重入的概念是:有一個訊息執行過程還沒有執行,相同的一個訊息又進入相同的函數處理。訊息重入很大原因是在很多軟體中調用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是投遞到訊息緩衝池排隊,立即返回(這時訊息可能沒有處理),訊息需要等到輪到它的時候再處理。
七、野指標
野指標在編譯時間候是無法檢測的,只有在運行時候才會出現,出現野指標最常見的錯誤就是Access violation錯誤(簡稱AV錯誤),出現這種錯誤是你指向的實體記憶體不可用。出現野指標主要是由於以下四種引起:1、指標變數沒有初始化;2、指標被Free或Dispose之後再次使用;指標操作超越了變數的範圍;4、取string的地址,沒有判斷string是否已經分配記憶體。
代碼在判斷指標是否是null 指標是通過判斷指標的值是否介於0x00000000和0x0000FFFF之間,如果在這之間用if語句是可以判斷,如果不介於這之間,則認為指標是有效。因此指標在申請之後或者釋放之後,指向的地址是隨機值,因此用if語句是無法判斷。另外在DELPHI中,你把指標置為nil,翻譯成彙編代碼就是異或一下,可以開啟CPU視窗查看,如:
Fm := nil;產生的彙編是:xor eax eax,即把指標置為0x00000000。

八、記憶體流失
記憶體流失指的是軟體在運行過程中對於申請的記憶體空間沒有釋放,導致記憶體佔用越來越大,最後程式異常崩潰,而且此時也不會留下任何痕迹,沒有任何系統日誌可查。記憶體流失也分為兩種,一種是程式一起動,然後佔用了記憶體,不會隨著程式運行增長;一種是隨著程式運行不停增長的;如果是第一種可以放過,對二種一定要仔細檢查,檢查工具推薦用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,釋放的時候要判斷是否為空白,因為釋放null 指標也會導致記憶體流失;
7. 另外PostMessage也有可能導致記憶體流失,這種情況是通過PostMessage發送結構體,釋放記憶體放在訊息處理函數中,這時如果頻繁的調用PostMessage,訊息處理迴圈忙不過來,就會丟掉一些訊息,造成記憶體流失,預設的Windows訊息佇列長度是4000,如果說訊息佇列有4000個,你這時再用PostMessage投遞訊息,就會被丟掉,造成申請的結構體無法釋放,造成記憶體流失;
九、並發
如果程式涉及多線程,而且線程之間有協作關係,如果這時線程掛死了,就要查線程同步,一般這類問題比較難查,而且需要對代碼執行流程非常瞭解,屬於比較難以處理的一類問題。可以藉助一些三方工具,比如“procexp.exe”就是一個非常優秀的工具,用他可以看到每個線程的狀態,如果一個線程停在哪不動,你就可以通過MAP地址和呼叫堆疊找到問題點。如Excel的線程狀態如:

 

十、一些有效建議
針對以上的這些問題,我們在日常的開發中,應該注意哪些問題呢,下面是我給出的一些建議:
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

DELPHI編寫服務程式總結

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.