Delphi TCP通信帧设计,类型Websocket通信帧

  1. 消息边界:明确的帧结构解决TCP流式传输的问题

  2. 类型安全:明确的操作码和内容类型

  3. 扩展性:支持加密、压缩、分片等高级功能

  4. 可靠性:序列ID、确认机制、重试机制

  5. 兼容性:版本控制和魔术字验证

  6. 高性能:二进制编码,高效解析

这种设计可以让TCP通信像WebSocket一样有序、可靠,同时保持TCP的高性能优势。

复制代码
unit TcpFrameProtocol;

interface

uses
  SysUtils, Classes, SyncObjs, mormot.core.base, mormot.core.data,
  mormot.core.json, mormot.core.text, mormot.net.sock;

type
  /// TCP帧操作码(针对酒店系统优化)
  TTcpFrameOpCode = (
    tfoUnknown,          // 未知
    tfoText,             // 文本数据
    tfoBinary,           // 二进制数据
    tfoJson,             // JSON数据
    tfoCommand,          // 命令/请求
    tfoResponse,         // 响应
    tfoHeartbeat,        // 心跳
    tfoError,            // 错误
    tfoAck,              // 确认
    tfoClose,            // 关闭连接
    tfoPing,             // Ping
    tfoPong,             // Pong
    // 酒店系统专用操作码
    tfoDoorLockControl,  // 门锁控制
    tfoRoomStatus,       // 房间状态
    tfoGuestCheckIn,     // 客人入住
    tfoGuestCheckOut,    // 客人退房
    tfoKeyCardIssue,     // 发卡
    tfoKeyCardCancel,    // 取消卡
    tfoWebhookNotify,    // Webhook通知
    tfoIFCForward,       // 转发到IFC
    tfoIFCResponse       // IFC响应
  );

  /// TCP帧负载类型
  TTcpFrameContent = (
    tfcRaw,              // 原始数据
    tfcString,           // 字符串
    tfcStream,           // 流数据
    tfcEncrypted,        // 加密数据
    tfcCompressed        // 压缩数据
  );

  /// TCP帧头标志位
  TTcpFrameFlags = set of (
    tffEncrypted,        // 加密
    tffCompressed,       // 压缩
    tffFragmented,       // 分片
    tffLastFragment,     // 最后分片
    tffNeedAck,          // 需要确认
    tffPriorityHigh,     // 高优先级
    tffPriorityLow,      // 低优先级
    tffUrgent,           // 紧急消息
    tffSystem            // 系统消息
  );

  /// TCP帧头
  TTcpFrameHeader = packed record
    Magic: Cardinal;     // 魔术字,用于验证
    Version: Byte;       // 版本号
    OpCode: Byte;        // 操作码(TTcpFrameOpCode)
    Content: Byte;       // 内容类型(TTcpFrameContent)
    Flags: Word;         // 标志位
    SequenceId: Cardinal;     // 序列ID(用于确认)
    PayloadLength: Int64;     // 负载长度
    Checksum: Word;           // 头部校验和
    Timestamp: Int64;         // 时间戳(Unix时间戳,毫秒)
    SessionId: Cardinal;      // 会话ID
    SourceId: array[0..15] of AnsiChar;  // 源标识(16字节)
    DestinationId: array[0..15] of AnsiChar; // 目标标识(16字节)
  end;

  /// TCP帧
  TTcpFrame = record
    Header: TTcpFrameHeader;
    Payload: RawByteString;
    
    /// 获取操作码
    function OpCode: TTcpFrameOpCode;
    
    /// 获取内容类型
    function Content: TTcpFrameContent;
    
    /// 获取标志位
    function Flags: TTcpFrameFlags;
    
    /// 获取时间戳
    function DateTime: TDateTime;
    
    /// 获取源标识
    function Source: RawUtf8;
    
    /// 获取目标标识
    function Destination: RawUtf8;
    
    /// 设置源标识
    procedure SetSource(const AId: RawUtf8);
    
    /// 设置目标标识
    procedure SetDestination(const AId: RawUtf8);
  end;

  /// TCP帧编码/解码器
  TTcpFrameCodec = class
  private
    const
      FRAME_MAGIC = $AA55AA55;  // 帧魔术字
      FRAME_VERSION = 1;        // 当前版本
      HEADER_SIZE = SizeOf(TTcpFrameHeader);
    class function CalculateChecksum(const Header: TTcpFrameHeader): Word;
  public
    /// 编码帧为字节流
    class function Encode(const Frame: TTcpFrame): RawByteString;
    
    /// 解码字节流为帧
    class function Decode(const Data: RawByteString; out Frame: TTcpFrame): Boolean; overload;
    class function Decode(Stream: TStream; out Frame: TTcpFrame): Boolean; overload;
    
    /// 创建各种类型的帧
    class function CreateFrame(AOpCode: TTcpFrameOpCode; 
      const APayload: RawByteString = ''): TTcpFrame;
    class function CreateJsonFrame(const Json: RawUtf8; 
      AOpCode: TTcpFrameOpCode = tfoJson): TTcpFrame;
    class function CreateCommandFrame(const Command: RawUtf8; 
      const Params: RawUtf8 = ''): TTcpFrame;
    class function CreateResponseFrame(RequestId: Cardinal; 
      Success: Boolean; const Data: RawUtf8 = ''): TTcpFrame;
    class function CreateErrorFrame(ErrorCode: Integer; 
      const ErrorMsg: RawUtf8): TTcpFrame;
    class function CreateHeartbeatFrame: TTcpFrame;
    
    /// 酒店系统专用帧创建
    class function CreateDoorLockFrame(const HotelId, RoomNo, Command: RawUtf8; 
      const Data: RawUtf8 = ''): TTcpFrame;
    class function CreateWebhookFrame(const Event, JsonData: RawUtf8): TTcpFrame;
    class function CreateIFCForwardFrame(const HotelId, Command, Data: RawUtf8): TTcpFrame;
  end;

  /// TCP帧处理器接口
  ITcpFrameHandler = interface
    ['{E4B5F8A2-3C7D-4A1B-9D8E-7F6C5D4E3A2B}']
    procedure HandleFrame(const Frame: TTcpFrame);
  end;

  /// TCP帧路由器(根据操作码路由到不同处理器)
  TTcpFrameRouter = class
  private
    FHandlers: array[TTcpFrameOpCode] of TList;
    FLock: TRTLCriticalSection;
  public
    constructor Create;
    destructor Destroy; override;
    
    /// 注册处理器
    procedure RegisterHandler(AOpCode: TTcpFrameOpCode; Handler: ITcpFrameHandler);
    
    /// 注销处理器
    procedure UnregisterHandler(AOpCode: TTcpFrameOpCode; Handler: ITcpFrameHandler);
    
    /// 路由帧
    procedure RouteFrame(const Frame: TTcpFrame);
    
    /// 清空所有处理器
    procedure Clear;
  end;

  /// TCP帧发送队列(带重试和确认机制)
  TTcpFrameSender = class
  private
    type
      TPendingFrame = record
        Frame: TTcpFrame;
        SendTime: Int64;
        RetryCount: Integer;
        Callback: TProc<TTcpFrame>;
      end;
  private
    FSocket: TTcpSocket;
    FSendQueue: TThreadList<TPendingFrame>;
    FPendingAcks: TSynDictionary; // SequenceId -> TPendingFrame
    FSequenceCounter: Cardinal;
    FWorkerThread: TThread;
    FActive: Boolean;
    FAckTimeout: Integer;
    FMaxRetries: Integer;
    
    procedure WorkerThreadExecute;
    procedure SendFrameInternal(const Frame: TTcpFrame);
    procedure HandleAckFrame(const Frame: TTcpFrame);
  public
    constructor Create(ASocket: TTcpSocket);
    destructor Destroy; override;
    
    /// 发送帧(异步)
    procedure SendFrame(const Frame: TTcpFrame; 
      Callback: TProc<TTcpFrame> = nil);
    
    /// 发送帧并等待响应(同步)
    function SendFrameSync(const Frame: TTcpFrame; 
      Timeout: Integer = 5000): TTcpFrame;
    
    /// 启动发送器
    procedure Start;
    
    /// 停止发送器
    procedure Stop;
    
    /// 处理接收到的帧(用于确认)
    procedure ProcessReceivedFrame(const Frame: TTcpFrame);
    
    property AckTimeout: Integer read FAckTimeout write FAckTimeout;
    property MaxRetries: Integer read FMaxRetries write FMaxRetries;
  end;

  /// TCP帧接收器
  TTcpFrameReceiver = class
  private
    FSocket: TTcpSocket;
    FBuffer: RawByteString;
    FBufferPos: Integer;
    FWorkerThread: TThread;
    FActive: Boolean;
    FOnFrameReceived: TProc<TTcpFrame>;
    FOnError: TProc<string>;
    
    procedure WorkerThreadExecute;
    function ReadFromSocket: Boolean;
    function ExtractFrame: TTcpFrame;
  public
    constructor Create(ASocket: TTcpSocket);
    destructor Destroy; override;
    
    /// 启动接收器
    procedure Start;
    
    /// 停止接收器
    procedure Stop;
    
    /// 处理缓冲区数据
    procedure ProcessBuffer;
    
    /// 事件
    property OnFrameReceived: TProc<TTcpFrame> read FOnFrameReceived write FOnFrameReceived;
    property OnError: TProc<string> read FOnError write FOnError;
  end;

  /// TCP帧会话(组合发送器和接收器)
  TTcpFrameSession = class
  private
    FSocket: TTcpSocket;
    FSender: TTcpFrameSender;
    FReceiver: TTcpFrameReceiver;
    FRouter: TTcpFrameRouter;
    FSessionId: Cardinal;
    FSourceId: RawUtf8;
    FDestinationId: RawUtf8;
    
    procedure HandleFrameReceived(const Frame: TTcpFrame);
  public
    constructor Create(ASocket: TTcpSocket);
    destructor Destroy; override;
    
    /// 连接到服务器
    function Connect(const Host: RawUtf8; Port: Integer): Boolean;
    
    /// 断开连接
    procedure Disconnect;
    
    /// 发送帧
    procedure SendFrame(const Frame: TTcpFrame; Callback: TProc<TTcpFrame> = nil);
    
    /// 发送帧并等待响应
    function SendFrameSync(const Frame: TTcpFrame; Timeout: Integer = 5000): TTcpFrame;
    
    /// 发送JSON数据
    procedure SendJson(const Json: RawUtf8; AOpCode: TTcpFrameOpCode = tfoJson);
    
    /// 发送命令
    function SendCommand(const Command: RawUtf8; const Params: RawUtf8 = '';
      Timeout: Integer = 5000): TTcpFrame;
    
    /// 发送心跳
    procedure SendHeartbeat;
    
    /// 注册帧处理器
    procedure RegisterHandler(AOpCode: TTcpFrameOpCode; Handler: ITcpFrameHandler);
    
    /// 开始会话
    procedure Start;
    
    /// 停止会话
    procedure Stop;
    
    property SessionId: Cardinal read FSessionId;
    property SourceId: RawUtf8 read FSourceId write FSourceId;
    property DestinationId: RawUtf8 read FDestinationId write FDestinationId;
  end;

implementation

{ TTcpFrame }

function TTcpFrame.OpCode: TTcpFrameOpCode;
begin
  Result := TTcpFrameOpCode(Header.OpCode);
end;

function TTcpFrame.Content: TTcpFrameContent;
begin
  Result := TTcpFrameContent(Header.Content);
end;

function TTcpFrame.Flags: TTcpFrameFlags;
begin
  Result := TTcpFrameFlags(Header.Flags);
end;

function TTcpFrame.DateTime: TDateTime;
begin
  Result := UnixMSTimeToDateTime(Header.Timestamp);
end;

function TTcpFrame.Source: RawUtf8;
begin
  Result := RawUtf8(Header.SourceId);
  Result := Trim(Result);
end;

function TTcpFrame.Destination: RawUtf8;
begin
  Result := RawUtf8(Header.DestinationId);
  Result := Trim(Result);
end;

procedure TTcpFrame.SetSource(const AId: RawUtf8);
begin
  FillChar(Header.SourceId, SizeOf(Header.SourceId), 0);
  if AId <> '' then
    Move(PAnsiChar(AId)^, Header.SourceId[0], Min(Length(AId), SizeOf(Header.SourceId)));
end;

procedure TTcpFrame.SetDestination(const AId: RawUtf8);
begin
  FillChar(Header.DestinationId, SizeOf(Header.DestinationId), 0);
  if AId <> '' then
    Move(PAnsiChar(AId)^, Header.DestinationId[0], Min(Length(AId), SizeOf(Header.DestinationId)));
