4

我有一个使用 Indy 10 TIdHttpServer(随 Delphi 2006 提供)与 Delphi 2006 Web 服务应用程序通信的 Android 应用程序。Delphi 应用程序生成一个大的 XML 文件并提供它。XML 生成可能会持续 5 分钟以上。

如果持续时间GenerateXml()超过大约 5 分钟 (*),TIdHTTPResponseInfo.WriteContent如果在 Delphi IDE 中运行,我会检测到错误 10053:

Socket Error # 10053 Software caused connection abort.

但是,在 android 端没有检测到任何东西,并且HttpGet-call 会永远持续下去。

我的问题是:

1.) 为什么我会收到错误 10053 以及如何避免它?似乎android超时连接,但http.socket.timeout设置为无限。

2.) 我能做些什么来在客户端检测到这样的错误(除了设置超时,它必须太大而无用)?我可以在 TIdHttpServer.OnException 中做些什么吗?

这是我的代码。Android - 下载功能,在 AsyncTask 中运行:

protected static HttpEntity downloadEntity(String url) throws IOException {
    HttpClient client = new DefaultHttpClient();  

    //Check because of Error 10053: but timeout is null -> infinite
    Log.d("TAG", "http.socket.timeout: " + client.getParams().getParameter("http.socket.timeout"));

    HttpGet get = new HttpGet(url);
    HttpResponse response;
    try {
        //in case of Error 10053 the following call seems to last forever (in PlainSocketImpl.read)
        response = client.execute(get);
    } catch (ClientProtocolException e) {
        //...
    }

    //...

    return response.getEntity();  
}   

TIdHttpServer.OnCommandGet 的 Delphi 实现:

procedure ServeXmlDoc(XmlDoc: IXMLDocument; ResponseInfo: TIdHTTPResponseInfo);
var
    TempStream: TMemoryStream;
begin
    ResponseInfo.ContentType := 'text/xml';
    TempStream := TMemoryStream.Create;
    XMLDoc.SaveToStream(TempStream);
    ResponseInfo.FreeContentStream := True; 
    ResponseInfo.ContentStream := TempStream;
end;

procedure TMyService.HTTPServerCommandGet(AContext: TIdContext; RequestInfo: TIdHTTPRequestInfo;
  ResponseInfo: TIdHTTPResponseInfo);
begin
    Coinitialize(nil); 
    try
        //...
        ServeXmlDoc(GenerateXml(), ResponseInfo);
    finally
        CoUninitialize;
    end;
end;

编辑:(*)即使在整个过程持续时间不到 2 分钟的情况下,我也做了进一步的测试并遇到了错误。

4

1 回答 1

4

Android 和您的服务器之间的某些东西(例如防火墙/路由器)可能会在空闲时间过长后切断连接。您应该尝试启用 TCP keep-alives 以避免这种情况。

另一方面,这是 HTTP 1.1 的分块传输编码旨在处理的那种情况(假设您开始使用 HTTP 1.1)。与其等待 5 分钟以完整生成整个 XML,然后再将其发送到客户端,不如在生成 XML 时将其分段发送。这不仅使连接保持活动状态,而且还减少了服务器的内存占用,因为它不必一次将整个 XML 存储在内存中。

TIdHTTPServer不(还)本机支持发送分块响应(但TIdHTTP确实支持接收分块响应),但是手动实现并不是很困难。编写自定义TStream派生类并覆盖其虚拟Write()方法(或使用 Indy 的类)以使用RFC 2616 第 3.6.1 节TIdEventStream中概述的格式将数据写入 HTTP 客户端。这样,您可以将属性设置为并调用该方法,而无需设置or属性,然后将您的自定义流传递给,以便它将在标头之后完成写入响应数据。例如:ServeXmlDoc()ResponseInfo.TransferEncoding'chunked'ResponseInfo.WriteHeader()ResponseInfo.ContentTextResponseInfo.ContentStreamIXMLDocument.SaveToStream()

type
  TMyChunkedStream = class(TStream)
  private
    fIO: TIdIOHandler;
  public
    constructor Create(AIO: TIdIOHandler);
    function Write(const Buffer; Count: Longint): Longint; override;
    procedure Finished;
    ...
  end;

constructor TMyChunkedStream.Create(AIO: TIdIOHandler);
begin
  inherited Create;
  fIO := AIO;
end;

function TMyChunkedStream.Write(const Buffer; Count: Longint): Longint; override;
begin
  if Count > 0 then
  begin
    fIO.WriteLn(IntToHex(Count, 1));
    fIO.Write(RawToBytes(Buffer, Count));
    fIO.WriteLn;
  end;
  Result := Count;
end;

procedure TMyChunkedStream.Finished;
begin
  fIO.WriteLn('0');
  fIO.WriteLn;
end;
procedure ServeXmlDoc(XmlDoc: IXMLDocument; ResponseInfo: TIdHTTPResponseInfo);
var
  TempStream: TMyChunkedStream;
begin
  ResponseInfo.ContentType := 'text/xml';
  ResponseInfo.TransferEncoding := 'chunked';
  ResponseInfo.WriteHeader;

  TempStream := TMyChunkedStream.Create(ResponseInfo.Connection.IOHandler);
  try
    XMLDoc.SaveToStream(TempStream);
    TempStream.Finished;
  finally
    TempStream.Free;
  end;
end;

另一方面,如果您的大部分等待是在内部GenerateXml()而不是在内部XmlDoc.SaveToStream(),那么您需要重新考虑您的服务器设计,并找出一种加快速度的方法GenerateXml(),或者只是摆脱IXMLDocument并手动创建 XML,这样您就可以ResponseInfo.Connection.IOHandler在创建 XML 内容时使用 发送它。

于 2013-09-04T00:08:51.173 回答