comet在asp.net中的實現

周見智發表於2014-05-11

      網上有關“伺服器推送”的介紹非常多,其中一種實現方式就是採用comet技術,在瀏覽器與服務端之間建立一個http協議的“長連線”,所謂“長連線”,就是指瀏覽器到服務端的http請求不會馬上得到服務端的應答,而是當滿足一定條件的時候,伺服器端才“主動”將資料返回給瀏覽器,這時候一次http請求才完成,普通http連線與http長連線見下圖:

圖1

如上圖,左邊為一般http連線,服務端收到瀏覽器的http請求後會立即做出應答,右邊為http長連線,服務端收到瀏覽器的http請求後,如果有資料需要返回,則立即返回,否則,服務端會“維持住”這個http請求,也就是說,服務端凍結了該次http請求,直到服務端有資料需要返回給瀏覽器或者超時,服務端才“解凍”該次http請求,這時候,一次“瀏覽器->服務端”的http請求才完整結束。

      “長連線”的作用很明顯,它能讓服務端“主動”(注意這裡的主動加了雙引號)將資料傳送給瀏覽器,是的,你沒聽錯,傳統的Web程式只能是瀏覽器主動請求服務端,服務端再作出應答,而現在,服務端居然可以“主動推送”資料給瀏覽器了。既然服務端現在可以“主動推送”資料給瀏覽器,那麼我們可以完成許多之前不能做到的事情,比如“即時通訊”、“實時監控”等類似客戶端需要實時更新資料的應用程式了。這裡插一句,如果不採用服務端“主動推送”的方式,我們確實可以按照傳統瀏覽器端使用ajax主動迴圈請求服務端,來實現所謂的“實時”更新資料的效果,比如每秒ajax請求服務端,來重新整理介面資料,但是這種方式的缺點可想而知,不管服務端有沒有需更新的資料,瀏覽器都必須不斷地迴圈去請求(有關ajax輪詢的缺點請參考網上其他文章)。

具體實現comet的關鍵有以下幾點:

1)服務端不會立即響應瀏覽器的http請求(除非當時有資料需要返回給瀏覽器);

2)瀏覽器處理完服務端返回的資料後,要立即重新建立一個“http長連線”,供下次使用;

3)瀏覽器在處理服務端返回的資料時(下一次長連線建立之前),如果服務端有新的資料需要傳送給瀏覽器,那麼服務端必須將這些新資料儲存起來,等下次“長連線”建立後,再一起傳送給瀏覽器;

4)要想服務端能夠實時地、不斷地“主動推送”資料到瀏覽器,瀏覽器與服務端之間必須無時無刻保持一條“http通道”(也就是http長連線),服務端能夠藉助該通道將資料返回給瀏覽器;

5)瀏覽器一接收到服務端返回的資料,一次“http長連線”就結束了,需要重新建立下一個。

如果我們以socket程式設計的角度看“http長連線”,它會是這樣的:

圖2

如上圖,每個browser必須時刻存在一個http下行通道(服務端->瀏覽器,圖中(1)處),這樣任何時候服務端都能“主動推送”資料給browser,同時,每個瀏覽器均可以發起其他正常http請求(圖中(2)處),這樣一來,1號瀏覽器通過普通http請求傳送資料(get/post方式)給服務端,服務端就可以立馬將資料“推送”給2號瀏覽器(使用http下行通道),沒錯,這不就是socket程式設計嗎?

        asp.net中可以使用“非同步程式設計模型”(APM)簡單地實現comet,具體涉及到IHttpAsyncHandler介面以及它的BeginProcessRequest和EndProcessRequest兩個方法,當我們接收到來自瀏覽器發起的“http長連線”請求時,我們只呼叫IHttpAsyncHandler.BeginProcessRequest方法去非同步處理,由於我們不立即呼叫IHttpAsyncHandler.EndProcessRequest方法,所以這個http請求不會立馬結束(也就是說,該請求被服務端凍結住了),等服務端有資料時,我們再通過類似Response.Write()方法將資料傳送給瀏覽器,同時呼叫IHttpAsyncHandler.EndProcessRequest方法結束非同步處理http請求,這時候,瀏覽器端會接收到服務端“主動推送”的資料,瀏覽器端一次完整的http請求到此時才結束,緊接著,瀏覽器端通過指令碼再次發起一個“http長連線”的請求,依次迴圈。

      文章最後上傳一個即時通訊的demo,每個登入的使用者都可以傳送訊息,線上使用者均能及時收到服務端“推送”來的資料(包括上線、下線以及訊息內容等),由於服務端傳送的資料種類比較多,我簡單的指定了一個協議(protocol),類似如下:

 1 協議類似socket通訊程式設計中的協議(類似):
 2 [4bytes]+[16bytes]+[some]+[1byte]
 3 訊息型別+訊息長度+訊息內容+驗證位
 4 
 5 (Web Server->Browser方向)json格式資料包 每個包類似C++中的結構體:
 6 
 7 有使用者上線:
 8 {
 9     "type":"login",
10     "login_name":"xiaozhi_5638",
11     "login_time":"2014-04-15 12:34:19"
12 }
13 
14 有使用者傳送訊息:
15 {
16     "type":"sendmsg",
17     "msg":"hello world![emo:23]",
18     "send_name":"xiaozhi_5638",
19     "send_time":"2014-04-15 12:34:19"
20 }
21 
22 有使用者下線:
23 {
24     "type":"logout",
25     "logout_name":"xiaozhi_5638",
26     "logout_time":"2014-04-15 12:34:19"
27 }
28 
29 心跳包:
30 {
31     "type":"heartbeat",
32     "time":"2014-04-15 12:34:19"
33 }
34 
35 自己登入結果:
36 {
37     "type":"login_result",
38     "result":"true",   //or false
39     "online_users":["xiaozhi_5638","somebody","zhangsan"],  //登入成功 返回線上使用者
40     "time":"2014-04-15 12:34:19"
41 }
42 
43 自己傳送訊息結果
44 {
45     "type":"sendmsg_result",
46     "result":"true",  //or  false
47     "msg":"hello world![emo:23]",
48     "time":"2014-04-15 12:34:19"
49 }
50 
51 資料包集合(上面的都是單個資料包,data_list中包含多個資料包集合) 用於一次性將服務端快取的資料包傳遞到browser
52 {
53     "type":"data_list",
54     "list":[{"type":"login","login_name":"xiaozhi_5638","login_time":"2014-04-15 12:34:19"},{"type":"sendmsg","msg":"hello world!","send_name":"xiaozhi_5638","send_time":"2014-04-15 12:34:19"}]  //陣列型別 包含多個資料包
55 }
56 Browser->Web Server方向的資料 以普通get/post方式傳遞
View Code

服務端到瀏覽器端的資料均以json格式傳遞,瀏覽器到服務端採用jquery寫好的ajax庫方式。瀏覽器端的指令碼只需要解析服務端傳遞回來的json資料,然後更新介面,緊接著發起下一個“http長連線”請求。js指令碼發起http長連線程式碼如下:

 1 function Open_Http_Channel()  //開啟一個http通道(http長連線)
 2 {
 3     $.ajax(
 4     {
 5         url:"chat_aspx.ashx",  //action處理頁面
 6         type:"post",  //資料傳遞方式
 7         data:{"requestType":"a_long_connection","id":$("#input_user_name").val()},   //上傳引數
 8         dataType:"json",  //返回資料型別
 9         success:function(data)  //解析返回的json包
10         {
11             //接收web server端返回的資料  開始解析資料 參見protocol.txt
12             if(data.type == "login")  //有人上線
13             {
14                 ShowLogin(data);  //顯示登入資訊
15             }
16             if(data.type == "sendmsg")  //有人傳送訊息
17             {
18                 ShowMsg(data.msg,data.send_time,data.send_name);  //顯示訊息
19             }
20             if(data.type == "logout")  //有人下線
21             {
22                 Logout(data);
23             }
24             if(data.type == "data_list")  //資料包集合
25             {
26                 for(i in data.list)  //遍歷資料包集合
27                 {
28                     if(data.list[i].type == "login")
29                     {
30                         ShowLogin(data.list[i]);   //顯示登入資訊
31                     }
32                     if(data.list[i].type == "sendmsg")
33                     {
34                         ShowMsg(data.list[i].msg,data.list[i].send_time,data.list[i].send_name);  //顯示訊息
35                     }
36                     if(data.list[i].type == "logout")  //下線
37                     {
38                         Logout(data.list[i]);
39                     }
40                     //...
41                 }
42             }
43             //...
44             //...
45             //...定義的其他協議  在此處解析
46             Open_Http_Channel();  //馬上開啟第二次http通道
47         },
48         error:function(xhr,info,obj)
49         {
50             Open_Http_Channel();  //馬上開啟第二次http通道              
51         }
52     });
53 }
View Code

其他具體詳細的說明參見原始碼。效果圖一張圖3(gif表情沒有解析替換,直接顯示的文字)

圖3

原始碼地址:http://files.cnblogs.com/xiaozhi_5638/comet_in_aspnet.rar  vs2008

用到了jquery以及跟它相關的幾個介面庫。注意不要在同一個瀏覽器上登入太多使用者,因為瀏覽器會為每個使用者維持一個http連線,而瀏覽器對http請求數量有限制(筆者用的chrome上限為6個),超過上限的話,之後所有http請求都會被阻塞。

相關文章