【问题标题】:empty buffer but IdTCPClient.IOHandler.InputBufferIsEmpty is false空缓冲区但 IdTCPClient.IOHandler.InputBufferIsEmpty 为假
【发布时间】:2012-02-04 09:33:27
【问题描述】:

我在使用 idTCPClient 从 telnet 服务器读取缓冲区的以下代码中遇到问题:

procedure TForm2.ReadTimerTimer(Sender: TObject);
var
   S: String; 
begin
   if IdTCPClient.IOHandler.InputBufferIsEmpty then
   begin
     IdTCPClient.IOHandler.CheckForDataOnSource(10);
     if IdTCPClient.IOHandler.InputBufferIsEmpty then Exit;
   end;
   s := idTCPClient.IOHandler.InputBufferAsString(TEncoding.UTF8);
   CheckText(S);
end;

此过程每 1000 毫秒运行一次,当缓冲区有一个值 CheckText 时调用。

此代码有效,但有时会将空缓冲区返回给 CheckText。

有什么问题?

谢谢

【问题讨论】:

  • 我可以通过删除 InputbufferAsString 中的编码类型来解决这个问题。但是接收到的文本包含 UTF8 文本,在显示我的程序时我有 "YX'Y Z)X'X1X(X1[ X.YX/ X1X' YX'X1X/ Z)Y[X/: Z)YYY X9X(YX1:" text :-( , 请帮帮我
  • 一个问题,你为什么不用TidTelnet?这显然是由 telnet 控制字符引起的……
  • 因为 idTelnet 不支持 UTF8,而且我想对缓冲区文本进行一些处理,并可能在显示之前对其进行更改。
  • 这不是不使用TIdTelnet 的正当理由。它不关心文本编码。它的OnDataAvailable 事件使用TIdBytes 将原始字节传递给您。如果你想在String 中使用它,你必须在从TIdTelnet 收到它后自己复制/解码。

标签: delphi telnet tcpclient indy


【解决方案1】:

您的代码正在尝试从InputBuffer 读取任意数据块,并希望它们是完整且有效的字符串。它在没有任何考虑您收到的数据类型的情况下这样做。这会导致多层次的灾难。

您已连接到 Telnet 服务器,但您直接使用 TIdTCPClient 而不是使用 TIdTelnet,因此您必须手动解码 之前收到的任何 Telnet 序列 strong> 然后您可以处理任何剩余的字符串数据。查看TIdTelnet 的源代码。在OnDataAvailable 事件被触发之前有很多解码逻辑发生。所有 Telnet 序列数据都在内部处理,然后 OnDataAvailable 事件提供解码后剩余的任何非 Telnet 数据。

完成 Telnet 解码后,另一个需要注意的问题是 TEncoding.UTF8 只能处理正确编码的 COMPLETE UTF-8 序列。如果遇到编码错误的序列,或者更重要的是遇到不完整的序列,整个解码失败,它会返回一个空白字符串。这已被报告为错误(请参阅QC #79042)。

CheckForDataOnSource()当时 套接字中的所有原始字节存储到 InputBuffer 中。 InputBufferAsString() 提取InputBuffer 在那一刻 中的任何原始字节,并尝试使用指定的编码对其进行解码。当您调用 InputBufferAsString() 时,InputBuffer 中的原始字节很可能并不总是包含 COMPLETE UTF-8 序列。有时InputBuffer 中的最后一个序列可能仍在等待字节到达套接字,并且直到下一次调用CheckForDataOnSource() 时才会读取它们。这可以解释为什么您的 CheckText() 函数在使用 TEncoding.UTF8 时会收到空白字符串。

您应该改用IndyUTF8Encoding()(Indy 实现了自己的 UTF-8 编码器/解码器以避免TEncoding.UTF8 中的解码错误)。至少,您不会再得到空白字符串,但是当 UTF-8 序列跨越多个 CheckForDataOnSource() 调用时,您仍然会丢失数据(不完整的 UTF-8 序列将转换为 ? 字符)。仅出于这个原因,您不应该在这种情况下使用InputBufferAsString()(即使TEncoding.UTF8 确实工作正常)。要正确处理此问题,您应该:

1) 手动扫描InputBuffer,计算有多少字节仅构成COMPLETE UTF-8 序列,然后将该计数传递给InputBuffer.Extract()TIdIOHandler.ReadString()。任何剩余的字节都将保留在InputBuffer 中以备下次使用。为此,您必须摆脱第一个 InputBufferIsEmpty() 调用,而只需无条件调用 CheckForDataOnSource(),这样即使您已经有一些字节,您也总是会检查更多字节。

2) 改用TIdIOHandler.ReadChar() 并完全摆脱对InputBufferIsEmpty()CheckForDataOnSource() 的调用。缺点是如果 UTF-8 序列解码为 UTF-16 代理对,您将丢失数据。 ReadChar() 可以解码代理,但它不能返回对中的第二个字符(我已经开始为 Indy 的未来版本开发新的 ReadChar() 重载,它返回 String 而不是 Char 所以完整的代理对可以是返回)。

【讨论】:

  • Telnet 服务器是否返回多字节字符数据?
  • 它可以,如果这是它想要发送的内容,特别是如果实现了支持多字节/Unicode 的 RFC,如 5198 和 2066。另请记住,某些协议(如 FTP)基于 Telnet,但在 Telnet 之外有自己的文本编码语义,因此即使 Telnet 协议本身未设置为 Telnet 数据也可能包含多字节编码文本使用文本编码。
  • 谢谢 Remy,你能给我举个例子说明两种方法吗?
  • “两种方式中的每一种”,你是什么意思?
  • InputBuffer 和 TIdIOHandler.ReadChar() 方式
【解决方案2】:

虽然您的代码是正确的,但问题很可能是 inputBuffer 包含的数据可能包含将结束字符串的空字符 (#0)。

尝试 Remy's 解决方案,并检查原始字节字符串中的内容。

编辑

我没有读到 OP 正在从 TelnetServer 读取数据。 OP 应该使用 TidTelnet 而不是 IdTCPClient。

编辑2

我刚刚阅读了 OP 的 an older post,其中解释了他不使用 TidTelnet 的原因。

/爸爸

【讨论】:

  • 我在那篇较早的帖子中向他解释了如何使用 TIdTelnet 并且仍然完成目标。
【解决方案3】:

Telnet 服务器在每个回车后发送一个空字符 (#0)。这很可能是您所看到的。

编码为 UTF8 的空字符仍然是一个值为 0 的单字节。检查您收到的是否是这样的。

【讨论】:

    猜你喜欢
    • 2015-06-01
    • 2016-12-03
    • 2014-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    相关资源
    最近更新 更多