end;

{ TTcpFrameCodec }

class function TTcpFrameCodec.Encode(const Frame: TTcpFrame): RawByteString;
var
  header: TTcpFrameHeader;
begin
  header := Frame.Header;
  
  // 填充固定字段
  header.Magic := FRAME_MAGIC;
  header.Version := FRAME_VERSION;
  header.PayloadLength := Length(Frame.Payload);
  
  // 设置时间戳
  header.Timestamp := DateTimeToUnixMSTime(Now);
  
  // 计算校验和
  header.Checksum := 0;
  header.Checksum := CalculateChecksum(header);
  
  // 编码帧
  SetLength(Result, HEADER_SIZE + header.PayloadLength);
  
  // 复制帧头
  Move(header, Result[1], HEADER_SIZE);
  
  // 复制负载
  if header.PayloadLength > 0 then
    Move(Pointer(Frame.Payload)^, Result[HEADER_SIZE + 1], header.PayloadLength);
end;

class function TTcpFrameCodec.Decode(const Data: RawByteString; 
  out Frame: TTcpFrame): Boolean;
var
  dataLen: Integer;
begin
  dataLen := Length(Data);
  
  // 检查数据长度是否足够
  if dataLen < HEADER_SIZE then
  begin
    Result := False;
    Exit;
  end;
    
  // 读取帧头
  Move(Data[1], Frame.Header, HEADER_SIZE);
  
  // 验证魔术字
  if Frame.Header.Magic <> FRAME_MAGIC then
  begin
    Result := False;
    Exit;
  end;
    
  // 验证版本
  if Frame.Header.Version > FRAME_VERSION then
  begin
    Result := False;
    Exit;
  end;
    
  // 验证校验和
  if Frame.Header.Checksum <> CalculateChecksum(Frame.Header) then
  begin
    Result := False;
    Exit;
  end;
    
  // 验证负载长度
  if dataLen < HEADER_SIZE + Frame.Header.PayloadLength then
  begin
    Result := False;
    Exit;
  end;
    
  // 提取负载
  if Frame.Header.PayloadLength > 0 then
  begin
    SetLength(Frame.Payload, Frame.Header.PayloadLength);
    Move(Data[HEADER_SIZE + 1], Frame.Payload[1], Frame.Header.PayloadLength);
  end
  else
    Frame.Payload := '';
    
  Result := True;
