我开发了一个WCF双工服务和一个Windows Winforms客户端通过网络进行通信。TCP双工绑定。
两者都可以在我的局域网中正常通信和工作,WCF服务托管在Windows 8工作站的IIS 7上。
然后我尝试在一台租用的专用服务器上运行Windows server 2008 R2,使用固定IP地址:(94.23.220.199),运行IIS 7和。net 4.5.2。
WCF服务已安装在/ScgBroadcastorService虚拟路径和网络上。TCP协议已激活。(实际上,所有的IIS配置都与我在LAN上的个人IIS完全相同)。因此,服务应该可以通过以下URL从外部访问:"http://94.23.220.199/ScgBroadcastorService/Service.svc"。
如果您从浏览器访问此链接,您将获得一个正确的"ScgBroadcastorService Service"页面,其中包含两个wsdl链接。(这些链接正确指向"94.23.220.199"IP地址。
如果一个人点击这个链接,一个正确地得到wsdl xml文档。
因此,由于wsdl文档可以从外部访问,我希望客户端能够与WCF服务通信。
但是如果我启动客户端,我得到以下异常:(对不起,我的家用电脑是法语本地化…根异常是"The server rejected The client credentials.")
下面是完整的跟踪:
System.ServiceModel.Security.SecurityNegotiationException: Le serveur a rejeté les informations d'identification du client. ---> System.Security.Authentication.InvalidCredentialException: Le serveur a rejeté les informations d'identification du client. ---> System.ComponentModel.Win32Exception: La tentative d’ouverture de session a échoué
--- Fin de la trace de la pile d'exception interne ---
à System.Net.Security.NegoState.ProcessReceivedBlob(Byte[] message, LazyAsyncResult lazyResult)
à System.Net.Security.NegoState.StartReceiveBlob(LazyAsyncResult lazyResult)
à System.Net.Security.NegoState.CheckCompletionBeforeNextReceive(LazyAsyncResult lazyResult)
à System.Net.Security.NegoState.StartSendBlob(Byte[] message, LazyAsyncResult lazyResult)
à System.Net.Security.NegoState.CheckCompletionBeforeNextSend(Byte[] message, LazyAsyncResult lazyResult)
à System.Net.Security.NegoState.ProcessReceivedBlob(Byte[] message, LazyAsyncResult lazyResult)
à System.Net.Security.NegoState.StartReceiveBlob(LazyAsyncResult lazyResult)
à System.Net.Security.NegoState.CheckCompletionBeforeNextReceive(LazyAsyncResult lazyResult)
à System.Net.Security.NegoState.StartSendBlob(Byte[] message, LazyAsyncResult lazyResult)
à System.Net.Security.NegoState.ProcessAuthentication(LazyAsyncResult lazyResult)
à System.Net.Security.NegotiateStream.AuthenticateAsClient(NetworkCredential credential, ChannelBinding binding, String targetName, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel allowedImpersonationLevel)
à System.Net.Security.NegotiateStream.AuthenticateAsClient(NetworkCredential credential, String targetName, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel allowedImpersonationLevel)
à System.ServiceModel.Channels.WindowsStreamSecurityUpgradeProvider.WindowsStreamSecurityUpgradeInitiator.OnInitiateUpgrade(Stream stream, SecurityMessageProperty& remoteSecurity)
--- Fin de la trace de la pile d'exception interne ---
Server stack trace:
à System.ServiceModel.Channels.WindowsStreamSecurityUpgradeProvider.WindowsStreamSecurityUpgradeInitiator.OnInitiateUpgrade(Stream stream, SecurityMessageProperty& remoteSecurity)
à System.ServiceModel.Channels.StreamSecurityUpgradeInitiatorBase.InitiateUpgrade(Stream stream)
à System.ServiceModel.Channels.ConnectionUpgradeHelper.InitiateUpgrade(StreamUpgradeInitiator upgradeInitiator, IConnection& connection, ClientFramingDecoder decoder, IDefaultCommunicationTimeouts defaultTimeouts, TimeoutHelper& timeoutHelper)
à System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.SendPreamble(IConnection connection, ArraySegment`1 preamble, TimeoutHelper& timeoutHelper)
à System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.DuplexConnectionPoolHelper.AcceptPooledConnection(IConnection connection, TimeoutHelper& timeoutHelper)
à System.ServiceModel.Channels.ConnectionPoolHelper.EstablishConnection(TimeSpan timeout)
à System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.OnOpen(TimeSpan timeout)
à System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
à System.ServiceModel.Channels.ServiceChannel.OnOpen(TimeSpan timeout)
à System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
à System.ServiceModel.Channels.ServiceChannel.CallOpenOnce.System.ServiceModel.Channels.ServiceChannel.ICallOnce.Call(ServiceChannel channel, TimeSpan timeout)
à System.ServiceModel.Channels.ServiceChannel.CallOnceManager.CallOnce(TimeSpan timeout, CallOnceManager cascade)
à System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
à System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
à System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
请注意,如果我直接从主机启动客户端,使用相同的客户端配置文件,客户端连接和通信是完美的!
这是网页。当前安装在承载服务的服务器上的配置文件:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<compilation debug="true" />
</system.web>
<system.serviceModel>
<services>
<service name="ScgServiceLibrary.ScgBroadcastorService">
<endpoint binding="netTcpBinding" contract="ScgServiceLibrary.IScgBroadcastorService">
<identity>
<servicePrincipalName value="host/94.23.220.199" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True" httpsGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
</configuration>
这是我从外部和主机使用的客户端配置文件:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_IScgBroadcastorService">
<security mode="None"></security>
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://94.23.220.199/ScgBroadcastorService/Service.svc"
binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IScgBroadcastorService"
contract="ScgServiceLibrary.IScgBroadcastorService" name="NetTcpBinding_IScgBroadcastorService">
<identity>
<servicePrincipalName value="host/94.23.220.199" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
注意我已经添加了
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
行在卷筒纸的末尾。使用服务器上的配置文件来获取具有两个wsdl链接上的IP地址的服务页面。如果没有这一行,两个链接包括计算机名"ns304385"而不是IP地址,当然wsdl不能从外部获得。
感谢您帮助我解决剩余的部署问题。我现在卡住了,不知道该怎么做才能让我的客户端到达我在网上托管的WCF服务…
我终于在半夜解决了这个问题…
我必须关闭两侧netttcpbinding的安全性。
但是,对于需要双工通信的合约,如何在服务器端关闭它并不是那么简单。
这是网页。配置文件:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<compilation debug="true" />
</system.web>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="customTcpBinding" maxReceivedMessageSize="5242880" maxConnections="10">
<readerQuotas maxDepth="64" maxStringContentLength="5242880" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
<security mode="None"></security>
</binding>
</netTcpBinding>
</bindings>
<services>
<service name="ScgServiceLibrary.ScgBroadcastorService">
<endpoint binding="netTcpBinding" bindingConfiguration="customTcpBinding" contract="ScgServiceLibrary.IScgBroadcastorService">
<identity>
<servicePrincipalName value="host/94.23.220.199" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True" httpsGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
</configuration>
诀窍是添加一个安全模式设置为"None"的customTcpBinding,并在端点中使用bindingConfiguration属性引用这个新绑定。
不确定customTcpBinding的所有参数是否都是最佳的,但它们对于双工契约是可以的。(我的第一次尝试是拒绝双工合同)
在客户端,我还必须将绑定的安全模式设置为"None"。下面是我在客户端的新配置文件:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_IScgBroadcastorService">
<security mode="None"></security>
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://94.23.220.199/ScgBroadcastorService/Service.svc"
binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IScgBroadcastorService"
contract="ScgServiceLibrary.IScgBroadcastorService" name="NetTcpBinding_IScgBroadcastorService">
<identity>
<servicePrincipalName value="host/94.23.220.199" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
最后我的WCF Windows WinForm客户端工作良好,并与我的双工WCF服务通信!!
我不得不说解决这个问题真是一场噩梦。
希望这个完整的描述我的问题将帮助其他开发人员试图托管他们的双工网络。Tcp绑定的WCF服务在网上和无聊的所有意想不到的异常之前找到正确的方法来修改他们的配置文件。
大家晚安!再睡三个小时就回去工作了。:-(
哇!我只是找到了一个部分的解决方案!
至少解释一下为什么我不能从外部访问WCF服务!
阅读本文:https://social.msdn.microsoft.com/Forums/vstudio/en-US/1551b4e1-8e15-4da2-b155-d398379809b3/the-server-has-rejected-the-client-credentials-in-wcf?forum=wcf
所以我试图在服务器上创建一个帐户,与我在家用计算机上使用的帐户相同的名称和密码,当我试图启动客户端…和…它有效!!
你当然会同意我的看法,这是不可接受的…
我希望我的WCF服务能够接受从网络上任何地方登录的用户的客户端连接,并使用他们自己的用户名/密码,我不想关心!
所以,搜索继续在另一种方式…如何允许WCF客户端连接而无需在我的服务器上创建帐户?
敬请期待……希望很快就能找到答案…
将安全模式设置为"none"不是你应该解决这个问题的方法,因为将安全性设置为none也会删除消息的机密性(加密)和完整性(签名)。
<security mode="None"></security>
要删除身份验证,您可以:1)设置安全模式为消息
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_IScgBroadcastorService">
<security mode="Message">
<message clientCredentialType="None" />
</security>
</binding>
</netTcpBinding>
</bindings>
2)或将安全模式设置为Transport
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_IScgBroadcastorService">
<security mode="Transport">
<transport clientCredentialType="None" />
</security>
</binding>
</netTcpBinding>
</bindings>
我必须承认我没有对你的代码做过这样的尝试。我只是想确保阅读本文的人知道身份验证和消息安全不是一回事,并为他们指出正确的方向。
对于驻留在远程机器上的windows服务中的双工wcf服务,我也遇到了相同的错误。为了使其工作,我必须在Windows防火墙高级设置中创建入站和出站规则来打开服务端口。我还创建了一个服务器用户,并将以下代码包含在我的客户机中:
this._client = new WcfService.WcfServiceClient(context);
_client.ClientCredentials.Windows.ClientCredential.UserName = user;
_client.ClientCredentials.Windows.ClientCredential.Password = password;