asp.net 避免 ajax 定時調用,利用 ashx 實現 long polling (長輪詢)


動機:朋友跟我說他在公司實現了消息提醒機制,我問他是怎么實現的,他說采用定時調用 ajax 的方法來實現。我跟他說我在使用 web qq 時未曾看到系統有定時檢查是否有消息,但奇怪的是只要一有消息就能以最快的速度送達給你(從服務器推送給戶端,不知語義上有沒有說錯,請大家指教)。今天周末,有時間想想簡單地實現這一功能,於是 google 后發現一則 5 分鍾的視頻,很快便了解了原理並用 asp.net 實現這一功能(因為那則視頻是用 php 實現的)。

 先附上原視頻地址:http://www.screenr.com/SNH

由於原理簡單,所以直接貼代碼。

ReqHandler.ashx

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Web;

namespace SimpleAjaxLongPolling
{
    /// <summary>
    /// ReqHandler 的摘要說明
    /// </summary>
    public class ReqHandler : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/plain";
            string baseDir = "~/Data/";
            string fileName = "datasrc.txt";
            string filePath = context.Server.MapPath(baseDir + fileName);

            /*
             * 超時計數器
             */
            byte timeoutCount = 0;

            /*
             * 獲取文件最后修改時間
             */
            long otimestamp = File.GetLastWriteTime(filePath).Ticks;
            long ctimestamp = File.GetLastWriteTime(filePath).Ticks;

            /*
             * 獲取上一次送給客戶端的連接反饋中的文件的最后修改時間,以確定
             * 在新的連接到來時,文件是否有修改過,如果這個參數不為空,且不
             * 為 0(初始值為 0),則說明有過給客戶的連接反饋,就用這個值
             * 來作為有無修改的被比較對象。
             */
            var reqTimeStamp = context.Request.QueryString["ts"];

            if (!string.IsNullOrEmpty(reqTimeStamp) && !reqTimeStamp.Equals("0"))
            {
                otimestamp = Convert.ToInt64(reqTimeStamp);
            }

            /*
             * 核心。如果自上一次連接后,文件未修改過(即有新的內容),且未超
             * 時,此時等待,超時計數器遞增。
             * 循環跳出的條件是:文件有修改(有新的內容)或超時。
             */
            while (ctimestamp <= otimestamp && timeoutCount < 30)
            {
                Thread.Sleep(1000);
                ++timeoutCount;
                ctimestamp = File.GetLastWriteTime(filePath).Ticks;
            }

            var outputMsg = String.Empty;

            // 循環跳出后的操作

            if (timeoutCount < 30)
            {
                /*
                 * 如果未超時,則說明有新的內容
                 */
                var lines = File.ReadAllLines(filePath);
                if (lines.Length > 0)
                {
                    outputMsg = lines[0];
                }
            }

            var output = string.Format("{{'msg':'{0}', 'timestamp':'{1}'}}", outputMsg, ctimestamp);
            context.Response.Write(output);
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

  GetMsg.aspx

    <form id="form1" runat="server">
    <div id="msg_zone">
        <ul id="msg_list">
            <li>等待消息中...</li>
        </ul>
    </div>
    </form>
    <script type="text/javascript">
        var $msgFmtStr = '<li>%1</li>';
        var $timestamp = 0;
        var $timer = null;
        $(document).ready(function () {
            getMsgs();
        });

        function getMsgs() {
            $.ajax({
                type: 'GET',
                async: true,
                cache: false,
                url: 'ReqHandler.ashx',
                data:{ts:$timestamp},
                success: function (data) {
                    var $json = eval('(' + data + ')');
                    if ($json['msg'] != '') {
                        $('ul#msg_list').append($msgFmtStr.replace('%1', $json['msg']));
                    }
                    $timestamp = $json['timestamp'];
                    getMsgs();
                },
                error: function (XMLHttpRequest, textState, error) {
                    $('ul#msg_list').append($msgFmtStr.replace('%1', 'Error:(' + textState + ', ' + error + ')'));
                    setTimeout("getMsgs()", 5000);
                }
            });
        }
    </script>

  附上 demo 下載地址:

http://pan.baidu.com/share/link?shareid=541364&uk=657243248

補充:

針對有些園友對我的代碼提出疑問,我補充以下內容,歡迎大家繼續參與交流,畢竟是第一次認識長連接,也不知道自己了解得對不對,希望得到大牛們指點。

首先請看圖:

每一次調用都會發起一次連接,由於得不到服務器的響應,所以這個連接會一直保持,如圖紅框所示。圖中說明該連接保持了 30 s(因為后台以 30s 作為連接超時),在這 30s 內,該連接會一直阻塞,但客戶端的操作不會有任何阻塞,直到返回,釋放此次連接,轉而執行下一次連接,也就是說再次調用 getMsgs()。但請注意,getMsgs() 不會一直調用,因為調用他是有條件的,

其一:$(document).ready();

其二:發起的連接得到響應,且 Status 是 OK,則立即調用,如果出現錯誤,則會在 10s 后再次調用該函數。

所以不存在客戶端的 CPU 的占用會有 100% 的情況,而且服務器也不會占用太多 CPU,因為有 Sleep。 

請大家提供更好的解決方案,一起學習,ths!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM