說明
如果你已經閱讀了我之前的一篇文章《Asp.net構建可擴充的的Comet Web 應用程式》。你應該能夠理解我將要寫的內容。我解釋了Comet技術並且解釋了怎樣用asp.net構建具有可擴充性的應用。然而,我認為之前的的一篇文章寫得有點像主線。它展示了足夠的技術,但是沒有足夠包含任何有用的代碼。因此,我想我需要寫一個API來將之前一篇文章中的功能封裝起來。封裝為一系列整齊的類,讓它們可以被包含到一個通常的web項目中,給你機會去擴充和測試它。
我將不涉及太多關於執行緒模式的具體細節。因為在之前的一篇文章中涉及了太多關於它的內容。我僅僅講解涉及到API並且怎樣在你的web應用程式中使用它。
我決定寫一個輕量級的發送訊息的API,它類似於Bayeuxprotocol【Bayeux是在和web伺服器間提供低時延、傳輸非同步訊息的協議(主要基於HTTP)】交換資訊的方式。但是,它沒有它沒有一個基於改協議的實現當我相信它能夠矯枉過正,為了那些被用來需要用這些API工作的需求。並且它也僅僅只是一個草案。
我的原文將我會給出一個小遊戲。不巧的是,我覺得用一個簡單的聊天程式來說明它可能更加容易。這個程式使用一個Comet通道來接受資訊,用一個WCF服務來發送資訊。
基本的聊天程式:
術語表
下面是我這篇文章中使用的列表,以及他們的描述。
通道:這是一個Comet用戶端能夠串連到的結束點。任何發送到用戶端的資訊必須被轉交給通道傳輸。
逾時:當一個用戶端已經串連到一個通道,並且過了預定義的一段時間內,還沒有收到任何的訊息。當逾時的時候用戶端可以重建立立串連。
閑置用戶端:這是一個用戶端沒有串連到伺服器的時限。一個閑置的用戶端在一個預設的時間後將被斷開。
訊息:通過一個通道發送給一個用戶端的JSON格式的訊息。
訂閱:一個用戶端被訂閱到一個通道。他們被串連並且準備接受訊息。
項目的代碼
項目的程式碼封裝含所有的能夠在你的asp.net項目中使用Comet類。代碼和原文中設計的代碼非常接近。但我擴充了功能,使得能夠在用戶端和服務端傳輸通常的訊息。
控制Comet主要的類是CometStateManager。這個類管理單個的通道。這個類使用ICometStateProvider介面的一個執行個體以一種特別的方式來為你的應用程式管理狀態。在API中,有一個內建InProcCometStateProvider的實現,用來在伺服器的記憶體中儲存狀態。很顯然,這不是實現負載平衡環境的一個好做法。但你可以實現一個自訂的提供者(provider),使用資料庫或者一個自訂的狀態伺服器。
為了向外部暴露你的通道,需要用一個IHttpAsyncHandler的實現來封裝它。我最終試圖使用一個WCF的非同步模型。但發現它不會釋放asp.net背景工作執行緒,如同使用非同步handler一樣。這有點可惜,這並不是期望的。
下面的代碼展示了你應該怎樣去建立一個IHttpAsyncHandler來為你的Comet通道提供一個結束點。
上面的代碼足夠簡單。我們有一個CometStateManager類的靜態執行個體。它被用來構建ICometStateProvider的執行個體。在這個例子中,我們使用一個內建的InProcCometStateProvider的實現。
這個類其餘的實現是簡單得將BeginProcessRequest和EndProcessRequest方法映射到CometStateManager 類執行個體的BeginSubscribe和EndSubscribe方法。我們也需要在web.config中配置handler才能使用它。
這個通道現在已經可以被客訂閱了
CometClient類
通道需要與用戶端保持串連。每一個用戶端都代表一些被CometClient的執行個體排序的緩衝。我們不希望任何舊的用戶端串連著伺服器或者任何沒有被認證的用戶端註冊通道。所以我們希望實現一種授權和認證機制。也許是asp.net標準的 Form認證,或者也許是一個WCF調用一個服務來驗證憑據,並且在我們的通道中執行個體化一個用戶端。
下面的代碼展示了default.aspx頁面的登入操作:
我們沒有驗證密碼或任何其他的東西,我們只是在頁面上直接輸入使用者名稱,並且用它來區分不同的用戶端。一個Comet用戶端有兩個token供API使用:
PrivateToken這個token是用戶端私人的,被用來註冊訊息到用戶端。
PublicToken這個token被用來區分是哪一個用戶端。它通常在發送訊息到一個特殊的用戶端時被使用。
我們使用一個公用令牌和一個私人令牌的原因是,私人令牌能夠被用來註冊一個通道以及從別的使用者那裡接受訊息。我們不想任何其他的用戶端能夠隔離原本的用戶端(例如我們不希望訊息欺騙)。出於這個原因,如果我們想在兩個用戶端之間發送訊息我們使用公用令牌。
為了簡單起見,我已經在用戶端包含了一個DisplayName的屬性來儲存使用者名稱。
為了在用戶端建立一個通道,你需要調用InitializeClient。在上面有顯示。這個方法攜帶了下面的一些參數:
publicToken – 用戶端公用令牌
privateToken – 用戶端私人令牌
displayName – 用戶端顯示名稱
connectionTimeoutSeconds – 連線逾時時間
connectionIdleSeconds- 在伺服器殺掉一些閑置用戶端從而等待一個用戶端重新串連的秒數。
上面的例子中,InitializeClient
會調用。從表單中指定使用者名稱作為公用令牌,私人令牌以及顯示名稱。這不是非常的安全,但對於一個Demo來說已經足夠了。為了使它更加安全,我本可以產生一個GUID來作為私人令牌。並且使用公用令牌作為使用者名稱。
InitializeClient
將通過ICometStateProvider被調用。一個新的InitializeClient執行個體化了CometClient類。並且期望它被儲存在緩衝中。
隨著CometClient
的可訪問,用戶端可以使用它們自己的私人令牌來訂閱通道。
用戶端 JavaScript
為了實現用戶端的功能,有一個檔案在項目中,Scripts/AspNetComet.js包含了所有的需要訂閱通道的js(以及公有的JSON轉換器)。為了使一切變得簡單一點,我在CometStateManager中包含了一個靜態方法來調用RegisterAspNetCometScripts
。它接受一個帶有參數的頁面並且在頁面上註冊了指令碼。
隨著它被調用,我們就能夠很自由得使用我們能夠使用的用戶端API。下面的例子摘自項目中的chat.aspx。並且展示了一旦一個用戶端被執行個體化,你應該怎樣訂閱一個通常的通道。
用戶端API所有的功能都被包裹到一個被叫做AspNetComet的JavaScript類中。這個類的一個執行個體被用來跟蹤一個已串連的用戶端的狀態。被要求訂閱的是Comet結束端的handler的URL。CometClient的私人令牌,以及一個別名被用來區別用戶端通道。一旦我們構建一個AspNetComet的執行個體,我們可以在Comet的生命週期內定義一系列的handler以供在特殊時刻調用。
addTimeoutHandler–
當一個用戶端等待過了一個預定的事件並且沒有接收到訊息調用該handler。
addFailureHandler–
當一個Comet調用失敗,其中一個失敗的例子就是Comet用戶端沒有被串連,調用該handler。
addSuccessHandler–
每一個訊息被發送到用戶端的時候handler被調用。
接下來的代碼展示了每一個handler方法的簽名:
SuccessHandler
的參數是CometMessage類的一個執行個體。下面的代碼顯示了類和它的JSON格式:
發送一條訊息
在這個聊天的應用程式中,我已經包含了一個能使用AJAX的WCF web 服務來扮演發送訊息功能的結束點。下面的代碼顯示了點擊發送訊息按鈕的用戶端事件的處理器:
這段代碼構建了一個由asp.net Web Service framework建立的 ChatService
用戶端對象的執行個體。然後僅僅調用了SendMessage方法,通過傳遞用戶端的私人令牌和訊息。
SendMessage
代碼攜帶參數以及寫了一條訊息給所有的用戶端。下面的代碼展示了功能:
這個方法從私人令牌中尋找CometClient,並且建立了一個被用來作為訊息內容的ChatMessage的對象。這裡的訊息內容通過CometStateMessager的執行個體的SendMessage方法被發送到每一個串連著的用戶端。它將處罰任何串連著的用戶端來回調包含在chat.aspx頁面裡的SuccessHandler方法。它將資訊寫到頁面的聊天地區。
使用這段代碼
解決方案中的網站執行的時候不需要改變任何的配置,僅僅串連一些用戶端到應用程式中。並且聊天資訊應該被即時發送。
使用這個API將使你能在你的AJAX程式中使用一個Comet風格的方案。使用WCF能使你發送訊息到伺服器,這些都已經為你自動封裝了。然後僅僅只是在一個Comet通道中回調來串連到用戶端。