最近我开始调查一个关于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:
似乎服务器由于连接处于不活动状态而过早关闭了连接。如果我改为向服务器发送脉冲,即使是一个字节,我就永远不会遇到这个异常,并且我可以继续无限期地传输数据。我构建了一个非常简单的示例应用程序来演示这一点,它使用基本的basicHttpBinding,带有Streamed transferMode,并在客户端的自定义流实现中插入了人为延迟,延迟130秒。这模拟了类似于缓冲区不足的情况,在该情况下,从客户端调用中提供的流没有快速将数据馈送到WCF基础结构,以满足某种不可识别的超时值,该超时值似乎约为130秒左右。
使用WCF服务跟踪工具,我能够找到一条HttpException消息,其中消息读取“客户端已断开连接,因为底层请求已完成。不再有HttpContext可用。”
从IIS Express跟踪日志文件中,我看到一条条目,上面写着“IO操作已被中止,因为线程退出或应用程序请求。(0x800703e3)”
我已经配置了服务器和客户端超时,以使用远高于130秒标记的值,只是为了排除它们。我已经尝试了IIS Express中的idleTimeout和一系列与ASP.NET相关的超时值,以便发现问题来自哪里,但到目前为止还没有运气。到目前为止我能找到的最好信息是火狐问题跟踪器中开发人员的一个评论,描述了在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底层网络基础设施中。我使用的是64位Windows 7,我们是通过在各种客户端上运行服务部分并在Windows 2008 Server上运行服务部分来发现此问题的。
更新2013-01-15
感谢DarkWanderer,我发现了一些新线索,一旦意识到WCF在Windows 7的自托管场景中使用HTTP.sys。这使我开始调查我可以为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 文件似乎没有任何影响。也许IIS Express忽略此配置并在内部使用硬编码值?我尝试了自己的方法,发现 netsh http 命令添加了显示和添加超时值的新命令,因此我尝试了以下命令,但很遗憾,这样做似乎对此错误没有任何影响。
netsh http add timeout timeouttype=IdleConnectionTimeout value=300