Asp.Net实现Http长连接推送

   话说最新帮一个朋友搞智能家居方面的东西,做一个云平台。主要作用手机在局域网外环境时对手机客户端和智能网关中命令的互相转发。

   目前已经有了一个稳定的Socket版本,但是考虑到以后的扩展和性能指标要改成Http长连接形式,这确实是一个很逗逼的方案。

   下面普及一下Http长连接的概念,所谓的Http长连接其实不是指像Socket那样的建立一个连接client端和server端来回传递数据。Http长连接指的是客户端发送给服务器端的Http请求不会马上得到服务器的应答,而是当满足一定条件时服务器才“主动”将数据返回给客户端,这时一次Http请求才算结束。实际应用中为客户端在结束了一个长连接后往往要再次建立一个长连接,也就是客户端到服务器端总是维持一个打开的下行Http通道。

   搞过Socket的同学都知道,Socket通讯中除了有自己的协议以外还要有心跳的命令,以此来保证客户端和服务器端连接的状态。这些本文都不去深究,主要还是说长连接的这个小框架。

  代码是我们最好的伙伴,下面我们结合代码说说这个简单的东西。

  Asp.Net4.0中加入了很多异步特性,其中IHttpAsyncHandler配合IAsyncResult可以很好的解决本文的需求。首先我们定义一个类实现IAsyncResult这个接口

 

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

using log4net;

namespace SM.BIZKeepAliveHttp
{
    /// <summary>
    /// 一个异步会话,会话会被临时缓存
    /// </summary>
    public class HKAsyncRequest : IAsyncResult
    {
        private static readonly ILog logger = LogManager.GetLogger(typeof(HKAsyncRequest));

        public HKAsyncRequest(HttpContext context, AsyncCallback cb, object extraData)
        {
            this.Context = context;
            this.CallBack = cb;
            this.ExtraData = extraData;
        }

        public HttpContext Context
        {
            get;
            set;
        }

        public object ExtraData
        {
            get;
            set;
        }

        public AsyncCallback CallBack
        {
            get;
            set;
        }

        public bool IsCompleted
        {
            get;
            set;
        }


        public object AsyncState
        {
            get;
            set;
        }

        public System.Threading.WaitHandle AsyncWaitHandle
        {
            get;
            set;
        }

        public bool CompletedSynchronously
        {
            get { return false; }
        }

        public void Send(string response) {
            if (String.IsNullOrEmpty(response))
                return;
            try
            {
                this.Context.Response.ContentType = "text/plain";
                this.Context.Response.Write(response);
                if (this.CallBack != null)
                {
                    this.CallBack(this); 
                }
            }
            catch (Exception ex)
            {
                logger.Error("输出到客户端发生错误:" + ex.Message);
            }
            finally 
            {
                IsCompleted = true; 
            }
        }

        public void Send(byte[] b,int offset,int length){
            string str = Func.ByteArrayToHexString(b);
            this.Send(str);
        }

    }
}

   这个类没有什么难的,主要是保存外部传进来的HttpContext、AsyncCallBack和ExtraData,HttpContext用来向Response中写回应,AsyncCallBack用来结束当前Http长连接请求,ExtraData自己该干什么干什么我没有用它。这里需要注意的是这个类中的CompletedSynchronously属性要返回false,不然客户端收不到数据。而且各个属性也别随便返回null,不然在写入Response时会报空指针的错误。

  下面我们看看另一个接口的实现。在项目中新建一个一般处理程序(.ashx)文件。实现IAsyncHandler接口:

 

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

using log4net;
using Newtonsoft.Json.Linq;

namespace SM.BIZKeepAliveHttp
{
    public class Data : IHttpAsyncHandler
    {

        public static readonly string DATAFIELD = "data";
        private static readonly ILog logger = LogManager.GetLogger(typeof(Data));

        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {
            context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            string value = context.Request.Params.Get(DATAFIELD);

            //这里传过来的是SessionId,不是数据,数据不做重复Parse
            //用sessionId去缓存中找对应的会话,并填充异步AsyncResult
            HKAsyncRequest result = new HKAsyncRequest(context, cb, extraData);
            string error = null;
            if (String.IsNullOrEmpty(value))
            {
                error = "500 SessionId is null";
                context.Response.StatusCode = 500;
                logger.Error(error);
                result.Send(error);
                return result;
            }

            List<AliveClient> acs = AsyncManager.Sessions.FindAll(x => x.SessionId.Equals(value));
            if (acs == null || acs.Count == 0)
            {
                error = "404 SessionId:" + value + " has no connection.";
                context.Response.StatusCode = 404;
                logger.Debug(error);
                result.Send(error);
                return result;
            }

            AliveClient ac = acs.First();
            ac.Result = result;
            //执行命令
            CommondFactory.ExecuteCommond(ac);

            return result;
        }

        public void EndProcessRequest(IAsyncResult result)
        {
        }


