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来区分不同的用户

asp.net 中长尾链接实现推送 -- comet
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();

        }

    }

}
View Code
asp.net 中长尾链接实现推送 -- comet
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; }

        }

    }

}
View Code
asp.net 中长尾链接实现推送 -- comet
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);

                }

            }

        }

    }

}
View Code

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

asp.net 中长尾链接实现推送 -- comet
    $(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("");

        });



    });
View Code

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

asp.net 中长尾链接实现推送 -- comet
 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);

                }

            }

                

        }
View Code

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个图片

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

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

你可能感兴趣的:(asp.net)