日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

使用 .NET 实现 Ajax 长连接

發(fā)布時間:2023/11/27 生活经验 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用 .NET 实现 Ajax 长连接 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

作者:http://www.cnblogs.com/cathsfz/

?

Ajax的長連接,或者有些人所說的Comet,就是指以XMLHttpRequest的方式連接服務器,連接后服務器并非即時寫入相應并返回。服務器會保持連接并等待一個需要通知客戶端的事件,該事件發(fā)生后馬上將數(shù)據(jù)寫入響應,這時候客戶端就以相當“實時”的方式接收到事件通知。具體的通信模型,請參考這篇文章:《Comet:基于 HTTP 長連接的“服務器推”技術》,里面已經(jīng)說得非常詳細了,我就不再復述了。

我們接著開始討論如何使用.NET實現(xiàn)這個模型。首先我們能想到的是,我們需要一個Web Service,可以是ASP.NET Web Service,也可以是WCF Web Service,ASP.NET AJAX Library兩者都支持。在這里,為了簡單起見,就選擇大家更熟悉的ASP.NET Web Service舉例。然后,我們寫下以下兩個函數(shù)簽名:

public void Send(Message message);
public Message Wait();

其中,Send函數(shù)用來發(fā)送一個Message對象,而Wait函數(shù)用來等待一個Message對象。然后,讓我們來討論一些細節(jié)問題。

無事件導致超時

首先,長期保持連接時不行的。對于服務器和客戶端來說,這不是個問題,但我們永遠都要記住中間可能存在各式各樣配置怪異的網(wǎng)關和代理,它們上面可能有各式各樣的超時規(guī)則,因此Comet最好設計為定期重連。一般情況下,如果30秒沒有任何事件發(fā)生,服務器端就應該通知客戶端確實沒有事件發(fā)生,結束掉本次請求,然后重新開始一次新的請求以便繼續(xù)等待。

那么上述函數(shù)簽名可否用來返回一個無事件的消息呢?這是顯然可以的,我們可以選擇返回null表示無事件,或者返回一個EmptyMessage常量,這視乎我們使用class還是struct來定義Message。(甚至,我們還可以做一個名為NoMessageMessage的Message派生類來做這個事情。)

定義發(fā)送目標

上述函數(shù)簽名確實能用來收發(fā)消息,但是沒指名發(fā)給誰。可能有人會說,發(fā)送給誰可以在Message類里面通過一個屬性來定義啊。但是Wait()方法沒有說明接受方是誰,服務器端依然不知道哪些消息應該讓你接收。

因此,我們引入Channel的概念,Channel使用其名稱來標識,相同名稱的就必然是同一個Channel。在發(fā)送與接受時,通過名稱指定要發(fā)送到哪個Channel,這樣問題就解決了。此時,函數(shù)簽名修改如下:

public void Send(string channelName, Message message);
public Message Wait(string channelName);

可靠的消息隊列

