【问题标题】:delphi how to setup tcpserver to receive string datadelphi如何设置tcpserver来接收字符串数据
【发布时间】:2019-08-26 13:45:48
【问题描述】:

我必须设置一个 tcp 服务来处理一些客户端请求

所有请求都以十六进制字符串数据包的形式出现,长度为 1099 字节,并且都以 00D0 开头并以 00000000 结尾

procedure TForm2.IdTCPServer1Execute(AContext: TIdContext);
begin
  AContext.Connection.IOHandler.ReadBytes(data, 1099, False);

    RStr:='';
    for I := 0 to length(data)-1 do
      RStr := RStr + chr(data[i]);

    if(copy(RStr,1,4)='00D0') and (copy(RStr,1091,8)='00000000') then
      begin
       Memo14.Lines.Add( 'Frame added to database.' );
      end
    else
      begin
       Memo14.Lines.Add( 'error invalid Frame ...' );
      end;
end;

服务器接收到 1099 字节的数据包,但只显示 error invalid Frame ...

我的代码有什么问题!?

PS:客户端不断向服务器发送数据,我的意思是客户端从第三方接收数据并发送到服务器,因此它可能不是从数据包的第一个开始数据!所以我必须先丢弃一些数据才能到达数据包00D0

【问题讨论】:

  • 数据是十六进制格式的字符串吗?也许你的意思是(RStr, 1, 2) = $00$D0
  • 否,数据正在转换为字符串 (RStr, 1, 4) = '0D00'
  • 设置断点并在调试器中查看RStrif 行时会发生什么?前 4 个字符是您所期望的吗?最后 8 个呢?
  • @KenWhite 0D0000000000 在输入字符串中,但一开始和结束时都没有。我必须删除一些数据才能到达 00D0,然后每 1099 字节以 @ 开头987654332@ 并以00000000 结尾...
  • @roozgar 看,这些是您应该从一开始就提供的详细信息。它们会影响人们给你的答案类型

标签: delphi tcp indy tcplistener


【解决方案1】:

Indy 有一个BytesToString() 函数,在IdGlobal 单元中,因此您无需手动将TIdBytes 转换为string

RStr := BytesToString(data);

string 通常是 1 索引的(除非您正在为移动设备编译并且不使用 {$ZEROBASEDSTRINGS OFF}),因此 copy(RStr,1091,8) 应该使用 1092 而不是 1091,因为您读取的是 1099 字节而不是 1098字节:

copy(RStr,1092,8)

但是,Indy 有 TextStartsWith()TextEndsWith() 函数,也在 IdGlobal 单元中,所以你不需要手动提取和比较子字符串:

if TextStartsWith(RStr, '00D0') and TextEndsWith(RStr, '00000000') then

现在,话虽如此,如果您的套接字数据确实是文本性质的而不是二进制的,您应该使用TIdIOHandler.ReadString() 方法而不是TIdIOHandler.ReadBytes() 方法:

RStr := AContext.Connection.IOHandler.ReadString(1099);

另外,TIdIOHandler 也有 WaitFor()ReadLn() 读取分隔文本的方法,例如:

AContext.Connection.IOHandler.WaitFor('00D0');
RStr := '00D0' + AContext.Connection.IOHandler.ReadLn('00000000') + '00000000';

AContext.Connection.IOHandler.WaitFor('00D0');
RStr := '00D0' + AContext.Connection.IOHandler.WaitFor('00000000', True, True);

最后,TIdTCPServer 是一个多线程组件,它的OnExecute 事件在工作线程的上下文中触发,而不是在主 UI 线程中触发。因此,您必须在访问 UI 时与主 UI 线程同步,例如通过 RTL 的 TThread.Queue()TThread.Synchronize() 类方法,或 Indy 的 TIdNotifyTIdSync 类等。坏事可以 并且通常确实在您从主 UI 线程之外访问 UI 控件时发生。

更新:在 cmets 中,您说的数据实际上是以字节为单位的,而不是文本字符。并且您需要在开始读取记录之前删除字节。在这种情况下,您根本不应该将字节转换为 string。而是按原样处理字节,例如:

procedure TForm2.IdTCPServer1Execute(AContext: TIdContext);
var
  data: TIdBytes;
  b: Byte;
begin
  b := AContext.Connection.IOHandler.ReadByte;
  repeat
    if b <> $00 then Exit;
    b := AContext.Connection.IOHandler.ReadByte;
  until b = $D0;

  SetLength(data, 2);
  data[0] = $00;
  data[1] = $D0;
  AContext.Connection.IOHandler.ReadBytes(data, 1097, True);

  repeat
    if {(PWord(@data[0])^ = $D000)}(data[0] = $00) and (data[1] = $D0)
      and (PUInt32(@data[1091])^ = $00000000) then
    begin
      TThread.Queue(nil,
        procedure
        begin
          Memo14.Lines.Add( 'Frame added to database.' );
        end;
      );
    end else
    begin
      TThread.Queue(nil,
        procedure
        begin
          Memo14.Lines.Add( 'error invalid Frame ...' );
        end;
      );
    end;
    AContext.Connection.IOHandler.ReadBytes(data, 1099, False);
  until False;
end;

【讨论】:

  • 感谢您的完整回答,输入数据以字节为单位,为此我在代码中将字节转换为字符串,WaitFor 是否像您使用的那样工作(字符串参数)或者我必须使用像这样WaitFor('$00$D0') !?
  • 好的,我不需要在第一步转换为字符串,但是我如何在字节输入中使用AContext.Connection.IOHandler.WaitFor,我查看了但从未找到AContext.Connection.IOHandler.WaitFor 的文档!!!跨度>
  • "输入数据以字节为单位" - 那么您需要将各个字节转换为实际的十六进制字符串,例如IntToHex(),实际上您并不是正在做。更好的是,根本不要转换为字符串。但你不能真正将WaitFor() 用于二进制数据。
  • 感谢您的更新,请看我帖子的 PS 以及我为 ken 写的评论。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-31
  • 2019-03-03
相关资源
最近更新 更多