首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> Delphi

原始碼閱讀筆記:Scktsrvr.exe (DataSnap/MIDAS Socket 伺服器) 核心架構分析

     編輯:Delphi

前言

Scktsrvr.exe 是 Delphi 中 DataSnap (舊稱 MIDAS) 隨附的標準 Socket 伺服器,原始碼約 900 行,展示了工業級「多執行緒 + COM + 非阻塞 Socket + 自訂協定」的經典架構。本文挑選核心執行緒與資料解析器兩大模組深度解析。

核心執行緒:TSocketDispatcherThread

每個 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;

關鍵設計亮點

  • MsgWaitForMultipleObjects:同時等待 Socket 事件與訊息佇列,解決「阻塞 Socket 執行緒無法處理 COM 訊息」的經典難題。
  • 介面基礎傳輸層:ITransport 隱藏 TCP/HTTP/具名管道細節,TDataBlock 實作 IDataBlock 封裝「長度(4 bytes) + 簽名(4 bytes) + 資料」二進位協定。
  • COM 單元模型:CoInitialize(nil) 宣告 STA,配合 MsgWaitForMultipleObjects 處理跨單元呼叫。

資料解析器:TDataBlockInterpreter

負責依據資料塊 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;

範例:DoGetAppServerList 實作

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;

資料塊格式:TDataBlock 內部結構


// 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 + MsgWaitForMultipleObjectsIOCP / 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 + InterfaceDependency Injection + Interface

結語

Scktsrvr.exe 雖屬上世紀技術,但其「介面導向、關注點分離、非阻塞事件迴圈、可換裝傳輸/序列化」的架構思想,放在今天微服務、高併發網關設計依然完全適用。強烈建議有志於系統級開發的 Delphi 程式設計師完整閱讀一次原始碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved