如何使用Delphi中的Indy客户端从服务器读取所有字节?

5

我正在使用Indy客户端来读取服务器发送给我的(客户端)消息。它一次性向我发送512字节的数据。这512个字节的数据由两种数据类型(Word和String)组成。例如,它先发送2字节的Word,然后再发送2字节的Word,然后是50字节的String等等。我尝试使用以下代码来解决这个问题:

var BufferArray : Array[0..512] of Byte;

 if IdTCPClient1.IOHandler.InputBufferIsEmpty then
 begin
      if IdTCPClient1.IOHandler.CheckForDataOnSource(1000) then
      begin
          Edit1.Text := idtcpclient1.IOHandler.ReadBytes(BufferArray ,512, true);
      end;
 end;

我在编辑器中遇到了错误,具体是在这行代码 Edit1.Text := idtcpclient1.IOHandler.ReadBytes(BufferArray ,512, true); 中。错误提示为:实际参数和形式参数的类型必须相同。
我想将整个 512 字节存储到 Edit1.Text 中,然后再对这些数据进行操作。请帮助我从服务器获取全部 512 字节的数据,并检查我的方法是否正确。
更新:备选方案
我正在使用以下方法读取单词和字符串值。
WordArray : array[0..5] of word;

 if IdTCPClient1.IOHandler.InputBufferIsEmpty then
 begin
      if IdTCPClient1.IOHandler.CheckForDataOnSource(1000) then
      begin
        i := 0;
        while i < 6 do //Read all the words
        begin
            //Fill WORD data in array
            WordArray[i] :=  (IdTCPClient1.Socket.ReadWord(True));
        end;
      end;
end;

对于字符串类似的操作,可以采用类似的方法:

WordArray[i] := (IdTCPClient1.Socket.ReadString(50));

这个方法很好用,但在读取所有循环数据时必须保持连接处于打开状态。如果连接在此期间中断,我会丢失所有数据并不得不再次从服务器请求整个数据包。


1
使用动态数组来定义BufferArray,将声明更改为var BufferArray: TBytes; - TLama
是的,我也这样做了。它有效了。谢谢。现在我有进一步的问题。我该如何遍历这个BufferArray?我想把它的所有内容存储到一个字符串数组中。 - user1556433
@TLama - 我更新了我的问题。我的新方法正确吗? - user1556433
抱歉,我放弃这个问答;你要求我解决你客户端的整个通信部分。这是我没有时间和动力去做的。你发布的文档没有说明字节顺序的单词值,也没有说明字符串是如何发送或终止的(这些都是我们向你询问的内容)。关于你的更新,如果你想在读取整个512B数据包之前读取字值,那么为什么现在要尝试从套接字中读取字值?你看过我的回答并理解了它的用途吗(通过最后一次更新,我修改了字值的字节顺序)? - TLama
@Tlama 为什么你要以那种繁琐的显式方式进行顺序颠倒呢?http://docwiki.embarcadero.com/Libraries/XE2/en/System.Swap - Arioch 'The
显示剩余3条评论
2个回答

1

1:字符串的字符集是什么?是1字节的windows-1251吗?还是2字节的Unicode UCS-2?或者是可变长度的UTF-8或UTF-16?

2:字符串的长度是多少?总是50吗?


读取缓冲区:

1. 阅读手册 1.1 http://www.indyproject.org/docsite/html/TIdIOHandler_ReadBytes@TIdBytes@Integer@boolean.html 1.2 http://www.indyproject.org/docsite/html/TIdIOHandler_ReadSmallInt@Boolean.html 1.3 http://docwiki.embarcadero.com/Libraries/XE2/en/System.SetString 2. 根据类型和参数描述准确编写代码。 2.1 读取头文件:应该得到类似以下的结果
var Word1, Word2: word;
Word1 := IOHandler.ReadSmallInt(false); Word2 := IOHandler.ReadSmallInt(false);
3. 读取单字节字符串 3.1 读取缓冲区 3.2 将缓冲区转换为字符串
var Word1, Word2: word; Buffer: TIdBytes; var s: RawByteString; // 或者 AnsiString;或者可能是 UTF8String;但很可能不是 UnicodeString,也就是 string
Word1 := IOHandler.ReadSmallInt(false); Word2 := IOHandler.ReadSmallInt(false);
// 您应该检查是否真正收到了 50 个字节, // 然后执行以下操作:
IOHandler.ReadBytes(Buffer, 50, false); Assert(Length(Buffer)=50); SetString(s, pointer(@Buffer[0]), 50);
4. 继续阅读剩余部分 - 您只读取了 50+2+2 = 54 字节的 512 字节数据包 - 应该还有更多数据。