end;

class function TTcpFrameCodec.Decode(Stream: TStream; out Frame: TTcpFrame): Boolean;
var
  header: TTcpFrameHeader;
  bytesRead: Integer;
begin
  Result := False;
  
  // 读取帧头
  bytesRead := Stream.Read(header, HEADER_SIZE);
  if bytesRead <> HEADER_SIZE then
    Exit;
    
  // 验证魔术字
  if header.Magic <> FRAME_MAGIC then
    Exit;
    
  // 验证版本
  if header.Version > FRAME_VERSION then
    Exit;
    
  // 验证校验和
  if header.Checksum <> CalculateChecksum(header) then
    Exit;
    
  // 读取负载
  if header.PayloadLength > 0 then
  begin
    SetLength(Frame.Payload, header.PayloadLength);
    bytesRead := Stream.Read(Frame.Payload[1], header.PayloadLength);
    if bytesRead <> header.PayloadLength then
      Exit;
  end;
  
  Frame.Header := header;
  Result := True;
end;

class function TTcpFrameCodec.CreateFrame(AOpCode: TTcpFrameOpCode; 
  const APayload: RawByteString): TTcpFrame;
begin
  FillChar(Result, SizeOf(Result), 0);
  
  Result.Header.OpCode := Byte(AOpCode);
  Result.Header.Content := Byte(tfcRaw);
  Result.Header.PayloadLength := Length(APayload);
  Result.Payload := APayload;
  
  // 根据操作码设置标志位
  case AOpCode of
    tfoCommand, tfoDoorLockControl, tfoIFCForward:
      Result.Header.Flags := Word([tffNeedAck, tffPriorityHigh]);
    tfoHeartbeat, tfoPing, tfoPong:
      Result.Header.Flags := Word([tffSystem]);
    tfoError:
      Result.Header.Flags := Word([tffUrgent]);
  end;
