架構設計
- DCOM (Distributed COM):讓客戶端透過 RPC 遠端呼叫伺服器端物件方法,無需撰寫 Socket 程式碼。
- 介面 IChatManager:定義
SpeakTo、ReadFrom、ConnectRoom、DisconnectRoom、ReadReady、TestClearBufferTag。
- 伺服器端物件:
TChatManager (實作 IChatManager)、TChatRoomManager (管理多聊天室)、TChatRoom (單一聊天室緩衝區與同步鎖)。
- 客戶端:透過型別庫匯入介面,定時輪詢
ReadReady → ReadFrom 顯示訊息。
核心介面定義 (IDL/Type Library)
[
uuid(E7CD7F0D-447F-497A-8C7B-1D80E748B67F),
dual, oleautomation
]
interface IChatManager : IDispatch {
[id(1)] HRESULT SpeakTo([in] BSTR content, [in] long destid);
[id(2)] HRESULT ReadFrom([in] long sourceid, [out, retval] IStrings** result);
[id(3)] HRESULT ReadReady([in] long id, [out, retval] Byte* result);
[id(4)] HRESULT ConnectRoom([in] BSTR UserName, [in] long RoomID);
[id(5)] HRESULT DisconnectRoom([in] BSTR UserName, [in] long RoomID);
[id(6)] HRESULT TestClearBufferTag([in] long RoomID, [out, retval] long* result);
};
伺服器端關鍵實作重點
TChatRoom - 緩衝區與同步
type
TChatRoom = class
private
FBuffer: array[1..20] of string; // 環狀緩衝區
FBufferLength, FRoomID, FClearBufferTag: Integer;
FLocked: Boolean; // 簡易自旋鎖
FRoomName: string;
public
constructor Create(const RoomName: string; RoomID: Integer);
procedure OneSpeak(const Content: string);
function OneRead: IStrings; // 回傳介面供跨進程封送
procedure LoginRoom(const UserName: string);
procedure LeaveRoom(const UserName: string);
property Locked: Boolean read FLocked;
property CanRead: Boolean read GetCanRead;
property ClearBufferTag: Integer read FClearBufferTag;
end;
procedure TChatRoom.OneSpeak(const Content: string);
begin
// 簡易自旋等待鎖 (實務應用 Critical Section 或 Mutex)
while FLocked do Sleep(1);
FLocked := True;
try
Inc(FBufferLength);
if FBufferLength > 20 then ClearBuffer;
FBuffer[FBufferLength] := Content;
finally
FLocked := False;
end;
end;
TChatManager - 介面方法轉發
function TChatManager.SpeakTo(const Content: WideString; DestID: Integer): HResult;
var
Room: TChatRoom;
begin
Room := ChatRoomManager.FindRoomByID(DestID);
if Assigned(Room) then
begin
Room.OneSpeak(Content);
Result := S_OK;
end
else Result := E_INVALIDARG;
end;
function TChatManager.ReadFrom(SourceID: Integer): IStrings;
var
Room: TChatRoom;
StrList: TStringList;
begin
Room := ChatRoomManager.FindRoomByID(SourceID);
if Assigned(Room) then
begin
while Room.Locked do Sleep(1);
StrList := TStringList.Create;
try
Room.OneRead(StrList); // 填充 StrList
Result := StrList as IStrings; // 自動封送
except
StrList.Free;
raise;
end;
end
else Result := nil;
end;
客戶端輪詢邏輯
procedure TFormClient.TimerPollTimer(Sender: TObject);
var
Strs: IStrings;
i: Integer;
begin
if ChatServer.ReadReady(CurrentRoomID) = 1 then
begin
Strs := ChatServer.ReadFrom(CurrentRoomID);
if Assigned(Strs) then
begin
for i := FLastReadIndex to Strs.Count - 1 do
MemoChat.Lines.Add(Strs[i]);
FLastReadIndex := Strs.Count;
end;
end;
end;
部署與設定步驟
- 伺服器專案建立 COM+ 應用程式 (元件服務),註冊
TChatManager 為 MultiInstance、Apartment 執行緒模型。
- 客戶端機器:複製型別庫 (
.tlb) 並執行 regsvr32 ChatServer.tlb,或在 Delphi 專案 Import Type Library 產生介面單元。
- DCOM 設定 (
dcomcnfg):元件 → 內容 → 身分識別 (互動使用者/特定帳號) → 安全性 → 啟動及啟用權限/存取權限 → 加入 Everyone/網路用戶允許。
- 防火牆開放 RPC 動態連接埠範圍 (預設 49152-65535) 或設定固定端點。
同步與擴展性提醒
- 原始碼使用
while Locked do Sleep(1) 忙碌等待,高併發下極度低效,實務必須使用 TCriticalSection、TMonitor、Named Mutex 或無鎖佇列。
- DCOM 預設每個呼叫建立獨立執行緒 (Apartment 模型),伺服器需妥善管理執行緒池與物件生命週期。
- 現代替代方案:DataSnap (TCP/HTTP/HTTPs)、gRPC、WebSocket (sgcWebSockets)、REST + SignalR,跨平台、防火牆友善、易於維運。