        public void ProcessRequest(HttpContext context)
        {
        }

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

  这个类中主要实现的方法只有一个,那就是BeginProcessRequest,其他方法不用写任何代码。这个方法主要作用是建立一个IAsyncResult实例后保存起来,便于以后服务器端有了数据或是满足了特定情况把数据返回给客户端。所以我在代码里面建立了一个静态List的缓存保存这些IAsyncResult实现。当然这就是Asp.Net实现Http长连接的核心所在了。
  其它的就不多说了,大家可以看源代码,看代码时大家会发现我实际建立了两个.ashx文件,这和我这个项目的逻辑有关,因为协议规定客户端发送一条数据后服务器端马上要做出回应,所以我用一个传统的ashx作回应,回应前这个传统的ashx(connection.ashx)先分析数据把分析后的数据模型保存起来,同时给客户端一个SessionId。客户端收到回应后用这个SessionId发起长连接请求,服务器端就不再重复分析数据了,而是将之前的数据从缓存中取出使用,为了调试方便我把这个SessionId写死了。同时我用Quartz.Net建立了两个任务,一个CleanJob.cs实际是作为清理任务,定时清理掉缓存中的无效或已完成请求5分钟跑一次。还有一个任务是HeartJob.cs主要是用来模拟服务器端推送的逻辑,30秒跑一次。

  用到Quartz.Net是因为我个人认为在Asp.net中直接启动BackgroundWorker的方式不是很好,还是调度引擎的线程模型更可靠。具体启动调度引擎的代码在Global.asax里面。

  附件中是我剥离出来的代码,删除了业务部分只做测试用。测试界面为index.aspx,在文本框中写点东西点提交,先收到服务器的回应后每个30秒收到服务器的回应弹出alert窗口。这里要提的就是客户端js代码在收到一个长连接反馈后马上又建立一个长连接,这是关键所在。

  源代码

 

转载于:https://www.cnblogs.com/knowesoft/p/3978882.html

.net 稳定 高效 易用 可同步 TCP 通信框架 使用平台: WinXP,WIN7,WIN8,WINCE,WINPHONE。 使用.net 2.0 框架。 主要功能介绍: 1、可以代替 Oracle,Mysql客户端 在不安装Oracle,MySql客户端的情况下访问, 对数据库进行间接访问(需开始框架的服务器端)。 2、可以使本来没有网经功能的Sqlite具有网络访问的能力。(也是需要开启服务器端) 以上两点可以兼容现有代码生成器时,客户端代码仅需要特别小的改动就可以。 3、基本功能。可以实现聊天,传文件,图片。 4、使用长连接,有断线自动连接功能,心跳包。 5、使用自定义数据包协议,自建Session机制加强数据连接安全。 6、框架稳定,支持高并发。 7、简单的事件处理机制。使用更加简单。 8、支持同步处理,使程序的开发更架简单,不需要另行回调处理。 下载地址: 使用方式: 首选需要 引用 DataUtils.v1.1.dll。DataUtils 内包含客户端与服务器端 处理类。 1、服务器端 代码示例。 设置服务器端默认端口 ,不设置端口会使用默认端口 TcpSettings.DefultPort = 8511; 既可以使用静态默认对象,也可以创建服务器端对象。 SocketListener server= new SocketListener(); 对象创建后 注册一些事件,以接收客户端发送的信息。 SocketListener.Server.RegeditSession += new Feng.Net.Tcp.SocketListener.RegeditSessionEventHandler(server_RegeditSession); RegeditSession 事件用于是否允许客户端连接此服务器。可以使用用户名,密码的核对方式。 SocketListener.Server.DataReceive += new SocketListener.DataReceiveEventHandler(server_DataReceive); DataReceive 在这个事件里处理接收到的数据。 事件注册完成就可以打开监听 SocketListener.Server.StartListening(); 2、客户端 代码示例 设置服务器的IP地址 TcpSettings.DeafultIPAddress = "192.168.1.3"; TcpSettings.DefultPort = 8511;//不设置端口会使用默认端口。 这样就可以使用默认的静态客户端了。 也可以自己创建对象。 客户端创建后需要在Connected事件注册用户,以限制某些用户是否可以使此链接。用户来源可以是数据库等。 void client_Connected(object sender, SocketClient sh) { Client.RegeditSession("aaa", "bbb"); } 发送文字消息给其他用户 SocketClient.Client.SendToOtherUser(string user, string text); //USER代表发达的目白用户,text表示为发送的内容。 发送图片,音频,视屏可以使用 SocketClient..SendToOtherUser(string user, byte[] data)////USER代表发达的目白用户,data表示为发送的内容。 data数据中数据有多种类型时可以使用 using (Feng.IO.BufferWriter bw = new Feng.IO.BufferWriter()) { bw.WriteBitmap(new Bitmap(100, 100)); bw.Write(text);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值