以下是Duplex Service及其WebConfig文件。在服务中,发送客户端通过 TransmitUserData 方法发送数据,接收客户端通过CallBackContract中的 PublishUserData 接收数据。
现在的问题是,如果我第一次运行客户端它运行正常,但如果我重新运行客户端,它会在传输时出现以下错误。
通信对象System.ServiceModel.Channels.ServiceChannel, 不能用于通信,因为它已被中止。
双面服务合同
[ServiceContract(CallbackContract=typeof(IContactManagerCallBackService))]
public interface IContactManagerUserService
{
[OperationContract(IsOneWay = true)]
void SubscribeAsReceiver();
[OperationContract(IsOneWay = true)]
void SubscribeAsTransmitter();
[OperationContract]
void TransmitUserData(UserInfo info);
}
[ServiceContract]
public interface IContactManagerCallBackService
{
[OperationContract(IsOneWay = true)]
void PublishUserData(UserInfo info);
}
[DataContract]
public class UserInfo
{
[DataMember]
public bool PaidUser { get; set; }
[DataMember]
public string ProfileName{ get; set;}
[DataMember]
public string ComputerName { get; set; }
[DataMember]
public string IPAddress { get; set; }
[DataMember]
public string MACAddress { get; set; }
}
服务实施
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class ContactManagerUserService : IContactManagerUserService
{
SynchronizedCollection<IContactManagerCallBackService> receiverClients;
public ContactManagerUserService()
{
receiverClients = new SynchronizedCollection<IContactManagerCallBackService>();
}
public void SubscribeAsReceiver()
{
IContactManagerCallBackService client = OperationContext.Current.GetCallbackChannel<IContactManagerCallBackService>();
receiverClients.Add(client);
}
public void TransmitUserData(UserInfo info)
{
foreach (IContactManagerCallBackService receiverClient in receiverClients)
{
receiverClient.PublishUserData(info);
}
}
Web.config
<system.serviceModel>
<diagnostics>
<messageLogging logEntireMessage="true" logMalformedMessages="true"
logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="true" />
</diagnostics>
<bindings>
<netTcpBinding>
<binding name="NewBinding0" maxConnections="1000"
portSharingEnabled="true">
<security mode="None" />
</binding>
</netTcpBinding>
</bindings>
<services>
<service name="ContactManagerService.ContactManagerUserService">
<endpoint address="mex" binding="netTcpBinding" bindingConfiguration=""
name="dataEndPoint" contract="ContactManagerService.IContactManagerUserService" />
<endpoint binding="mexTcpBinding" bindingConfiguration="" name="mexHttp"
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost/ContactManagerUserService" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
修改
代码第一次完美运行。 实际上问题是由Transmitter创建的,我们从不订阅或取消订阅。
答案 0 :(得分:3)
第一个接收器退出后,其通道仍保存在服务的receiverClients集合中,因此下次发生传输时,服务器将尝试回调到不存在的客户端。您必须在收件人结束时将其从集合中移除,可能需要取消订阅合同操作。有很多方法可以实现这一点。这是一个例子。 (这些只是变化 - 如果你想要整个项目,请告诉我。)
在IContactManagerUserService.cs中:
// receivers must identify themselves
[OperationContract(IsOneWay = true)]
void SubscribeAsReceiver(string receiverID);
// new operation
[OperationContract(IsOneWay = true)]
void UnSubscribeAsReceiver(string receiverID);
在ContactManagerUserService.cs
中// replace synchronized collection with thread-safe dictionary
using System.Collections.Concurrent;
ConcurrentDictionary<string, IContactManagerCallBackService> receiverClients;
public ContactManagerUserService()
{
receiverClients = new ConcurrentDictionary<string, IContactManagerCallBackService>();
}
public void SubscribeAsReceiver(string receiverID)
{
IContactManagerCallBackService client = OperationContext.Current.GetCallbackChannel<IContactManagerCallBackService>();
receiverClients.TryAdd(receiverID, client);
}
public void UnSubscribeAsReceiver(string receiverID)
{
IContactManagerCallBackService client;
receiverClients.TryRemove(receiverID, out client);
}
public void TransmitUserData(UserInfo info)
{
foreach (IContactManagerCallBackService receiverClient in receiverClients.Values)
{
receiverClient.PublishUserData(info);
}
}
在ReceiverClient.Program.cs中(不要忘记更新ServiceReferences,因为合同已经更改):
// one way to uniquely identify this receiver
string rcvID = Guid.NewGuid().ToString();
// .... //
receivingClient.SubscribeAsReceiver(rcvID);
// .... //
Console.ReadLine();
// Important - this line must be executed, or service will again throw error.
// So when debugging, don't just close window, press enter to execute this line.
// In more robust setting, this would probably go in finally{} block, or in Dispose()
receivingClient.UnSubscribeAsReceiver(rcvID);
调试提示您可能希望拥有可在本地运行的服务版本,因此您可以进入并调试它。此外,当您无法在本地运行服务时,我发现将日志消息发送到服务日志非常有用。
您可以搜索WCF发布者/订阅者以获取更多信息。
答案 1 :(得分:1)
1)您可以使用IErrorHandle来捕获此异常(可能有用)。通常在回调期间抛出异常(序列化/(un)KnownType(s)问题)。
2)如果您在服务器/服务端(在svclog中)看不到问题 - 通信中止的根本原因可能在客户端上。 查看客户端上的回调实现。如果在回调方法中抛出异常(引发事件处理程序等)并且它被吞下(或者你因某种原因错过了它,可能是因为它没有抛出UI线程或其他东西......调试器没有通知你) - 它会(或者它可能)中止通信(客户端代理将出现故障)。