想象一個可能發(fā)生的情況,服務器端向你發(fā)送一個消息,你沒有成功接收,但是服務器端認為發(fā)送了就成功了,消息從隊列刪除了,然后這個消息就永久丟失掉了??赡苡腥藭娬{TCP多么可靠,服務器端發(fā)送的消息如果在TCP的層面發(fā)生問題了,肯定會引發(fā)Socket級別的Exception,這個Exception冒泡上來,服務器端就能截獲,從而得知發(fā)送失敗,然后先不刪除隊首消息。可是別忘了,中間是可能存在代理的,如果代理成功把消息收回去了,可是代理發(fā)送到客戶端這一步失敗了,服務器端就不一定會發(fā)生異常了。

因此,我們需要制定一種策略,來確保下行消息總能發(fā)送到客戶端。在這里,我們選擇了引入逐個ACK的機制,來確認消息的接收。也就是說,服務器端發(fā)送給客戶端的消息帶有一個序號,在客戶端收到消息后就將該序號發(fā)回給服務器端,已確認它受到了該消息。這時候,函數(shù)簽名更改如下:

public int Send(string channelName, Message message);
public Message Wait(string channelName, int sequence);

我們使用Wait()接收到的Message中,應該有一個Sequence的屬性,標記它的序號。然后,再我們執(zhí)行下一次Wait()時就將該序號加1的值通過sequence參數(shù)傳遞回去,讓服務器知道我們期望下一條消息的編號是這個。例如我們收到Message,其Sequence屬性為836,那么下一次調用Wait()的時候就傳給服務器837。服務器端此時應該保留了編號為836的Message在對首,如果客戶端繼續(xù)請求836號消息,證明它上次沒收到,這次仍然發(fā)送836號消息給它;如果客戶端請求837號消息,證明它成功收到836號消息的,這次就發(fā)送837號消息給它。

如果都不是,那該怎么辦?那意味著,這是一個錯誤的請求,甚至可能是攻擊請求,因為正常情況下不應該出現(xiàn)這樣的請求的,服務器端可以考慮拋個無關緊要的Exception(不要告訴攻擊者你知道他在攻擊了),甚至直接給個400 (bad request)的響應代號。

與Wait()類似的,Send()也可以加入ACK機制,只需要將返回類型從void改為int就可以了,這個值就專門用于傳遞消息編號,實現(xiàn)方式和Wait()是一樣的,不過Send()是由客戶端保存待發(fā)送消息的隊列。

小結

到此為止。我們的Web Service就寫好了。這就寫好了?只有簽名沒有函數(shù)體?是的,復雜的工作留給model去做,Web Service在這里只是相當于一個view,用于將model的接口暴露出來。

在下一次的文章中,我們將開始討論如何實現(xiàn)服務器端的消息傳遞機制。

?

在上一次的文章中,我們說到了如何設計一個ASP.NET Web Service來處理長連接請求。很多人對此就提出了問題,如何hold住請求讓它30秒不斷開了?這其實很簡單,只需要Sleep()一下就可以了:

Thread.Sleep(30 * 1000);

然而問題是,我們不是要等30秒然后看看是否有事件需要返回,而是在這30秒內(nèi)隨時有事件隨時返回。因此,我們需要一套機制來在等待的過程中檢查是否有事件發(fā)生了。

Monitor模型

在.NET里面,大家最熟悉的線程同步模型應該就是Monitor模型了。沒聽說過?就是C#的那個lock關鍵字,實際上它編譯出來就是一對Monitor.Enter()和Monitor.Exit()。

通過lock命令,我們可以針對一個對象創(chuàng)建一個臨界區(qū),代碼執(zhí)行到臨界區(qū)入口時必須獲取到該對象的鎖才能執(zhí)行下去,并且在臨界區(qū)的出口釋放該鎖。然而這種模型不太適用于解決我們的問題,因為我們需要等待一個事件,如果使用lock來等待的話,那就是說要先在Web Service外部把對象鎖上,然后等事件觸發(fā)了就解鎖,這時候Web Service才順利進入臨界區(qū)域。

事實上,要進行這類型的阻塞,還有一個更好的選擇,那就是Mutex。

Mutex模型

Mutex,也就是mutual exclusive的縮寫,“互斥”的意思。Mutex是如何運作的?這有點像是銀行的排隊叫號系統(tǒng),所有等待服務的人都坐在大廳里等候(wait)被叫,當一個服務窗口空閑時它就會發(fā)出一個信號(signal)來通知下一位等候服務的人??傊?#xff0c;所有執(zhí)行wait指令的線程都在等候,而每一個signal能夠讓一個線程結束等候繼續(xù)執(zhí)行。

在.NET里面,wait和signal這兩個操作分別對應Mutex.WaitOne()和Mutex.ReleaseMutex()這兩個方法。我們可以讓Web Service的線程使用Mutex.WaitOne()進入等候狀態(tài),而在事件發(fā)生時使用Mutex.ReleaseMutex()來通知Web Service線程。因為必須在Mutex.ReleaseMutex()發(fā)生后Mutex.WaitOne()才可能繼續(xù)執(zhí)行下去,因此能夠執(zhí)行下去就證明必然有事件發(fā)生了并且調用了Mutex.ReleaseMutext(),這時候就可以放心地去讀取事件消息了。

簡單示例

在選定使用Mutex模型后,我們來編寫一個簡單的示例。首先,我們要在WebService派生類內(nèi)定義一個Mutex,還有一個代表消息的字符串。

Mutex mutex = new Mutex();
string message;

然后,我們定義兩個WebMethod。為了把問題簡單化,我們選用上一篇文章中開頭所說的兩個函數(shù)簽名,也就說只能在一個Web Service內(nèi)自己發(fā)自己收,沒有發(fā)送目標的概念,也沒有超時的概念,還沒有可靠性設計。同時,我們將Message類型替換為普通字符串,以便于我們測試。

我們先編寫發(fā)送消息的函數(shù):

public void Send(string message) {
? this.message = message;
? this.mutex.ReleaseMutex();
}

在這個發(fā)送函數(shù)里,首先我們把消息放進了類內(nèi)全局的變量中,然后讓全局的Mutex類釋放一個signal。這時候,如果有線程在等待,它可以馬上執(zhí)行下去。如果此時沒有線程在等待,那么下一個wait的線程執(zhí)行到該阻塞的地方就能夠不受阻塞繼續(xù)執(zhí)行下去。

現(xiàn)在我們來編寫接收消息的函數(shù):

public string Wait() {
? this.mutex.WaitOne();
? return this.message;
}

接收函數(shù)一開始就進入wait狀態(tài)。在得到signal后,需要做的事情就是把全局的消息返回給客戶端。

親身體驗

最后,我們可以通過ASP.NET Web Service本身支持的Web測試界面來測試一下我們的代碼。我們開兩個瀏覽器窗口,一個進入Send()調用,一個進入Wait()調用。然后我們按照如下方法來測試:

  1. 首先執(zhí)行Send("Hello"),然后執(zhí)行Wait()。這時候你可以馬上看到"Hello"。
  2. 首先執(zhí)行Wait(),讓它等待返回,這時候執(zhí)行Send("Hello")。隨后你可以看到Wait()那段返回"Hello"了。
  3. 按如下順序執(zhí)行:Send("Hello");Wait();Send("World");Wait();
  4. 按如下順序執(zhí)行:Send("Hello");Send("World");Wait();Wait();
  5. 按如下順序執(zhí)行:Wait();Wait();Send("Hello");Send("World");
  6. 按如下順序執(zhí)行:Wait();Send("Hello");Wait();Send("World");

你會發(fā)現(xiàn)這樣一些奇怪的結果:第3個測試返回的是"World"和"World"。第5個測試先返回"Hello"的并不一定是先執(zhí)行的那個Wait()線程。后者在某些情況下不是什么問題,特別是長連接中一般之后一個Wait()線程在等待中,所以我們可以不管。而前者,則是因為沒有消息隊列所造成的,我們只有長度為1的消息窗口,所以只能緩存最后一個消息。這個問題我們將在下一篇文章中解決。

小結

在本文中,我們看到了不同的線程同步模型的差異。Monitor模型的lock本質上是一個Semaphore,也就是一個不能連續(xù)signal的Mutex,一個signal發(fā)出去后必須被一個wait接收了才能進行下一次的signal。同時,Semaphore也限制了signal和wait必須在同一個線程內(nèi)成對執(zhí)行,而Mutex則沒有此限制。雖然.NET是針對Monitor模型優(yōu)化的,但在我們的需求當中,只能通過Mutex模型來解決。

接著,我們便寫了一個小小的消協(xié)發(fā)送與接收函數(shù),實現(xiàn)了我們想要的阻塞式Web Service。同時我們也看到了沒有消息隊列造成的問題,因此確定接下來我們要做一個消息隊列。

總結

以上是生活随笔為你收集整理的使用 .NET 实现 Ajax 长连接的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。