zoukankan      html  css  js  c++  java
  • WCF回调已离线客户端的异常处理

    http://www.cnblogs.com/wengyuli/archive/2010/11/17/wcf-duplex-exception-solve.html

    熟悉WCF的朋友应该都了解WCF的双工回调,这里简单说一下,WCF的服务开放出去后,一旦有客户端调用,服务端便会保留各个客户端的一个句柄,然后服务端会在合适的时候做远程调用来给客户端传递一些数据,这个类似远程事件绑定的机制非常有用,很多时候可以避免timer的主动请求服务器,而是由服务器主动推送数据给客户端:

    image

    然而,这种机制,有一个问题,就是当client注册到服务器后,client可能会意外掉线但来不及通知服务端,当服务端再试图回调此client时,由于回调句柄无法找到客户端实现而出现异常:

    The communication object, System.ServiceModel.Security.SecuritySessionServerSettings+SecurityReplySessionChannel, cannot be used for communication because it has been Aborted.

    大概意思就是连接已经终止,无法使用连接。

    为了解决这个问题,我今天晚上特意写了一个Demo来测试,这个例子是客户端一旦注册到服务器后,服务器每隔3秒钟回调一次客户端并传给客户端参数:

    服务契约

    代码
    [ServiceContract(CallbackContract=typeof(IAddServiceCallBack))] 
    public interface IAddService 

    [OperationContract] 
    void Login(string name); 

    [ServiceContract] 
    public interface IAddServiceCallBack 

    [OperationContract(IsOneWay
    =true)] 
    void ReturnValue(string returnName); 
    }
     服务实现 
    代码
    public class AddService:IAddService 

    public class Client 

    public string username { getset; } 
    public IAddServiceCallBack callbackHandler { setget; } 

    static List<Client> list = new List<Client>(); 
    public void Login(string name) 

    if (list.Where(m => m.username == name).Count() == 0

    list.Add(
    new Client() { username=name, callbackHandler=OperationContext.Current.GetCallbackChannel<IAddServiceCallBack>() }); 


    static System.Timers.Timer timer; 
    public static void Start() 

    timer 
    = new System.Timers.Timer(); 
    timer.Interval 
    = 1000
    timer.Elapsed 
    += new System.Timers.ElapsedEventHandler(timer_Elapsed); 
    timer.Start(); 
    }
     
    static void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) 

    try 

    if (DateTime.Now.Second%3 == 0

    list.ForEach(m 
    => m.callbackHandler.ReturnValue("服务端回调:" + m.username)); 


    catch(Exception ex) 

    RemoveCallBack(


    }
    static void RemoveCallBack(string name)
            {
                
    if (DIC.ContainsKey(name))
                {
                    DIC.Remove(name);
                }
            }

     

    为了找出原因,我做了一个有趣的测试,将解决方案编译后,切到windows资源管理器,找到了生成的服务端和客户端的控制台程序

    imageimage

    我开启了一个服务实例和三个client实例

    image

    我试着将第2个client直接关掉,这样服务端是不知道2已经掉线的,因此在回调2的时候会出现异常,这时会出现什么问题呢?

    image

    如上图,关闭2后,最先注册到服务器的客户端1仍然继续被服务器回调,但是在2后注册到服务器的3停止被回调,于是猜想出异常的那个客户端以后的其他客户端都会停止掉,而之前的不受影响。为了验证想法,重新做了测试,关闭1后,果然2和3都停止了。

    问题确认后,就得有解决办法,否则留个异常跟吃个苍蝇没有什么区别了。

    办法一,在客户端的Close或停止的事件中告诉服务器移除回调句柄,这个方法我首先给排除了,关闭窗口怎么办?断电怎么办…客户端的路走不通了

    办法二,服务端监控客户端是否离线,心跳包出场,客户端每5秒钟想服务器回发一次,若服务器监控到某个客户端的最后更新时间比现在大5秒则做离线处理,移除客户端。

    在仅有的办法里,我选了第二个办法,于是在服务器端加上了

    代码
    [OperationContract] 
    void Update(string name);
    static Dictionary<string, DateTime> dicOfOnLine = new Dictionary<string, DateTime>(); 
    static System.Timers.Timer timer1; 
    public static void StartListenClients() 

    timer1 
    = new System.Timers.Timer(); 
    timer1.Interval 
    = 500
    timer1.Elapsed 
    += new System.Timers.ElapsedEventHandler(timer1_Elapsed); 
    timer1.Start(); 

    static void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e) 

    foreach(var item in dicOfOnLine) 

    if (item.Value.AddSeconds(5< DateTime.Now) 

    DIC.ToList().RemoveAll(m 
    => m.Key == item.Key); 


    }
     
    具体实现是,当客户端注册到服务器时,服务器将客户端添加到一个字典中,这个字典中保存有客户端名称和添加时间,以后由客户端定时心跳来更新服务器上的这个字典集合,在服务中会有一个定时器,500毫秒一次去检测这个集合,如果发现有大于5秒钟还未更新的客户端,则从回调句柄集合中移除,由此避免句柄的调用异常问题。 

    客户端定时调用update进行更新。

    经多次测试,这种异常在IIS下捕获不了,会直接导致IIS进程崩溃,Console下异常可以捕获但是服务有停止的可能性。

    源代码:https://files.cnblogs.com/wengyuli/HostTcpTest.rar 

    有朋友提醒可以在循环中设置try catch,代码已经改成

    static void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)

            {

                string myname = string.Empty;

                for (int i = 0; i < DIC.Count;i++)

                {

                    try

                    {

                        myname = DIC.ToList()[i].Key;

                        DIC.ToList()[i].Value.ReturnValue("服务端回调:" + DIC.ToList()[i].Key);

                    }

                    catch

                    {

                        DIC.Remove(myname); 

                    }

                }            

            }  

  • 相关阅读:
    解决xcode5升级后,Undefined symbols for architecture arm64:问题
    第8章 Foundation Kit介绍
    app 之间发送文件 ios
    iphone怎么检测屏幕是否被点亮 (用UIApplication的Delegate)
    CRM下载对象一直处于Wait状态的原因
    错误消息Customer classification does not exist when downloading
    How to resolve error message Distribution channel is not allowed for sales
    ABAP CCDEF, CCIMP, CCMAC, CCAU, CMXXX这些东东是什么鬼
    有了Debug权限就能干坏事?小心了,你的一举一动尽在系统监控中
    SAP GUI和Windows注册表
  • 原文地址:https://www.cnblogs.com/fx2008/p/2278352.html
Copyright © 2011-2022 走看看