如果 Android 中的 HttpGet 操作持续时间过长,如何避免出现错误 10053 (WSAECONNABORTED)?
Posted
技术标签:
【中文标题】如果 Android 中的 HttpGet 操作持续时间过长,如何避免出现错误 10053 (WSAECONNABORTED)?【英文标题】:How to avoid getting an error 10053 (WSAECONNABORTED) if a HttpGet operation in Android is lasting too long? 【发布时间】:2013-09-03 21:14:56 【问题描述】:我有一个使用 Indy 10 TIdHttpServer(随 Delphi 2006 提供)与 Delphi 2006 Web 服务应用程序通信的 android 应用程序。 Delphi 应用程序生成一个大的 XML 文件并提供它。 XML 生成可能会持续 5 分钟以上。
如果GenerateXml()
的持续时间超过大约 5 分钟 (*),如果在 Delphi IDE 中运行,我会在 TIdHTTPResponseInfo.WriteContent
中检测到错误 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 分钟的情况下也遇到了错误。
【问题讨论】:
旁注:如果我切断 WLAN 连接,我确实会在 Android 中收到错误:java.net.SocketException recvfrom failed: ETIMEDOUT (Connection timed out) 【参考方案1】:Android 和您的服务器之间的某些东西(例如防火墙/路由器)可能会在闲置时间过长后切断连接。您应该尝试启用 TCP keep-alives 以避免这种情况。
另一方面,这是 HTTP 1.1 的 chunked transfer encoding 旨在处理的那种情况(假设您开始使用 HTTP 1.1)。与其等待 5 分钟以完整生成整个 XML,然后再将其发送到客户端,不如在生成 XML 时将其分段发送。这不仅使连接保持活动状态,而且还减少了服务器的内存占用,因为它不必一次将整个 XML 存储在内存中。
TIdHTTPServer
本身(还)不支持发送分块响应(但TIdHTTP
确实支持接收分块响应),但是手动实现并不难。编写自定义TStream
派生类并覆盖其虚拟Write()
方法(或使用Indy 的TIdEventStream
类)以使用RFC 2616 Section 3.6.1 中概述的格式将数据写入HTTP 客户端。这样,您可以让ServeXmlDoc()
将ResponseInfo.TransferEncoding
属性设置为'chunked'
并调用ResponseInfo.WriteHeader()
方法而不设置ResponseInfo.ContentText
或ResponseInfo.ContentStream
属性,然后将您的自定义流传递给IXMLDocument.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,这样您就可以在创建 XML 内容时使用 ResponseInfo.Connection.IOHandler
发送它。
【讨论】:
非常感谢您的详细解答。我尝试使用SetSockOpt 启用 TCP keep-alives。那没有帮助。我是否也必须在客户端做一些事情?我同意,摆脱 IXMLDocument 并使用分块传输编码会更好,但这将是一项艰巨的任务。 (顺便说一句,我在华为路由器或 Windows 防火墙中找不到可以指定上述超时的设置。) 我建议在客户端而不是服务器端启用 TCP keepalive。并不是所有的路由器/防火墙都有明确的设置来设置空闲超时。 好像是difficult在android上设置TCP keepalive超时。我已经放弃了。我已经将生成的 XML 文件拆分成几个文件,暂时解决了这个问题。奇怪的是,我不得不禁用 TIdHTTPServer 中的 keepalive,否则即使对于拆分的 XML 文件(大约 1 分钟后),我也会收到 10053 错误。当然,这只是一种解决方法。 我担心可能会发生异常,这意味着我的 HttpGet 调用将永远持续下去。正如我在第二个问题中所问的那样:除了超时之外,真的没有办法在客户端检测到这种异常吗? Android 实际上是基于 Linux 的,Linux 通过setsockopt()
函数支持TCP_KEEPIDLE
和TCP_KEEPINTVL
套接字选项。至于错误处理,如果服务器端发生异常,你让服务器处理它,它就会关闭连接,Android 应该能够像任何其他断开连接一样检测到。以上是关于如果 Android 中的 HttpGet 操作持续时间过长,如何避免出现错误 10053 (WSAECONNABORTED)?的主要内容,如果未能解决你的问题,请参考以下文章
android中httppost和httpget方法的区别? [复制]
在 android 中调用 httpget 时出错(检索 google 方向)
如何在Android java插件端等待异步操作(任何I / O)?