我正在试图找出为什么当一个故障或者在没有取消订阅的情况下断开连接时使用众所周知的发布 - 订阅模式连接多个回调客户端时,所有客户端状态都设置为已关闭然后出现故障。
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ICallback))]
public interface IPubSubService
{
[OperationContract(IsOneWay = false, IsInitiating = true)]
void Subscribe();
[OperationContract(IsOneWay = false, IsInitiating = true)]
void UnSubscribe();
[OperationContract(IsOneWay = false)]
void BroadcastMessage(string message);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class PubSubService : IPubSubService
{
private ICallback _callbackClient;
public static event Action<string> _action;
public void Subscribe()
{
_callbackClient = OperationContext.Current.GetCallbackChannel<ICallback>();
_action += ActionInvoked;
}
public void UnSubscribe()
{
_action -= ActionInvoked;
}
public void BroadcastMessage(string message)
{
_action.Invoke(message);
}
public void ActionInvoked(string message)
{
_callbackClient.SendMessage(message);
}
}
public interface ICallback
{
[OperationContract(IsOneWay = true)]
void SendMessage(string message);
}
// The Publisher that doesn't subscribe only sends the message
[CallbackBehaviorAttribute(UseSynchronizationContext = false)]
public partial class Form1 : Form, ICallback
{
public Form1()
{
InitializeComponent();
}
private ServiceClient _proxy;
private void button1_Click(object sender, EventArgs e)
{
try
{
_proxy = new ServiceClient(new InstanceContext(this));
_proxy.BroadcastMessage(textBox1.Text);
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
public void SendMessage(string message)
{
}
}
public static class ControlExtensions
{
public static void Invoke(this Control Control, Action Action)
{
Control.Invoke(Action);
}
}
public class ServiceClient : DuplexClientBase<IPubSubService>, IPubSubService
{
public ServiceClient(InstanceContext callbackInstance)
: base(callbackInstance)
{ }
public void Subscribe()
{
Channel.Subscribe();
}
public void UnSubscribe()
{
Channel.UnSubscribe();
}
public void BroadcastMessage(string message)
{
Channel.BroadcastMessage(message);
}
}
// The Subscriber
[CallbackBehaviorAttribute(UseSynchronizationContext = false)]
public partial class Form1 : Form, ICallback
{
public Form1()
{
InitializeComponent();
}
private ServiceClient _proxy;
private void button1_Click(object sender, EventArgs e)
{
_proxy = new ServiceClient(new InstanceContext(this));
_proxy.Subscribe();
this.Invoke(() => textBox1.AppendText("Subscribed..."));
}
public void SendMessage(string message)
{
this.Invoke(() => textBox1.AppendText(message + "\r\n"));
}
private void button2_Click(object sender, EventArgs e)
{
if (_proxy != null && _proxy.State == CommunicationState.Opened)
{
_proxy.UnSubscribe();
}
}
private void button3_Click(object sender, EventArgs e)
{
Thread.Sleep(new TimeSpan(0, 1, 0));
}
}
public static class ControlExtensions
{
public static void Invoke(this Control Control, Action Action)
{
Control.Invoke(Action);
}
}
public class ServiceClient : DuplexClientBase<IPubSubService>, IPubSubService
{
public ServiceClient(InstanceContext callbackInstance) : base(callbackInstance)
{ }
public void Subscribe()
{
Channel.Subscribe();
}
public void UnSubscribe()
{
Channel.UnSubscribe();
}
public void BroadcastMessage(string message)
{
Channel.BroadcastMessage(message);
}
}
// config for both clients publisher and subscriber
<configuration>
<system.windows.forms jitDebugging="true" />
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="netTCPBinding">
<reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="true"/>
<security mode="None">
</security>
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint
address="net.tcp://localhost:8008/PubSubService"
binding="netTcpBinding"
bindingConfiguration="netTCPBinding"
contract="ServiceLibrary.IPubSubService"
name="netTCPBinding">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
</client>
</system.serviceModel>
<startup>
</startup>
</configuration>
// config for Service
<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<services>
<service name="ServiceLibrary.PubSubService">
<endpoint address="net.tcp://localhost:8008/PubSubService"
binding="netTcpBinding"
bindingConfiguration="netTCPBinding"
contract="ServiceLibrary.IPubSubService"/>
</service>
</services>
<bindings>
<netTcpBinding>
<binding name="netTCPBinding" closeTimeout="00:00:10" openTimeout="00:00:10" receiveTimeout="00:00:10" sendTimeout="00:00:10" transactionFlow="false" transferMode="Buffered" maxBufferPoolSize="524288" maxBufferSize="65536" maxConnections="1000" maxReceivedMessageSize="65536">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
<reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="true"/>
<security mode="None">
</security>
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>
答案 0 :(得分:2)
不仅仅是断开连接的客户端的情况导致事件引发在处理其调用列表时抛出异常,因此所有剩余的客户端都不会被调用 - 您可以手动处理调用列表这样的事情
List<Action<string>> targets = _action.GetInvocationList().Cast<Action<string>>().ToList();
foreach(var target in targets)
{
try
{
target(message);
}
catch(CommunicationException)
{
_action -= target;
}
}
编辑(查看代码后)
您使用的是NetTcpBinding,它本身就是sessionful。该会话将在两种情况中的一种情况下被拆除(断开连接) - 当客户端关闭其代理时,或当请求之间超过服务receiveTimeout时。
在您的PubSubService主机中,您将接收超时(影响订阅者会话)设置为5秒,与sendTimeout相同(这会影响您在广播时确定订阅者死亡之前等待的时间)。所以当你意识到用户已经死亡时,所有其他用户都已经超时了他们的会话
将PubSubService主机中的receiveTimeout增加到您希望订阅有效的时间,并且它将正常工作