【问题标题】:Delphi: Indy TIdTCPClient Reading XMLDelphi:Indy TIdTCPClient 读取 XML
【发布时间】:2015-06-25 22:56:36
【问题描述】:

我正在使用 TidTCPClient 打开一个到服务器的套接字连接。 服务器开始向我发送不同 XML 数据的音调。 但是 ReadLn 对我没有帮助,因为我不知道 xml 在哪里开始和停止。所以我决定使用 Capture。这工作正确。但正如我从文档捕获中读到的那样,它也调用了 ReadLn,这意味着它逐行读取。有时读取 30~40 行的 XML 需要 1~2 秒。所以我需要更快地阅读它。我可以使用 ReadStream 或其他任何有区别的东西吗?

我现在使用的代码:

procedure TfrmMain.ThreadRun(Sender: TIdThreadComponent);
var
  FData: TStringList;
  FMemory: TStringStream;
begin
  if Client.Connected = False then Exit;
  FData := TStringList.Create;
  Client.IOHandler.Capture(FData, '', False);
  FMemory := TStringStream.Create('',TEncoding.UTF8);
  FMemory.WriteString(FData.Text);
  CoInitialize(Nil);
  QInsert.ParamByName('XMLData').LoadFromStream(FMemory,ftBlob);
  QInsert.Execute;
  CoUninitialize;
  FMemory.Free;
  FData.Free;
end;

如您所见,我将 XML 数据插入到本地表中,这非常快,15ms ~ 60ms 取决于 XML 的大小,但获取数据真的很痛苦。

【问题讨论】:

  • 你能提供一个你试图读取的 TCP 数据的例子吗?请说明如何在 TCP 流中分隔多个 XML 文档(如果有的话)。
  • 顺便说一句,您的CoInitialize()CoUninitialize() 调用属于TIdThreadComponentOnBeforeRunOnAfterRun 事件,而不是OnRun 事件。跨度>
  • 感谢 CoInitialize() 和 CoUninitialize() 警告。我已经解决了。

标签: xml delphi indy


【解决方案1】:

Capture() 在循环中调用ReadLn() 直到读取指定的终止符行(在您的示例中为空行)。

您需要知道每个 XML 文档的结束位置以及下一个文档的开始位置。如果每个文档之间没有明显的标记(空行很好,只要每个 XML 文档在空行之前都以换行符结尾,并且 XML 中不包含任何空行),那么您只剩下选项是读取第一个文档的开始标记,继续阅读直到找到相应的结束标记,然后读取下一个文档的开始标记并等待其结束标记,依此类推。

在这种情况下,您最好使用支持推送模型的 XML 解析器来处理它。您只需从套接字读取原始字节并将它们按原样推送到解析器中,让它在遇到每个顶级文档元素的开始和结束时告诉您。您可以在这两个事件之间缓冲解析器提供给您的任何数据,使用 start 事件来清除/准备缓冲区,并使用 end 事件来完成/刷新缓冲区到您的数据库。

如果您不愿意/不能使用预先存在的 XML 库,则必须编写自己的解析器。

更新:您展示的示例Capture() 将其ADelim 参数设置为'',仅当XML 文档由LF LF 或@987654327 分隔时才有效@ 序列。在这种情况下,您可以使用该分隔符作为 ReadLn()ATerminator 参数,并让它一次读取 1 个完整文档,而不是使用 Capture()

procedure TfrmMain.ThreadBeforeRun(Sender: TIdThreadComponent);
begin
  CoInitialize(Nil);
end;

procedure TfrmMain.ThreadAfterRun(Sender: TIdThreadComponent);
begin
  CoUninitialize;
end;

procedure TfrmMain.ThreadRun(Sender: TIdThreadComponent);
var
  Xml: String;
  FMemory: TStringStream;
begin
  // read until a 'CR LF CR LF' sequence is reached...
  Xml := TrimRight(Client.IOHandler.ReadLn(CR+LF+CR+LF, IndyTextEncoding_UTF8));
  // alternatively:
  // Xml := Client.IOHandler.WaitFor(CR+LF+CR+LF, True, False, IndyTextEncoding_UTF8);

  FMemory := TStringStream.Create(Xml, TEncoding.UTF8);
  try
    QInsert.ParamByName('XMLData').LoadFromStream(FMemory, ftBlob);
  finally
    FMemory.Free;
  end;

  QInsert.Execute;
end;

【讨论】:

  • 嗨雷米,感谢您的回答。 XML 解析器是 MSSQL Server 本身。解析器没有问题。在找到 Capture() 之前,我对 ReadLn() 做了同样的事情,试图找出结束标签。这就是问题开始的地方。一些 XML 仅以“/>”结尾,其中一些以“”结尾,而且在大 xml 的内部树中还有许多“/>”结束标记,因此无法找出它们真正关闭的位置。并且 Capture() 工作完美。正如你所说的那样,一个空白行完成了这项工作。如何读取原始字节(当然知道 xml 文档在哪里完成)并将它们推送到表格?
  • 我想你误解了我的意思。如果 TCP 消息不提供 XML 文档之间的分隔符(并且您没有显示实际的 TCP 数据是什么样的),那么您需要一个 单独的 XML 解析器来处理流式传输,然后才能将单个XML 文档导入数据库。正是由于您给出的原因 - 结束标签可能不同,因此很难告诉 Indy 要查找什么。您需要将入站数据缓存到中间缓冲区并解析该缓冲区以获取单个文档。MSSQL 支持 XML 的事实与此过程无关
  • 我用一个示例更新了我的答案以替换 Capture()
  • 此代码无效:Xml := TrimRight(Client.IOHandler.ReadLn(CR+LF+CR+LF, IndyTextEncoding_UTF8)); 但此有效:Xml := Client.IOHandler.WaitFor(CR+LF+CR+LF, True, False, IndyTextEncoding_UTF8); 我还制作了 2 个不同的应用程序,一个正在使用 Capture(),另一个正在使用 WaitFor,如您所写。他们两个同时开始,并且在测试期间得到了完全相同的数据:2060 个 XML 文件。所以基本上它们对我来说是相同的速度。现在我可以看到这可能是 xml 发送服务器的正常行为/正常速度。与我的客户无关。
  • ReadLn(CR+LF+CR+LF)WaitFor(CR+LF+CR+LF) 基本上在内部做同样的事情 - 从套接字读取直到 CR LF CR LF 到达,然后返回之前收到的所有内容。不同之处在于ReadLn() 将在其结果中包含CR LF CR LF(因此额外的TrimRight()),而WaitFor() 有一个AInclusive 参数以在退出时将其删除。所以我看不出WaitFor() 会如何工作,而ReadLn() 不会,因为它们每次都应该返回相同的数据(除了上面提到的差异)。
猜你喜欢
  • 2013-06-26
  • 1970-01-01
  • 2017-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多