Scktsrvr.exe 是 Delphi 中 DataSnap (舊稱 MIDAS) 隨附的標準 Socket 伺服器,原始碼約 900 行,展示了工業級「多執行緒 + COM + 非阻塞 Socket + 自訂協定」的經典架構。本文挑選核心執行緒與資料解析器兩大模組深度解析。
每個 Client 連線對應一條專屬執行緒,採用阻塞模式簡化邏輯(因為每連線一執行緒,阻塞不影響其他連線)。
procedure TSocketDispatcherThread.ClientExecute;
var
Data: IDataBlock;
msg: TMsg;
Obj: ISendDataBlock;
Event: THandle;
WaitTime: DWORD;
begin
CoInitialize(nil); // 單執行緒單元 (STA) 初始化 COM
try
Synchronize(AddClient); // UI 更新需同步到主執行緒
FTransport := CreateServerTransport; // 建立傳輸層 (ITransport)
try
Event := FTransport.GetWaitEvent; // 取得底層 Socket 事件 Handle
PeekMessage(msg, 0, WM_USER, WM_USER, PM_NOREMOVE); // 建立訊息佇列
GetInterface(ISendDataBlock, Obj); // 取得發送介面
FInterpreter := TDataBlockInterpreter.Create(Obj, SSockets); // 建立解析器
try
Obj := nil;
WaitTime := IIF(FTimeout = 0, INFINITE, 60000);
while not Terminated and FTransport.Connected do
begin
// 核心等待:同時等待 Socket 事件 與 Windows 訊息
case MsgWaitForMultipleObjects(1, Event, False, WaitTime, QS_ALLEVENTS) of
WAIT_OBJECT_0: // Socket 有資料可讀
begin
WSAResetEvent(Event);
Data := FTransport.Receive(False, 0); // 讀取資料塊
if Assigned(Data) then
begin
FLastActivity := Now;
FInterpreter.InterpretData(Data); // 委派解析器處理
Data := nil;
FLastActivity := Now;
end;
end;
WAIT_OBJECT_0 + 1: // 有 Windows 訊息 (如 COM 回呼)
while PeekMessage(msg, 0, 0, 0, PM_REMOVE) do DispatchMessage(msg);
WAIT_TIMEOUT:
if (FTimeout > 0) and ((Now - FLastActivity) > FTimeout) then
FTransport.Connected := False; // 閒置逾時斷線
end;
end;
finally FInterpreter.Free; end;
finally FTransport := nil; end;
finally
CoUninitialize;
Synchronize(RemoveClient);
end;
end;
ITransport 隱藏 TCP/HTTP/具名管道細節,TDataBlock 實作 IDataBlock 封裝「長度(4 bytes) + 簽名(4 bytes) + 資料」二進位協定。CoInitialize(nil) 宣告 STA,配合 MsgWaitForMultipleObjects 處理跨單元呼叫。負責依據資料塊 Signature (前 4 bytes) 分派處理邏輯,對應 MIDAS 核心協定指令:
procedure TDataBlockInterpreter.InterpretData(const Data: IDataBlock);
var Action: Integer;
begin
Action := Data.Signature; // 取得指令碼
if (Action and asMask) = asError then DoException(Data);
try
case (Action and asMask) of
asInvoke: DoInvoke(Data); // 遠端方法呼叫
asGetID: DoGetIDsOfNames(Data); // 介面查詢
asCreateObject: DoCreateObject(Data); // 建立遠端物件
asFreeObject: DoFreeObject(Data); // 釋放遠端物件
asGetServers: DoGetServerList(Data); // 取得伺服器清單
asGetAppServers: DoGetAppServerList(Data); // 取得 AppServer 清單 (重點)
else
if not DoCustomAction(Action and asMask, Data) then
raise EInterpreterError.CreateResFmt(@SInvalidAction, [Action and asMask]);
end;
except on E: Exception do
begin
Data.Clear;
WriteVariant(E.Message, Data);
Data.Signature := ResultSig or asError;
FSendDataBlock.Send(Data, False); // 回傳錯誤
end;
end;
end;
procedure TDataBlockInterpreter.DoGetAppServerList(const Data: IDataBlock);
var VList: OleVariant; List: TStringList; i: Integer;
begin
Data.Clear;
List := TStringList.Create;
try
GetMIDASAppServerList(List, FCheckRegValue); // 讀取註冊表/組態取得 AppServer ProgID 清單
if List.Count > 0 then
begin
VList := VarArrayCreate([0, List.Count - 1], varOleStr);
for i := 0 to List.Count - 1 do VList[i] := List[i];
end else VList := NULL;
finally List.Free; end;
WriteVariant(VList, Data); // 將 Variant 序列化進 Data.Stream
Data.Signature := ResultSig or asGetAppServers;
FSendDataBlock.Send(Data, False); // 非同步發送回 Client
end;
// Stream 記憶體佈局
// Offset 0..3 : Signature (Integer) -> 指令碼
// Offset 4..7 : DataSize (Integer) -> 後續資料長度
// Offset 8.. : Payload (Variant 序列化二進位)
function TDataBlock.GetSignature: Integer;
begin
FStream.Position := 0;
FStream.Read(Result, SizeOf(Result));
end;
function TDataBlock.GetStream: TStream;
var DataSize: Integer;
begin
FStream.Position := 4;
DataSize := FStream.Size - BytesReserved; // BytesReserved = 8
FStream.Write(DataSize, SizeOf(DataSize)); // 回寫長度
FStream.Position := 0;
Result := FStream;
end;
若需加密/壓縮,只需另行實作 IDataBlock 介面替換 TDataBlock,架構極具彈性。
| 模組 | 核心技術 | 現代對應 |
|---|---|---|
| 執行緒模型 | Per-Connection Thread + MsgWaitForMultipleObjects | IOCP / async/await / Node.js Event Loop |
| 傳輸抽象 | ITransport / IDataBlock 介面 | gRPC Transport / Netty Channel |
| 協定解析 | Signature-based Dispatch (Interpreter 模式) | Protobuf / JSON-RPC Router |
| 序列化 | OleVariant + 自訂二進位 | Protobuf / MessagePack / JSON |
| 元件模型 | COM STA + Interface | Dependency Injection + Interface |
Scktsrvr.exe 雖屬上世紀技術,但其「介面導向、關注點分離、非阻塞事件迴圈、可換裝傳輸/序列化」的架構思想,放在今天微服務、高併發網關設計依然完全適用。強烈建議有志於系統級開發的 Delphi 程式設計師完整閱讀一次原始碼。