就在最近,我开始研究 WCF 流的一个棘手问题,如果客户端在两次发送到服务器之间等待的时间超过 130 秒,就会产生 CommunicationException。
这是完整的例外:
System.ServiceModel.CommunicationException was unhandled by user code
HResult=-2146233087
Message=The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue. Local socket timeout was '23:59:59.9110000'.
Source=mscorlib
StackTrace:
Server stack trace:
at System.ServiceModel.Channels.HttpOutput.WebRequestHttpOutput.WebRequestOutputStream.Write(Byte[] buffer, Int32 offset, Int32 count)
at System.IO.BufferedStream.Write(Byte[] array, Int32 offset, Int32 count)
at System.Xml.XmlStreamNodeWriter.FlushBuffer()
at System.Xml.XmlStreamNodeWriter.GetBuffer(Int32 count, Int32& offset)
at System.Xml.XmlUTF8NodeWriter.InternalWriteBase64Text(Byte[] buffer, Int32 offset, Int32 count)
at System.Xml.XmlBaseWriter.WriteBase64(Byte[] buffer, Int32 offset, Int32 count)
at System.Xml.XmlDictionaryWriter.WriteValue(IStreamProvider value)
at System.ServiceModel.Dispatcher.StreamFormatter.Serialize(XmlDictionaryWriter writer, Object[] parameters, Object returnValue)
at System.ServiceModel.Dispatcher.OperationFormatter.OperationFormatterMessage.OperationFormatterBodyWriter.OnWriteBodyContents(XmlDictionaryWriter writer)
at System.ServiceModel.Channels.Message.OnWriteMessage(XmlDictionaryWriter writer)
at System.ServiceModel.Channels.TextMessageEncoderFactory.TextMessageEncoder.WriteMessage(Message message, Stream stream)
at System.ServiceModel.Channels.HttpOutput.WriteStreamedMessage(TimeSpan timeout)
at System.ServiceModel.Channels.HttpOutput.Send(TimeSpan timeout)
at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.SendRequest(Message message, TimeSpan timeout)
at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at WcfService.IStreamingService.SendStream(MyStreamUpRequest request)
at Client.Program.<Main>b__0() in c:\Users\jpierson\Documents\Visual Studio 2012\Projects\WcfStreamingTest\Client\Program.cs:line 44
at System.Threading.Tasks.Task.Execute()
InnerException: System.IO.IOException
HResult=-2146232800
Message=Unable to write data to the transport connection: An existing connection was forcibly closed by the remote host.
Source=System
StackTrace:
at System.Net.Sockets.NetworkStream.MultipleWrite(BufferOffsetSize[] buffers)
at System.Net.ConnectStream.InternalWrite(Boolean async, Byte[] buffer, Int32 offset, Int32 size, AsyncCallback callback, Object state)
at System.Net.ConnectStream.Write(Byte[] buffer, Int32 offset, Int32 size)
at System.ServiceModel.Channels.BytesReadPositionStream.Write(Byte[] buffer, Int32 offset, Int32 count)
at System.ServiceModel.Channels.HttpOutput.WebRequestHttpOutput.WebRequestOutputStream.Write(Byte[] buffer, Int32 offset, Int32 count)
InnerException: System.Net.Sockets.SocketException
HResult=-2147467259
Message=An existing connection was forcibly closed by the remote host
Source=System
ErrorCode=10054
NativeErrorCode=10054
StackTrace:
at System.Net.Sockets.Socket.MultipleSend(BufferOffsetSize[] buffers, SocketFlags socketFlags)
at System.Net.Sockets.NetworkStream.MultipleWrite(BufferOffsetSize[] buffers)
InnerException:
由于连接不活动,服务器似乎过早地关闭了连接。如果我改为向服务器发送一个脉冲,甚至一次一个字节,那么我永远不会得到这个异常,我可以继续无限期地传输数据。我构建了一个非常简单的示例应用程序来演示这一点,它使用带有 Streamed transferMode 的 basicHttpBinding,我从客户端上的自定义流实现中插入了一个人工延迟,延迟 130 秒。这模拟了类似于缓冲区运行不足的情况,在这种情况下,我在客户端的服务调用中提供的流没有足够快地将数据提供给 WCF 基础结构,无法满足某种似乎在 130 秒左右的无法识别的超时值标记。
使用 WCF 服务跟踪工具,我可以找到一个 HttpException,并显示一条消息:“客户端已断开连接,因为基础请求已完成。不再有可用的 HttpContext。”
从 IIS Express 跟踪日志文件中,我看到一个条目显示“由于线程退出或应用程序请求,I/O 操作已中止。(0x800703e3)”
我已将服务器和客户端超时配置为使用远超过 130 秒标记的值来排除它们。我已经在 IIS Express 中尝试了 idleTimeout 以及许多与 ASP.NET 相关的超时值,以便发现这个问题来自哪里,但到目前为止还没有运气。到目前为止,我能找到的最佳信息是开发人员在 FireFox 问题跟踪器中的评论,该评论描述了在 WCF 体系结构之外工作的类似问题。出于这个原因,我猜这个问题可能更具体地与 IIS7 或 Windows Server 相关。
服务器 Web.config 上的自定义绑定
<binding name="myHttpBindingConfiguration"
closeTimeout="02:00:00"
openTimeout="02:00:00"
receiveTimeout="02:00:00"
sendTimeout="02:00:00">
<textMessageEncoding messageVersion="Soap11" />
<httpTransport maxBufferSize="65536"
maxReceivedMessageSize="2147483647"
maxBufferPoolSize="2147483647"
transferMode="Streamed" />
</binding>
代码中的客户端配置:
var binding = new BasicHttpBinding();
binding.MaxReceivedMessageSize = _maxReceivedMessageSize;
binding.MaxBufferSize = 65536;
binding.ReaderQuotas.MaxStringContentLength = int.MaxValue;
binding.ReaderQuotas.MaxArrayLength = int.MaxValue;
binding.TransferMode = TransferMode.Streamed;
binding.ReceiveTimeout = TimeSpan.FromDays(1);
binding.OpenTimeout = TimeSpan.FromDays(1);
binding.SendTimeout = TimeSpan.FromDays(1);
binding.CloseTimeout = TimeSpan.FromDays(1);
为了响应 wals 的想法,尝试通过自行托管我的服务来查看我是否得到任何不同的结果,我想补充一点,我这样做了,发现我得到的结果与在 IIS 中托管时相同。这是什么意思?我的猜测是,这意味着问题出在 WCF 或 Windows 的底层网络基础结构中。我使用的是 Windows 7 64 位,我们通过运行各种客户端并在 Windows 2008 Server 上运行服务部分发现了这个问题。
2013-01-15 更新
一旦我意识到 WCF 在 Windows 7 上的自托管场景中使用 HTTP.sys,多亏了 DarkWanderer,我发现了一些新线索。这让我研究了我可以为 HTTP.sys 配置什么以及人们报告的问题类型对于 HTTP.sys,这听起来与我所经历的相似。这将我带到位于C:\Windows\System32\LogFiles\HTTPERR\httperr1.log的日志文件,该文件似乎记录了 HTTP.sys 的特定类型的 HTTP 问题。在此日志中,我每次运行测试时都会看到以下类型的日志条目。
2013-01-15 17:17:12 127.0.0.1 59111 127.0.0.1 52733 HTTP/1.1 POST /StreamingService.svc - - Timer_EntityBody -
因此,要找出哪些条件可能导致Timer_EntityBody错误,以及 IIS7 或其他地方的哪些设置可能会影响何时以及是否发生该错误。
来自官方 IIS 网站:
在请求实体正文到达之前连接已过期。当明确请求具有实体主体时,HTTP API 会打开 Timer_EntityBody 计时器。最初,此计时器的限制设置为 connectionTimeout 值。每次在此请求上收到另一个数据指示时,HTTP API 都会重置计时器,以便为连接提供更多分钟,如 connectionTimeout 属性中指定的。
尝试按照上面的参考在 IIS Express 的 applicationhost.config 中建议修改 connectionTimeout 属性似乎没有任何区别。也许 IIS Express 会忽略此配置并在内部使用硬编码值?我自己尝试了一些东西,我发现添加了新的 netsh http 命令来显示和添加超时值,所以我想出了以下命令,但不幸的是,这样做似乎对这个错误也没有任何影响。
netsh http 添加超时 timeouttype=IdleConnectionTimeout value=300