【问题标题】:Delphi Append packet to TBytes, validate and readDelphi 将数据包附加到 TBytes,验证和读取
【发布时间】:2014-03-16 11:57:12
【问题描述】:

我有这个PCBATMEL-microcontroller 通过Lantronix Xport 通过 TCP 通信。它使用 30 字节长的 asci 字符串发送一些继电器和传感器的状态报告,格式如下:

400000000000000000414243303031303030303030303030303030303339
|                 | | |
| byte[0] = $40   | | |
                  | | |- byte[11] = $43 // 'C' Always 
                  | |--- byte[10] = $42 // 'B' Always
                  |----- byte[9]  = $41 // 'A' Always

byte[1..8] = boolean values := Bool(byte[x]);
byte[12..29] = 6 different numbers as string, 3 chars long each, range 0..999

我对数据包的加载状态进行了简单的验证:

procedure Load(iData: TBytes);
const vsp = 9;  //Validation String Postition
   buflen = 30;
begin
  If (Length(iData) = buflen) And (iData[0] = $40) And (iData[vsp] = $41) And (iData[vsp + 1] = $42) And (iData[vsp + 2] = $43) Then
    SetStatus(iData)  //Function for loading validated packet
  Else Begin
    DoError(1, 'Invalid packet. Length = ' + IntToStr(Length(iData)) + #13#10 +
               'iData[0]       = ' + String(ByteToHex(iData[0])) + #13#10 +
               'iData[vsp]     = ' + String(ByteToHex(iData[vsp])) + #13#10 +
               'iData[vsp + 1] = ' + String(ByteToHex(iData[vsp + 1])) + #13#10 +
               'iData[vsp + 2] = ' + String(ByteToHex(iData[vsp + 2]))
               );
  End;
End;

问题在于,有时 asci 字符串被拆分为多个数据包或连接到一个数据包中(第一个数字是 tickcount):

4239483 4000  // Could be missing in case of disconnect-reconnect.
4239514 00000000000000414243303030303030303030303030303030303338400000000000000000414243
4239545 303031303030303030303030303030303339
4239576 400000000000000000414243303031303030303030303030303030303339400000000000000000414243303031303030303030303030303030303338
4239670 40000000000000000041424330303130
4239701 3030303030303030303030303339

在断开-重新连接的情况下,部分状态信息将丢失。

如何使用 TMemoryStream 或 Move 来读取收到的每个数据包而不丢弃任何信息?

编辑: SetStatus 看起来像这样,所以我想避免将状态报告移植到字符串:

Procedure TTractor.SetStatus(AData: TBytes);
begin
  StatusStream := AData;
  pStatus.HighSpeed             := Bool(AData[1]);
  pStatus.RevDir                := Bool(AData[2]);
  pStatus.FwdDir                := Bool(AData[3]);
  pStatus.FrontExpanded         := Bool(AData[4]);
  pStatus.RearExpanded          := Bool(AData[5]);
  pStatus.AntiSpin              := Bool(AData[6]);
  pStatus.Unknown1              := DecodePCBNumber(AData[12], AData[13], AData[14]);
  pStatus.PumpPressureVoltage   := DecodePCBNumber(AData[15], AData[16], AData[17]);
  pStatus.Unknown2              := DecodePCBNumber(AData[18], AData[19], AData[20]);
  pStatus.WheelPressureVoltage  := DecodePCBNumber(AData[21], AData[22], AData[23]);
  pStatus.OilTemperatureVoltage := DecodePCBNumber(AData[24], AData[25], AData[26]);
  pStatus.PCBTemperatureVoltage := DecodePCBNumber(AData[27], AData[28], AData[29]);
End;

阅读代码:

procedure TForm1.FormCreate(Sender: TObject);
begin
  If Traktor = nil Then Traktor := TTractor.Create(500);
  //...
End;

// TidConnectionIntercept
procedure TForm1.trCItcReceive(ASender: TIdConnectionIntercept; var ABuffer: TArray<System.Byte>);
Var
  tmpList: TList;
  i: Integer;
Begin
  If Traktor <> nil Then Traktor.StatusTBytes := ABuffer;
  AppendConLog(TraktorReceive, False, False, BytesToHexStr(ABuffer) );
End;

procedure TForm1.AppendConLog(LogTyp: TLogType; ShowInConsole, PlainText: Boolean; Text: String);
begin
  If ShowInConsole Or (PlainText And DebugMode) Then Begin
    MemoConsole.Lines.Add(FormatDateTime('[hh:nn:ss] ', Now) + Text);
    Text := String2Hex(AnsiString(Text));
  End;
  LogFile.Add(IntToHex(DateTimeToUNIXTimeFAST(Now()), 8) + ' ' + IntToHex(GetTickCount, 8) + ' ' +
              IntToHex(Word(ShowInConsole), 1) + IntToHex(Word(PlainText), 1) + ' ' +
              IntToHex( Ord(LogTyp), 2) + ' ' + Text);
end;

