asp.net 中长尾链接实现推送 -- comet

一般需求推送服务时,都会去第三方拿推送组件,如”极光“,”百度“,”小米"什么的,自己用.net实现推送服务端需要面对很多问题,比如C10K,但是企业内部使用往往用不了10K的链接,有个1K,2K就足够,这个时候完全可以自己实现一个推送服务,这样手机应用就不用走外网了。

使用.net实现推送服务有几个选择

1.是使用WCF 基于TCP的回调-针对.net To .net 端,经过7*24小时测试,2K左右的链接能稳定hold住,参考:http://www.cnblogs.com/wdfrog/p/3924718.html

2.自己使用TcpListener或Socket实现一个长连击服务器,由于推送的信息都很短(长信息只推送信息编号),这样在一个MTU里面就可以完成传输,整体上实现起来也不麻烦 ,最近也做了个,大概加一起就400百来行代码,2K链接已经(实际只要hold住98个用户就好了)稳定运行了6天了。

3.使用Comet Request, 首先Comet Request 采用Asp.net + IIS,整整网页就好了,另外由于IIS应用程序池会定期回收,程序写的烂点也不影响,每天都给你一个新的开始,还有Comet 的实现网上代码很多,还有开源的SignalR可以用.

采用Comet方式的实现

1.服务端,使用IHttpAsyncHandler来Hold住请求,需要根据cookie或QueryString中带的UserId来区分不同的用户

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MangoPush.WebComet.Core
{
    public class PushAsyncHandle : IHttpAsyncHandler
    {
        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {

            var ar = new PushAsyncResult(context, cb, extraData);
            CometRequestMgr.Instance.Add(ar);
            return ar;
        }

        public void EndProcessRequest(IAsyncResult result)
        {

        }

        public bool IsReusable
        {
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            throw new NotImplementedException();
        }
    }
}

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

namespace MangoPush.WebComet.Core
{
    public class PushAsyncResult:IAsyncResult
    {
        private static int GId = 0;

        private bool m_IsCompleted = false;
        private AsyncCallback Callback = null;
        public HttpContext Context;
        private Object _AsyncState;
        public DateTime AddTime{get;private set;}
        public int Id { get; set; }

        public PushAsyncResult(HttpContext context, AsyncCallback callback, object asyncState)
        {
            Context = context;
            Callback = callback;
            _AsyncState = asyncState;
            AddTime = DateTime.Now;
            Interlocked.Increment(ref GId);
            int userId = int.TryParse(Context.Request["UserId"], out userId) ? userId : 0;
            Id = userId;
            if (userId == 0)
            {
                Release(JUtil.ToJson(new {Code=-1,Msg="未提供UserId" }));
            }
        }
        public void Release(string msg)
        {
            try
            {
                try
                {
                    Context.Response.Write(msg);
                }
                catch { }
                if (Callback != null)
                {
                    Callback(this);
                }
            }
            catch { }
            finally
            {
                m_IsCompleted = true;
            }
        }
        public object AsyncState
        {
            get { return _AsyncState; }
        }

        public System.Threading.WaitHandle AsyncWaitHandle
        {
            get { return null; }
        }

        public bool CompletedSynchronously
        {
            get { return false; }
        }

        public bool IsCompleted
        {
            get { return m_IsCompleted; }
        }
    }
}

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Web;

namespace MangoPush.WebComet.Core
{
    public class CometRequestMgr
    {

        public static readonly CometRequestMgr Instance = new CometRequestMgr();
        private ConcurrentDictionary<int, PushAsyncResult> Queue = new ConcurrentDictionary<int, PushAsyncResult>();

        private CometRequestMgr()
        {
            var timer = new System.Timers.Timer();
            timer.Interval = 1000 * 30;
            timer.AutoReset = false;
            timer.Elapsed += (s, e) =>
            {

                try
                {

                    var list = Queue.Select(ent => ent.Value).ToList();
                    #region 清理完成链接
                    foreach (var it in list)
                    {
                        if (it.IsCompleted)
                        {
                            try
                            {
                                PushAsyncResult c = null;
                                Queue.TryRemove(it.Id,out c);

                            }
                            catch (Exception ex)
                            {

                                continue;
                            }
                        }
                    }
                    #endregion

                }
                catch (Exception ex)
                {

                }
                finally
                {
                    timer.Start();
                }

            };
            timer.Start();

        }

