• 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!

  • 相关阅读:
    请输入关键字
    如何把心动变成行动
    理解ASP.NET MVC系列之一:ASP.NET MVC基于MVC设计模式
    window.showModalDialog()
    visual studio 2010 winform程序不能添加对system.web的引用[转载]
    理解ASP.NET MVC系列之三:从URL到Route
    Dan计划:重新定义人生的10000个小时
    为Visual Studio添加配色方案
    [转载]用缓存服务器负载均衡 提数据库查询效率
    Json的序列化和反序列化
  • 原文地址:https://www.cnblogs.com/syblogs/p/3085575.html
Copyright © 2020-2023  润新知