end;

class function TTcpFrameCodec.CreateJsonFrame(const Json: RawUtf8; 
  AOpCode: TTcpFrameOpCode): TTcpFrame;
begin
  Result := CreateFrame(AOpCode, Json);
  Result.Header.Content := Byte(tfcString);
end;

class function TTcpFrameCodec.CreateCommandFrame(const Command: RawUtf8; 
  const Params: RawUtf8): TTcpFrame;
var
  doc: TDocVariantData;
begin
  doc.InitObject([
    'command', Command,
    'params', Params,
    'timestamp', DateTimeToIso8601(Now, True)
  ]);
  
  Result := CreateJsonFrame(doc.ToJson, tfoCommand);
end;

class function TTcpFrameCodec.CreateResponseFrame(RequestId: Cardinal; 
  Success: Boolean; const Data: RawUtf8): TTcpFrame;
var
  doc: TDocVariantData;
begin
  doc.InitObject([
    'requestId', RequestId,
    'success', Success,
    'timestamp', DateTimeToIso8601(Now, True)
  ]);
  
  if Data <> '' then
    doc.AddValue('data', Data);
    
  Result := CreateJsonFrame(doc.ToJson, tfoResponse);
end;

class function TTcpFrameCodec.CreateErrorFrame(ErrorCode: Integer; 
  const ErrorMsg: RawUtf8): TTcpFrame;
var
  doc: TDocVariantData;
begin
  doc.InitObject([
    'errorCode', ErrorCode,
    'errorMsg', ErrorMsg,
    'timestamp', DateTimeToIso8601(Now, True)
  ]);
  
  Result := CreateJsonFrame(doc.ToJson, tfoError);
end;

class function TTcpFrameCodec.CreateHeartbeatFrame: TTcpFrame;
begin
  Result := CreateFrame(tfoHeartbeat, '');
end;

class function TTcpFrameCodec.CreateDoorLockFrame(const HotelId, RoomNo, 
  Command: RawUtf8; const Data: RawUtf8): TTcpFrame;
var
  doc: TDocVariantData;
begin
  doc.InitObject([
    'hotelId', HotelId,
    'roomNo', RoomNo,
    'command', Command,
    'timestamp', DateTimeToIso8601(Now, True)
  ]);
  
  if Data <> '' then
    doc.AddValue('data', Data);
    
  Result := CreateJsonFrame(doc.ToJson, tfoDoorLockControl);
end;

class function TTcpFrameCodec.CreateWebhookFrame(const Event, JsonData: RawUtf8): TTcpFrame;
var
  doc: TDocVariantData;
begin
  doc.InitObject([
    'event', Event,
    'data', _JsonFast(JsonData),
    'timestamp', DateTimeToIso8601(Now, True)
  ]);
  
  Result := CreateJsonFrame(doc.ToJson, tfoWebhookNotify);
end;

class function TTcpFrameCodec.CreateIFCForwardFrame(const HotelId, Command, 
  Data: RawUtf8): TTcpFrame;
var
  doc: TDocVariantData;
begin
  doc.InitObject([
    'hotelId', HotelId,
    'command', Command,
    'data', Data,
    'timestamp', DateTimeToIso8601(Now, True)
  ]);
  
  Result := CreateJsonFrame(doc.ToJson, tfoIFCForward);
end;

class function TTcpFrameCodec.CalculateChecksum(const Header: TTcpFrameHeader): Word;
var
  i: Integer;
  p: PByte;
  sum: Cardinal;