Type TTractor = class
    constructor Create(LoadInterval: Cardinal; SyncInterval: Cardinal = 25);
    //....
  public
    property StatusTBytes: TBytes read StatusStream write Load;    
  //...
End;    

【问题讨论】:

  • 这个不错。但是,除非必须,否则我想避免将其移植到字符串中。
  • 你用什么来读取 TCP 数据?例如,如果 Indy,您可以在需要时调用 ReadBytes(30),让它为您处理数据包拆分/连接。
  • 您不必将其移植到字符串中,只需从中获得灵感并使用Byte 而不是CharTBytes 而不是string
  • @wittrup:Intercept 用于处理数据(压缩、加密等),而不是用于检测何时读取数据。请改用IOHandler 方法,例如ReadBytes()TIdIOHandler 有自己的内部缓冲区来为您处理拆分/连接的数据包。这使您可以专注于更高级别的逻辑(读取 30 字节的消息),而 Indy 处理较低级别的细节(循环读取直到 30 字节可用,缓存未使用的数据以供后续读取等)。如果您显示您当前的阅读代码,我可以帮助您重写它以正确使用 Indy 的IOHandler

标签: delphi sockets tcp bytearray delphi-xe3


【解决方案1】:

Intercept.OnReceive 事件不会自行触发,它是在另一个读取操作的上下文中触发的。您显示的代码中没有任何内容可以执行任何读取,因此永远不会触发 OnReceive 事件。

Intercept 用于处理数据(压缩、加密等),而不是用于检测何时读取数据。请改用IOHandler 方法,例如ReadBytes()TIdIOHandler 有自己的内部缓冲区来为您处理拆分/连接的数据包。这让您可以专注于更高级别的逻辑(读取 30 字节的消息),而 Indy 处理较低级别的细节(循环读取直到 30 字节可用,缓存未使用的数据以供后续读取等)。

彻底摆脱Intercept,然后在需要时调用TIdTCPClient.IOHandler.ReadBytes(),例如:

在计时器中:

procedure TForm1.IdTCPClient1Connected(Sender: TObject);
begin
  ReadTimer.Enabled := True;
end;

procedure TForm1.IdTCPClient1Disconnected(Sender: TObject);
begin
  ReadTimer.Enabled := False;
end;

procedure TForm1.ReadTimerElapsed(ASender: TObject);
var
  Buffer: TIdBytes;
Begin
  try
    if IdTCPClient1.IOHandler.InputBufferIsEmpty then
    begin
      IdTCPClient1.IOHandler.CheckForDataOnSource(10);
      IdTCPClient1.IOHandler.CheckForDisconnect;
      if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
    end;
    IdTCPClient1.IOHandler.ReadBytes(Buffer, 30);
  except
    IdTCPClient1.Disconnect;
    Exit;
  end;
  If Traktor <> nil Then Traktor.StatusTBytes := Buffer;
  AppendConLog(TraktorReceive, False, False, BytesToHexStr(Buffer) );
End;

或者在工作线程中:

type
  TDataEvent = procedure(const Data: TIdBytes) of object;

  TReadingThread = class(TThread)
  private
    FBuffer: TIdBytes;
    FClient: TIdTCPClient;
    FOnData: TDataEvent;
    procedure DoData;
  protected
    procedure Execute; override;
  public
    constructor Create(AClient: TIdTCPClient; AOnData: TDataEvent); reintroduce;
  end;

constructor TReadingThread.Create(AClient: TIdTCPClient; AOnData: TDataEvent);
begin
  inherited Create(False);
  FClient := AClient;
  FOnData := AOnData;
end;

procedure TReadingThread.Execute;
begin
  while not Terminated do
  begin
    FClient.IOHandler.ReadBytes(FBuffer, 30);
    Synchronize(DoData);
  end;
end;

procedure TReadingThread.DoData;
begin
  if Assigned(FOnData) then FOnData(FBuffer);
end;

var
  ReadThread: TReadingThread = nil;

procedure TForm1.IdTCPClient1Connected(Sender: TObject);
begin
  ReadThread := TReadingThread.Create(IdTCPClient1, DataAvailable);
end;

procedure TForm1.IdTCPClient1Disconnected(Sender: TObject);
begin
  if Assigned(ReadThread) then
  begin
    ReadThread.Terminate;
    ReadThread.WaitFor;
    FreeAndNil(ReadThread);
  end;
end;

procedure TForm1.DataAvailable(const Data: TIdBytes);
begin
  If Traktor <> nil Then Traktor.StatusTBytes := Data;
  AppendConLog(TraktorReceive, False, False, BytesToHexStr(Data) );
end;

【讨论】:

    【解决方案2】:

    将每个部分的数据从端口添加到全局缓冲区:ansistring 检查缓冲区是否在位置 1 包含所需的标头(否则删除标头字节之前的所有标头),并检查缓冲区长度 >= buflen。如果是,则处理数据并将其从缓冲区中删除。

    【讨论】:

    • 我会先检查可能的标题,然后再将其视为数据以说明断开连接。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多