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

Delphi Indy 串流傳輸完全指南:WriteStream/ReadStream 兩種模式與緩衝區控制

     編輯:Delphi

前言

Indy (TIdTCPClient/TIdTCPServer) 傳輸串流資料時,關鍵在於雙端協議一致:發送端如何寫入長度與資料,接收端如何讀取長度與資料。本文詳細比較兩種主流模式:單一串流+長度前綴與連線關閉觸發讀取,並解析 OpenWriteBuffer/CloseWriteBuffer 緩衝區機制。

模式一:單一串流 + 明確長度前綴 (推薦)

發送端先寫入 4-byte 整數長度,再寫入串流內容;接收端用 ReadStream 自動讀取長度再讀資料。

客戶端發送

procedure TClientForm.BtnSendStreamClick(Sender: TObject);
var
  Strm: TMemoryStream;
  S: string;
begin
  S := 'Hello Indy Stream!';
  Strm := TStringStream.Create(S, TEncoding.UTF8);
  try
    // 1. 開啟寫入緩衝 (合併多次 Write 為單次發送,減少封包碎片)
    IdTCPClient1.IOHandler.OpenWriteBuffer;
    try
      // 2. 寫入長度 (ReadStream 預期前 4 bytes 為長度)
      IdTCPClient1.IOHandler.WriteInteger(Strm.Size);
      // 3. 寫入串流內容,True = 寫入後自動 Free 串流 (這裡手動管理故 False)
      IdTCPClient1.IOHandler.Write(Strm, 0, False);
    finally
      // 4. 關閉緩衝,實際發送
      IdTCPClient1.IOHandler.CloseWriteBuffer;
    end;
  finally
    Strm.Free;
  end;
end;

伺服端接收

procedure TServerForm.IdTCPServer1Execute(AContext: TIdContext);
var
  Strm: TMemoryStream;
begin
  // 檢查連線狀態
  if AContext.Connection.Connected then
  begin
    Strm := TMemoryStream.Create;
    try
      // 關鍵:ReadStream 會自動讀取前 4 bytes 作為長度,再讀取指定位元組數
      AContext.Connection.IOHandler.ReadStream(Strm);
      Strm.Position := 0;
      
      // 處理接收資料
      Memo1.Lines.LoadFromStream(Strm, TEncoding.UTF8);
    finally
      Strm.Free;
    end;
  end;
end;

模式二:多重串流 + 連線關閉觸發 (適用大檔案/串流)

發送端持續寫入串流,不寫入長度;接收端在連線關閉時一次讀取所有資料 (ASize=-1, AReadUntilDisconnect=True)。

客戶端發送 (連接→發送多個串流→斷開)

procedure TClientForm.BtnSendMultiStreamClick(Sender: TObject);
var
  Strm: TMemoryStream;
  I: Integer;
begin
  IdTCPClient1.Connect;
  try
    for I := 1 to 3 do
    begin
      Strm := TStringStream.Create('Data Block ' + IntToStr(I), TEncoding.UTF8);
      try
        IdTCPClient1.IOHandler.OpenWriteBuffer;
        try
          // 不寫入長度,直接寫入串流
          IdTCPClient1.IOHandler.Write(Strm, 0, True); // True = 寫入後 Free 串流
        finally
          IdTCPClient1.IOHandler.CloseWriteBuffer;
        end;
      except
        Strm.Free;
        raise;
      end;
    end;
  finally
    IdTCPClient1.Disconnect; // 關閉連線觸發伺服端讀取結束
  end;
end;

伺服端接收 (連線關閉時一次讀完)

procedure TServerForm.IdTCPServer1Execute(AContext: TIdContext);
var
  Strm: TMemoryStream;
begin
  if AContext.Connection.Connected then
  begin
    Strm := TMemoryStream.Create;
    try
      // ASize=-1 表示讀到連線關閉;AReadUntilDisconnect=True 等待斷開
      AContext.Connection.IOHandler.ReadStream(Strm, -1, True);
      Strm.Position := 0;
      Memo1.Lines.LoadFromStream(Strm, TEncoding.UTF8);
    finally
      Strm.Free;
    end;
  end;
