轉(zhuǎn)帖|其它|編輯:郝浩|2010-11-17 17:24:58.000|閱讀 1047 次
概述:本文主要介紹WCF如何回調(diào)已離線客戶端的異常處理,希望對(duì)大家有幫助。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門(mén)軟控件火熱銷售中 >>
熟悉WCF的朋友應(yīng)該都了解WCF的雙工回調(diào),這里簡(jiǎn)單說(shuō)一下,WCF的服務(wù)開(kāi)放出去后,一旦有客戶端調(diào)用,服務(wù)端便會(huì)保留各個(gè)客戶端的一個(gè)句柄,然后服務(wù)端會(huì)在合適的時(shí)候做遠(yuǎn)程調(diào)用來(lái)給客戶端傳遞一些數(shù)據(jù),這個(gè)類似遠(yuǎn)程事件綁定的機(jī)制非常有用,很多時(shí)候可以避免timer的主動(dòng)請(qǐng)求服務(wù)器,而是由服務(wù)器主動(dòng)推送數(shù)據(jù)給客戶端:
然而,這種機(jī)制,有一個(gè)問(wèn)題,就是當(dāng)client注冊(cè)到服務(wù)器后,client可能會(huì)意外掉線但來(lái)不及通知服務(wù)端,當(dāng)服務(wù)端再試圖回調(diào)此client時(shí),由于回調(diào)句柄無(wú)法找到客戶端實(shí)現(xiàn)而出現(xiàn)異常:
The communication object, System.ServiceModel.Security.SecuritySessionServerSettings+
SecurityReplySessionChannel, cannot be used for communication because it has been Aborted.
大概意思就是連接已經(jīng)終止,無(wú)法使用連接。
為了解決這個(gè)問(wèn)題,我今天晚上特意寫(xiě)了一個(gè)Demo來(lái)測(cè)試,這個(gè)例子是客戶端一旦注冊(cè)到服務(wù)器后,服務(wù)器每隔3秒鐘回調(diào)一次客戶端并傳給客戶端參數(shù):
服務(wù)契約
[ServiceContract(CallbackContract=typeof(IAddServiceCallBack))]
public interface IAddService
{
[OperationContract]
void Login(string name);
}
[ServiceContract]
public interface IAddServiceCallBack
{
[OperationContract(IsOneWay=true)]
void ReturnValue(string returnName);
}
服務(wù)實(shí)現(xiàn)
public class AddService:IAddService
{
public class Client
{
public string username { get; set; }
public IAddServiceCallBack callbackHandler { set; get; }
}
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("服務(wù)端回調(diào):" + m.username));
}
}
catch(Exception ex)
{
string s = ex.Message;
}
}
}
static void RemoveCallBack(string name)
{
if (DIC.ContainsKey(name))
{
DIC.Remove(name);
}
}
為了找出原因,我做了一個(gè)有趣的測(cè)試,將解決方案編譯后,切到windows資源管理器,找到了生成的服務(wù)端和客戶端的控制臺(tái)程序
我開(kāi)啟了一個(gè)服務(wù)實(shí)例和三個(gè)client實(shí)例
我試著將第2個(gè)client直接關(guān)掉,這樣服務(wù)端是不知道2已經(jīng)掉線的,因此在回調(diào)2的時(shí)候會(huì)出現(xiàn)異常,這時(shí)會(huì)出現(xiàn)什么問(wèn)題呢?
如上圖,關(guān)閉2后,最先注冊(cè)到服務(wù)器的客戶端1仍然繼續(xù)被服務(wù)器回調(diào),但是在2后注冊(cè)到服務(wù)器的3停止被回調(diào),于是猜想出異常的那個(gè)客戶端以后的其他客戶端都會(huì)停止掉,而之前的不受影響。為了驗(yàn)證想法,重新做了測(cè)試,關(guān)閉1后,果然2和3都停止了。
問(wèn)題確認(rèn)后,就得有解決辦法,否則留個(gè)異常跟吃個(gè)蒼蠅沒(méi)有什么區(qū)別了。
辦法一,在客戶端的Close或停止的事件中告訴服務(wù)器移除回調(diào)句柄,這個(gè)方法我首先給排除了,關(guān)閉窗口怎么辦?斷電怎么辦…客戶端的路走不通了
辦法二,服務(wù)端監(jiān)控客戶端是否離線,心跳包出場(chǎng),客戶端每5秒鐘想服務(wù)器回發(fā)一次,若服務(wù)器監(jiān)控到某個(gè)客戶端的最后更新時(shí)間比現(xiàn)在大5秒則做離線處理,移除客戶端。
在僅有的辦法里,我選了第二個(gè)辦法,于是在服務(wù)器端加上了
[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);
}
}
}
具體實(shí)現(xiàn)是,當(dāng)客戶端注冊(cè)到服務(wù)器時(shí),服務(wù)器將客戶端添加到一個(gè)字典中,這個(gè)字典中保存有客戶端名稱和添加時(shí)間,以后由客戶端定時(shí)心跳來(lái)更新服務(wù)器上的這個(gè)字典集合,在服務(wù)中會(huì)有一個(gè)定時(shí)器,500毫秒一次去檢測(cè)這個(gè)集合,如果發(fā)現(xiàn)有大于5秒鐘還未更新的客戶端,則從回調(diào)句柄集合中移除,由此避免句柄的調(diào)用異常問(wèn)題。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:網(wǎng)絡(luò)轉(zhuǎn)載