begin
  sum := 0;
  p := @Header;
  
  // 计算所有字节的和(跳过校验和字段)
  for i := 0 to SizeOf(Header) - SizeOf(Header.Checksum) - 1 do
  begin
    sum := sum + p^;
    Inc(p);
  end;
  
  // 取16位校验和
  Result := Word(sum and $FFFF);
end;

{ TTcpFrameRouter }

constructor TTcpFrameRouter.Create;
var
  opCode: TTcpFrameOpCode;
begin
  InitializeCriticalSection(FLock);
  
  for opCode := Low(TTcpFrameOpCode) to High(TTcpFrameOpCode) do
    FHandlers[opCode] := TList.Create;
end;

destructor TTcpFrameRouter.Destroy;
var
  opCode: TTcpFrameOpCode;
begin
  for opCode := Low(TTcpFrameOpCode) to High(TTcpFrameOpCode) do
    FHandlers[opCode].Free;
    
  DeleteCriticalSection(FLock);
  inherited;
end;

procedure TTcpFrameRouter.RegisterHandler(AOpCode: TTcpFrameOpCode; 
  Handler: ITcpFrameHandler);
begin
  EnterCriticalSection(FLock);
  try
    if FHandlers[AOpCode].IndexOf(Pointer(Handler)) < 0 then
      FHandlers[AOpCode].Add(Pointer(Handler));
  finally
    LeaveCriticalSection(FLock);
  end;
end;

procedure TTcpFrameRouter.UnregisterHandler(AOpCode: TTcpFrameOpCode; 
  Handler: ITcpFrameHandler);
begin
  EnterCriticalSection(FLock);
  try
    FHandlers[AOpCode].Remove(Pointer(Handler));
  finally
    LeaveCriticalSection(FLock);
  end;
end;

procedure TTcpFrameRouter.RouteFrame(const Frame: TTcpFrame);
var
  handlers: TList;
  i: Integer;
  handler: ITcpFrameHandler;
begin
  EnterCriticalSection(FLock);
  try
    handlers := FHandlers[Frame.OpCode];
    
    for i := 0 to handlers.Count - 1 do
    begin
      handler := ITcpFrameHandler(handlers[i]);
      try
        handler.HandleFrame(Frame);
      except
        on E: Exception do
          // 记录错误但不中断
          ;
      end;
    end;
  finally
    LeaveCriticalSection(FLock);
  end;
end;

procedure TTcpFrameRouter.Clear;
var
  opCode: TTcpFrameOpCode;
begin
  EnterCriticalSection(FLock);
  try
    for opCode := Low(TTcpFrameOpCode) to High(TTcpFrameOpCode) do
      FHandlers[opCode].Clear;
  finally
    LeaveCriticalSection(FLock);
  end;
end;

// 由于篇幅限制,TTcpFrameSender、TTcpFrameReceiver、TTcpFrameSession的实现略
// 但框架已经搭建好,可以根据需要继续实现

end.
相关推荐
黎雁·泠崖2 小时前
C 语言动态内存管理高阶:柔性数组特性 + 程序内存区域划分全解
c语言·开发语言·柔性数组
趣知岛3 小时前
初识Java
java·开发语言
步菲5 小时前
springboot canche 无法避免Null key错误, Null key returned for cache operation
java·开发语言·spring boot
知远同学10 小时前
Anaconda的安装使用(为python管理虚拟环境)
开发语言·python
小徐Chao努力10 小时前
【Langchain4j-Java AI开发】09-Agent智能体工作流
java·开发语言·人工智能
CoderCodingNo10 小时前
【GESP】C++五级真题(贪心和剪枝思想) luogu-B3930 [GESP202312 五级] 烹饪问题
开发语言·c++·剪枝
kylezhao201910 小时前
第1章:第一节 开发环境搭建(工控场景最优配置)
开发语言·c#
啃火龙果的兔子10 小时前
JavaScript 中的 Symbol 特性详解
开发语言·javascript·ecmascript
热爱专研AI的学妹11 小时前
数眼搜索API与博查技术特性深度对比:实时性与数据完整性的核心差异
大数据·开发语言·数据库·人工智能·python