end;

核心 API 對照表

方法簽名行為
WriteIntegerWriteInteger(Value: Integer)寫入 4-byte Little Endian 整數 (網路序需自行轉換)
Write(Stream, ASize, AFree)Write(const AStream: TStream; ASize: Integer = 0; AFree: Boolean = False)寫入串流內容。ASize=0 表示寫入全部。AFree=True 寫入後 Free 串流
ReadStreamReadStream(ADest: TStream; ASize: Integer = -1; AReadUntilDisconnect: Boolean = False)讀取串流。ASize=-1 讀到斷開。若前 4 bytes 為長度,自動解析並讀取指定量
OpenWriteBufferOpenWriteBuffer啟用寫入緩衝,後續 Write 累積在記憶體
CloseWriteBufferCloseWriteBuffer刷新緩衝區一次性發送,減少 TCP 封包碎片

常見陷阱與解決方案

問題原因解決
接收端卡住/逾時發送端未寫入長度,接收端用預設 ReadStream 等待長度前綴統一協議:雙端都用長度前綴,或雙端都用連線關閉模式
大檔案記憶體暴漲用 TMemoryStream 接收 GB 級檔案改用 TFileStream 直接寫入磁碟,或分塊處理
中文字串亂碼編碼不一致雙端明確指定 TEncoding.UTF8 (或約定編碼)
WriteStream 後資料未送達未呼叫 CloseWriteBuffer務必成對呼叫 Open/CloseWriteBuffer
伺服端 Execute 重複執行非阻塞模式下 Execute 會輪詢使用 AContext.Connection.IOHandler.CheckForDataOnSource 或改用 OnExecute 事件配合阻塞讀取

進階:自訂協議幀結構

type
  TStreamHeader = packed record
    Magic: DWORD;     // $DE1DSTREAM
    CmdType: Word;    // 指令類型
    Flags: Word;      // 壓縮/加密旗標
    DataLen: Int64;   // 資料長度 (8 bytes 支援 >4GB)
    CRC32: DWORD;     // 校驗和
  end;

// 發送端
procedure SendStreamWithHeader(IO: TIdIOHandler; Strm: TStream; Cmd: Word);
var
  Hdr: TStreamHeader;
begin
  Hdr.Magic := $DE1DSTREAM;
  Hdr.CmdType := Cmd;
  Hdr.Flags := 0;
  Hdr.DataLen := Strm.Size;
  Hdr.CRC32 := CalculateCRC32(Strm); // 需先計算或邊算邊寫
  
  IO.OpenWriteBuffer;
  try
    IO.WriteBuffer(@Hdr, SizeOf(Hdr));
    IO.Write(Strm, 0, False);
  finally
    IO.CloseWriteBuffer;
  end;
end;

// 接收端
function RecvStreamWithHeader(IO: TIdIOHandler; out Cmd: Word): TMemoryStream;
var
  Hdr: TStreamHeader;
begin
  Result := TMemoryStream.Create;
  try
    IO.ReadBuffer(@Hdr, SizeOf(Hdr));
    if Hdr.Magic <> $DE1DSTREAM then
      raise Exception.Create('Invalid stream header');
    
    Cmd := Hdr.CmdType;
    IO.ReadStream(Result, Hdr.DataLen, False);
    // 驗證 CRC32...
  except
    Result.Free;
    raise;
  end;
end;

結語

Indy 串流傳輸的核心在於協議對稱性。模式一 (長度前綴) 適合指令/回應、小型資料、需即時處理的場景;模式二 (連線關閉) 適合大檔案單向傳輸、串流媒體。生產環境強烈建議採用自訂幀頭結構,包含魔數、版本、指令類型、長度、校驗和,構建健壯、可擴展的網路協議。

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