        public void Add(PushAsyncResult ar)
        {
            Queue[ar.Id] = ar;
        }
        public void BroadCastMsg(string msg)
        {
            msg +="," + DateTime.Now.ToString();
            foreach (var c in Queue)
            {
                try
                {

                    c.Value.Release(JUtil.ToJson( new {Code=0,Msg= msg}));

                }
                catch { }
                finally
                {
                    PushAsyncResult outIt=null;
                    Queue.TryRemove(c.Key,out outIt);
                }
            }
        }
    }
}

2.网页端,使用ajax方式发起访问,特别注意的是需要设置timeout,这样如果断线或者服务端挂了重启,可以自动修复

    $(function () {

        function long_polling() {
            var _url = ‘/pushService.act?userId=‘ + $("#userId").val();
            console.log(_url);

            var ajaxRequest = $.ajax({
                url: _url,  //请求的URL
                timeout: 1000 * 60 * 3, //超时时间设置,单位毫秒
                type: ‘get‘,  //请求方式,get或post
                data: {},  //请求所传参数,json格式
                dataType: ‘json‘, //返回的数据格式
                success: function (data) { //请求成功的回调函数

                    console.log(data);
                    if (data.Code == 0) {
                        $(‘#msg‘).append(data.Msg + "<br/>");
                    }
                },
                complete: function (XMLHttpRequest, status) { //请求完成后最终执行参数
                    if (status == ‘timeout‘) {//超时,status还有success,error等值的情况
                        ajaxRequest.abort();
                        console.log("超时");
                    }

                    if ($("#btn").val() == "停止") {
                        long_polling();
                    }
                }
            });

        }
        $("#btn").click(function () {

            if ($("#btn").val() == "启动") {
                long_polling();
                $("#btn").val("停止");
            } else {
                $("#btn").val("启动");
            }
        });

        $("#btnCls").click(function () {
            $("#msg").text("");
        });

    });

3.Winform端,采用WebClient发起请求,并且使用AutoResetEvent控制超时重连(相当于心跳包)

 private void Do()
        {
            try
            {

                while (Enable)
                {
                    Console.WriteLine("发起请求!");
                    var url = @"http://192.168.9.6:9866/pushService.act?userId=18";
                    using (var wc = new WebClient())
                    {
                        wc.Encoding = Encoding.UTF8;
                        #region 回调处理
                        wc.DownloadStringCompleted += (s, e) => {

                            if (e.Error != null)
                            {
                                Console.WriteLine(e.Error);
                            }
                            else if (e.Cancelled)
                            {
                                Console.WriteLine("Be Cancelled!");
                            }
                            else
                            {
                                Console.WriteLine(e.Result);

                            }
                            autoReset.Set();
                        };
                       #endregion
                        var uri = new Uri(url);
                        wc.DownloadStringAsync(uri);

                        var isOK= autoReset.WaitOne(1000 * 60 * 5);
                        if (!isOK)
                        {
                            wc.CancelAsync();
                        }

                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("错误" + ex.Message);
            }
            finally
            {
                if (Enable)
                {
                    ThreadPool.QueueUserWorkItem(o => { Do(); }, null);
                }
            }

        }

4.Android端

1.类似WinForm端,目前采用AsyncHttpClient,写法跟Js差不多,需要设置timeout

2.由于android会回收进程,需要AlarmManager,定期检查推送服务是否还存活

3.android系统重启,开关网络,调整时间,需要订阅相应广播,调整AlermManager,触发平率。

总结:

整个能支持10K,100K,2000K链接的,挺不容易,但是一般中小企业使用1K,2K甚至0.1K足矣,3,4百行代码就完事,至少可以让员工不用连3G,4G了,NND,提速降价,也不知道降那去了。

最后整2个图片

时间: 2024-10-11 08:52:35

asp.net 中长尾链接实现推送 -- comet的相关文章

在 Asp.NET MVC 中使用 SignalR 实现推送功能 [转]

在 Asp.NET MVC 中使用 SignalR 实现推送功能 罗朝辉 ( http://blog.csdn.net/kesalin ) CC许可,转载请注明出处 一,简介 Signal 是微软支持的一个运行在 Dot NET 平台上的 html websocket 框架.它出现的主要目的是实现服务器主动推送(Push)消息到客户端页面,这样客户端就不必重新发送请求或使用轮询技术来获取消息. 可访问其官方网站:https://github.com/SignalR/ 获取更多资讯. 二,实现机制

Asp.NET MVC3 使用 SignalR 实现推送

一,简介 Signal 是微软支持的一个运行在 Dot NET 平台上的 html websocket 框架.它出现的主要目的是实现服务器主动推送(Push)消息到客户端页面,这样客户端就不必重新发送请求或使用轮询技术来获取消息. 二,实现机制 SignalR 的实现机制与 .NET WCF 或 Remoting 是相似的,都是使用远程代理来实现.在具体使用上,有两种不同目的的接口:PersistentConnection 和 Hubs,其中 PersistentConnection 是实现了长

Asp.NET MVC3 使用 SignalR 实现推送(接上)

一,Persistent Connection 示例教程 1,实现服务器端代码 1),编写服务器 PersistentConnection 代码 项目中 SignalR 目录下创建 PersistentConnection.cs 文件 using System; using System.Collections.Generic; using System.Threading.Tasks; using SignalR; namespace SignalTutorial.SignalR { publ

Asp.NET MVC 使用 SignalR 实现推送功能二(Hubs 在线聊天室 获取保存用户信息)

简单介绍 关于SignalR的简单实用 请参考 Asp.NET MVC 使用 SignalR 实现推送功能一(Hubs 在线聊天室) 在上一篇中,我们只是介绍了简单的消息推送,今天我们来修改一下,实现保存消息,历史消息和用户在线 由于,我这是在一个项目([无私分享:从入门到精通ASP.NET MVC]从0开始,一起搭框架.做项目 目录索引)的基础上做的,所以使用到的一些借口和数据表,不详细解析,只是介绍一下思路和实现方式,供大家参考 用户登录注册信息 当用户登录之后,我们注册一下用户的信息,我们

Asp.NET MVC 中使用 SignalR 实现推送功能

一,简介 Signal 是微软支持的一个运行在 Dot NET 平台上的 html websocket 框架.它出现的主要目的是实现服务器主动推送(Push)消息到客户端页面,这样客户端就不必重新发送请求或使用轮询技术来获取消息. 可访问其官方网站:https://github.com/SignalR/ 获取更多资讯. 二.Asp.net SignalR 是个什么东东 Asp.net SignalR是微软为实现实时通信的一个类库.一般情况下,SignalR会使用JavaScript的长轮询(lo

在 Asp.NET MVC 中使用 SignalR 实现推送功能

一,简介 Signal 是微软支持的一个运行在 Dot NET 平台上的 html websocket 框架.它出现的主要目的是实现服务器主动推送(Push)消息到客户端页面,这样客户端就不必重新发送请求或使用轮询技术来获取消息. 二,实现机制 SignalR 的实现机制与 .NET WCF 或 Remoting 是相似的,都是使用远程代理来实现.在具体使用上,有两种不同目的的接口:PersistentConnection 和 Hubs,其中 PersistentConnection 是实现了长

百度链接主动推送代码对在织梦系统中的添加方法

百度站长推出的一个链接提交的工具,支持网站链接的主动推送,在这里面我们有一段织梦系统的测试代码分享给大家,让大家可以在不用谢代码的直接复制皆可以解决.在dede后台的article_add.php文件进行简单的修改,在259行加入如下代码(代码进行简单修改,$url[]=之后单引号内的网址改成自己的站点,$api后引号内的链接修改成,自己站长平台里的主东推送链接). else{$urls[]='https://www.jianzhumuju.com/'.$artUrl;$api = 'http:

AngularJS+ASP.NET MVC+SignalR实现消息推送

原文:http://www.mincoder.com/article/4565.shtml 背景 OA管理系统中,员工提交申请单,消息实时通知到相关人员及时进行审批,审批之后将结果推送给用户. 技术选择 最开始发现的是firebase,于是很兴奋的开始倒腾起来.firebase用 起来倒是简单:引用一个js即可,按官网上的教程很快便应用到了项目中.第二天打开项目发现推送功能不好使了,这是为何?最后发现firebase官网打 不开了...难道firebase被google收了也会被天朝给墙掉?也许

Asp.NET MVC 使用 SignalR 实现推送功能一(Hubs)

简介 ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信.什么是实时通信的Web呢?就是让客户端(Web页面)和服务器端可以互相通知消息及调用方法,当然这是实时操作的. WebSockets是HTML5提供的新的API,可以在Web网页与服务器端间建立Socket连接,当WebSockets可用时(即浏览器支持Html5)SignalR使用WebSockets,当不支持时SignalR将使用其它技术来保证达到相同效果. Sign