我正在运行一个.NET Windows服务,该服务具有一个线程,该线程使用具有wsHttpBinding
安全性的TransportWithMessageCredential
调用WCF服务。我们最近移动了WCF服务器,现在它仅支持TLS 1.2,并且已更新Windows服务客户端以使用ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
Windows服务线程每隔2个小时在do ... while循环中调用WCF服务。
do
{
GetUpdates();
}
while (!_syncEvents.ExitThreadEvent.WaitOne(_interval, true));
...
private void GetUpdates()
{
MerchantProcessingClient svc = new MerchantProcessingClient();
try
{
int lastProcessedBatch = facility.GetLastBatchRetrieved();
bool alreadyProcessedMostRecentBatch = svc.IsFacilitysMostRecentBatch(_facilityID, lastProcessedBatch);
if (!alreadyProcessedMostRecentBatch)
{
MPResultOfSettledPaymentsResult mpResult = svc.GetSettledPayments2(_facilityID, lastProcessedBatch);
//Continues to process the response - never calls svc.Close()
当Windows服务启动时,WCF调用会正确执行,但是下次在2小时间隔后再次运行时,每次,它将收到以下异常:
System.ServiceModel.CommunicationException: An error occurred while making the HTTP request to https://example.org/MerchantProcessing.svc. This could be due to the fact that the server certificate is not configured properly with HTTP.SYS in the HTTPS case. This could also be caused by a mismatch of the security binding between the client and the server. ---> System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)
--- End of inner exception stack trace ---
at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)
at System.Net.FixedSizeReader.ReadPacket(Byte[] buffer, Int32 offset, Int32 count)
at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Net.TlsStream.ProcessAuthentication(LazyAsyncResult result)
at System.Net.TlsStream.Write(Byte[] buffer, Int32 offset, Int32 size)
at System.Net.ConnectStream.WriteHeaders(Boolean async)
--- End of inner exception stack trace ---
at System.Net.HttpWebRequest.GetResponse()
at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
--- End of inner exception stack trace ---
Server stack trace:
at System.ServiceModel.Channels.HttpChannelUtilities.ProcessGetResponseWebException(WebException webException, HttpWebRequest request, HttpAbortReason abortReason)
at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Security.SecuritySessionSecurityTokenProvider.DoOperation(SecuritySessionOperation operation, EndpointAddress target, Uri via, SecurityToken currentToken, TimeSpan timeout)
at System.ServiceModel.Security.SecuritySessionSecurityTokenProvider.GetTokenCore(TimeSpan timeout)
at System.IdentityModel.Selectors.SecurityTokenProvider.GetToken(TimeSpan timeout)
at System.ServiceModel.Security.SecuritySessionClientSettings`1.ClientSecuritySessionChannel.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.CallOpenOnce.System.ServiceModel.Channels.ServiceChannel.ICallOnce.Call(ServiceChannel channel, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.CallOnceManager.CallOnce(TimeSpan timeout, CallOnceManager cascade)
at System.ServiceModel.Channels.ServiceChannel.EnsureOpened(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 Data.MerchantProcessingService.IMerchantProcessing.IsFacilitysMostRecentBatch(Guid facilityID, Int32 facilityBatchHistoryID)
at WindowsService.ProcessMerchantServices.GetUpdates()
如果我们更新间隔以每60秒而不是每2小时调用一次服务,则大约10分钟后不会记录任何异常。如果我们将其更改为120秒,则它确实会在第二次尝试中出现异常。
因此,似乎某种东西在保留在安全令牌上,或者试图重用连接之类的东西,如果在不到2分钟的间隔内没有这样做,它就会过期。服务器的receivetimeout
设置为90秒,所以我有点怀疑这是限制。我们已经有一个更新等待发布,该更新在WCF代理上调用svc.Close
,但是我不了解的是在每次迭代中创建新的MerchantProcessingClient
时如何或在哪里保留某些内容。我也不确定为什么我们在Windows服务的姊妹WPF应用程序中看不到该原因,该应用程序使用相同的Data.MerchantProcessingService.IMerchantProcessing
代理来调用该服务。这是怎么回事?
更新:
将其进一步缩小,但仍然感到困惑。我已经排除了服务器的receiveTimeout
-这没有涉及。我将以下日志记录添加到Windows服务:
logger.Debug("ServicePointManager.SecurityProtocol = " + (int)System.Net.ServicePointManager.SecurityProtocol);
bool alreadyProcessedMostRecentBatch = svc.IsFacilitysMostRecentBatch(_facilityID, lastProcessedBatch);
,并在运行时查看了到Wireshark中的WCF服务器的流量。 Wireshark显示使用Tls 1.2进行的第一个调用,以及使用Tls v1失败的第二个调用!这样就可以解释该异常,因为服务器不支持v1。在上面的第一个调用之前记录日志:
ServicePointManager.SecurityProtocol = 240 //Ssl3 & Tls
因此,我的应用程序ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
在包含Settings
的类库中的类中的MerchantProcessingClient
行现在未被应用或被覆盖,我假设是后者。好的,但是接下来如何使用Tls 1.2进行首次通话? ServicePointManager.SecurityProtocol
仅在第一次被某个东西覆盖?
然后在第二次调用之前,日志显示如下:
ServicePointManager.SecurityProtocol = 192 //Tls
然后我尝试在每次服务调用之前设置SecurityProtocol
,如下所示:
logger.Debug("ServicePointManager.SecurityProtocol = " + (int)System.Net.ServicePointManager.SecurityProtocol);
System.Net.ServicePointManager.SecurityProtocol = (System.Net.SecurityProtocolType)3072;
logger.Debug("New ServicePointManager.SecurityProtocol = " + (int)System.Net.ServicePointManager.SecurityProtocol);
bool alreadyProcessedMostRecentBatch = svc.IsFacilitysMostRecentBatch(_facilityID, lastProcessedBatch);
这消除了异常,但是前两个迭代的日志很有趣:
ServicePointManager.SecurityProtocol = 192
New ServicePointManager.SecurityProtocol = 3072
/* service call succeeds here, Wireshark shows Tls 1.2 */
ServicePointManager.SecurityProtocol = 192
New ServicePointManager.SecurityProtocol = 3072
/* 2nd service call succeeds here, Wireshark shows Tls 1.2 */
在第二次迭代中,SecurityProtocol
重新设置为192!我有95%的肯定不是明确地写了我们的代码,是将SecurityProtocol
改回Tls
v1。此Windows服务中的其他线程正在按自己的间隔并行调用另一个可能不支持Tls 1.2的WCF服务,因此我认为它在内部调用这些服务时会自动降级SecurityProtocol
,但这并不能将间隔设置为1分钟(而不是2分钟)时,无法解释第一个呼叫始终如何成功或原始代码中的所有呼叫如何成功。我感到困惑。我们当前的计划是尽快升级到.NET 4.6.2,但仍在设法解决这一问题。未知数太多。