512 = 54*9+26 - 所以它看起来像一个循环 - 并且舍弃掉26个字节的尾部。

    var Word1, Word2: word;  Buffer: TIdBytes;    
    var s: RawByteString;     

    for i := 1 to 9 do begin    
      Word1 := IOHandler.ReadSmallInt(false);     
      Word2 := IOHandler.ReadSmallInt(false);  

      IOHandler.ReadBytes(Buffer, 50, false);        
      Assert(Length(Buffer)=50);      
      SetString (s, pointer(@Buffer[0]), 50);    

      SomeOutputCollection.AppendNewElement(Word1, Word2, s);   
    end;   
    IOHandler.ReadBytes(Buffer, 512 - 9*(50+2+2), false); // discard the tail

@TLama的ReadString返回UnicodeString。主题发起人提到了50个字节。这里存在潜在的混淆 - 我在答案的标题中指出了这一点。但只要主题发起人保持原始的“50个字节”的说法 - 对我来说就是这样。除非Indy引入ReadAnsiString(const codepage:word; const length: cardinal);方法。 - Arioch 'The
是的,我意识到了并删除了我的评论…对此感到抱歉。 - TLama
让我更正一下并详细说明如何解析我的512数据包。我有这个数据包的文档,其中说将有6个2字节的单词,然后是50字节的字符串。现在我必须将单词存储在不同的单词数组中,将字符串存储在不同的字符串数组中。现在我该如何从这个数据包中提取这些内容? - user1556433
@TLama - 感谢您的评论。既然它让您感到困惑 - 它可能会让任何读者感到困惑。因此,让澄清保持不变。 - Arioch 'The
1
@Naresh 512 = 62 + 1050,对吗?如果我没有算错的话?我向您展示了读取Word值的代码,并向您展示了读取50字节AnsiString的代码。只需运行一个代码6次,另一个代码10次即可。 但是,如果单词是大端或小端,请参阅文档-也许您需要使用ReadSmallInt(true)。此外,您需要知道这些50字节字符串的字符集。最后-我几乎无法相信那些字符串确切地是50个字节,也许它们是C字符串?然后,在完成这些操作之后,您需要查找字符串中的终止符并调整其实际长度。 - Arioch 'The
显示剩余5条评论

1

除非您准确地描述您所拥有的文档中写了什么,否则很难回答您的问题。到目前为止,我们只知道您的512B数据包由6个字和10个50B字符串组成。因此,请将其视为起点,直到您告诉我们更多信息:

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
  Buffer: TBytes;
  WordArray: array[0..5] of Word;
  StringArray: array[0..9] of AnsiString;
begin
  if IdTCPClient1.IOHandler.InputBufferIsEmpty then
  begin
    if IdTCPClient1.IOHandler.CheckForDataOnSource(1000) then
      IdTCPClient1.IOHandler.ReadBytes(Buffer, 512, False);

    for I := 0 to High(WordArray) do
    begin
      WordRec(WordArray[I]).Hi := Buffer[I * 2];
      WordRec(WordArray[I]).Lo := Buffer[I * 2 + 1];
    end;
    for I := 0 to High(StringArray) do
      SetString(StringArray[I], PAnsiChar(@Buffer[I * 50 + 12]), 50);

    // here you have the arrays prepared to be processed
  end;
end;

好的,我会试一下这个。如果它有效,我会非常感谢你。 - user1556433
我并不指望它会起作用,因为单词部分可能已经交换了字节,而字符串部分则期望你接收 ANSI 字符,所以这两者都只是猜测。我完全不知道你的服务器如何发送字符串,它们是 ANSI 还是 Unicode,如何终止(可能是一些不寻常的东西,我不知道)。我也不知道这些单词是以哪种字节顺序发送的。你必须添加这些信息,让我通过它来完善答案。 - TLama
如果这不起作用,我会与您分享一个Word文档,其中显示数据包的确切格式。 - user1556433

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接