【问题标题】:WCF oneway exception faults channelWCF 单向异常故障通道
【发布时间】:2016-03-20 23:38:12
【问题描述】:

我还没有找到明确的答案。所以如果已经有关于这个的问题,我的错。

我有一个 WCF 服务,它通过回调方法将数据推送到连接的客户端。这个回调方法是单向的。所以每次有新数据时,我都会遍历连接的用户并推送数据。 我现在遇到的问题是当客户端断开连接时会引发错误并且通道出现故障。 我一直认为 oneway 不在乎消息是否到达目的地。所以如果没有客户,那么运气不好。但也不例外。 但是有一个异常,并且该异常使通道出错。

现在我在某处读到,如果您启用可靠会话,则异常不会导致通道出错。这是真的? 当单向调用发生异常时,如何防止通道进入故障状态?

【问题讨论】:

    标签: wcf oneway


    【解决方案1】:

    您可以存储在某些资源(例如 List)中的已注册和可用客户端的列表。
    创建另一个公开 Connect/Disconnect 方法的接口。当应用程序启动并在方法中将客户端添加到列表中时,将调用 Connect。当应用程序关闭时会依次调用断开连接以摆脱列表的客户端。 OnStartup/OnClosing 事件或其等价物,取决于应用程序客户端的类型,指的是应用程序启动和关闭的时刻。这样的解决方案可确保资源存储只有可访问的用户。

    [ServiceContract]
    interface IConnection
    {
        [OperationContract(IsOneWay = true)]
        void Connect();
    
        [OperationContract(IsOneWay = true)]
        void Disconnect();
    }
    
    [ServiceContract]
    interface IServiceCallback
    {
        [OperationContract(IsOneWay = true)]
        void CallbackMethod();
    }
    
    [ServiceContract(CallbackContract = typeof(IServiceCallback))]
    interface IService
    {
        [OperationContract]
        void DoSth();
    }
    
    class YourService : IConnection, IService
    {
        private static readonly List<IServiceCallback> Clients = new List<IServiceCallback>();
    
        public void Connect()
        {
            var newClient = OperationContext.Current.GetCallbackChannel<IServiceCallback>();
            if (Clients.All(client => client != newClient))
                Clients.Add(newClient);
        }
    
        public void Disconnect()
        {
            var client = OperationContext.Current.GetCallbackChannel<IServiceCallback>();
            if (Clients.Any(cl => cl == client))
                Clients.Remove(client);
        }
    
        public void DoSth()
        {
            foreach(var client in Clients)
                client.CallbackMethod();
        }
    }
    

    最后,使用 IConnection 公开另一个端点,以便客户端可以创建仅用于连接/断开连接的代理。

    编辑:

    我知道自从我发布 answear 以来已经有一段时间了,但我没有找到以准备一个示例。解决方法是让服务的接口派生 IConnection,然后仅将服务公开为端点。我附上 WCF 和 WPF 应用程序的简单示例作为客户端。客户端的应用程序违反了 MVVM 模式,但在这种情况下它是无关紧要的。下载here.

    【讨论】:

    • 谢谢。将尝试此解决方案。看起来不错。尤其是在接口中将连接/断开与“DoStuff”操作分开。
    • 刚刚尝试过,但失败了。因为当您连接时,您使用合同 IConnection 并且该合同没有回调接口。所以 OperationContext.Current.GetCallbackChannel();抛出 InvalidCastException
    • 我没有测试它,但最简单的解决方法是在 IService 中包含 Connect/Disconnect。我将对此进行深入研究,以便提供一些更清洁的解决方案,并具有更好的分离效果。
    【解决方案2】:

    补充一下马克西姆斯所说的话。

    我在一个类中实现了这种模式,在该类中,客户端可以订阅以获取系统内部状态的更新,因此监控客户端可以显示图形,而其他客户端可以执行其他操作,例如在某些状态处于活动状态时启用/禁用按钮。 当故障通道发生故障时,它会从列表中删除故障通道。当客户端连接时,所有当前状态也会发送。

    这是代码,希望对你有帮助!

    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class Publish : IPublish
    {
        private struct SystemState
        {
            public string State;
            public string ExtraInfo;
        }
    
        private static Dictionary<Key<string>, IPublishCallback> mCallbacks = new Dictionary<Key<string>, IPublishCallback>();
    
        private static Dictionary<string, SystemState> mStates = new Dictionary<string, SystemState>();
    
        public void RegisterClient(string name, string system)
        {
            lock (mCallbacks)
            {
                IPublishCallback callback = OperationContext.Current.GetCallbackChannel<IPublishCallback>();
    
                Key<string> key = new Key<string>(name, system);
    
                if (!mCallbacks.ContainsKey(key))
                {
                    mCallbacks.Add(key, callback);
                }
                else
                {
                    mCallbacks[key] = callback;
                }
    
                foreach (KeyValuePair<string, SystemState> s in mStates)
                {
                    mCallbacks[key].ServiceCallback(s.Key, s.Value.State, s.Value.ExtraInfo);
                }
            }
        }
    
        public void UnregisterClient(string name)
        {
            lock (mCallbacks)
            {
                outer:  foreach (var key in mCallbacks.Keys)
                {
                    if (key.Key1 == name)
                    {
                        mCallbacks.Remove(key);
                        goto outer;
                    }
                }
            }
        }
    
        public void SetState(string system, string state, string extraInfo)
        {
            lock (mCallbacks)
            {
                List<Key<string>> toRemove = new List<Key<string>>();
    
                SystemState s = new SystemState() { State = state, ExtraInfo = extraInfo };
                SystemState systemState;
                if (!mStates.TryGetValue(system, out systemState))
                    mStates.Add(system, s);
                else
                    mStates[system] = s;
    
                foreach (KeyValuePair<Key<string>, IPublishCallback> callback in mCallbacks)
                {
                    try
                    {
                        callback.Value.ServiceCallback(system, state, extraInfo);
                    }
                    catch (CommunicationException ex)
                    {
                        toRemove.Add(new Key<string>(callback.Key.Key1, callback.Key.Key2));
                    }
                    catch
                    {
                        toRemove.Add(new Key<string>(callback.Key.Key1, callback.Key.Key2));
                    }
                }
    
                foreach (Key<string> key in toRemove)
                    mCallbacks.Remove(key);
            }
        }
    }
    

    【讨论】: