ASP.net使用C#实现HTML5的server-sent events

bluebaby88 2015-09-22 09:34:23
前几天因客户的需要,希望把原来的监控系统从c/s模式变成b/s模式,其他的都好办,但是实时告警和实时数据处理是个问题。采用以前web的轮询技术可能导致多个客户端的告警和数据不一致的问题,于是考虑采用web推送技术。
web推送,在网上查了一下,有多种方式,比如HTML5的server-sent events技术、HTML5的WebSocket技术、基于comet服务器推送技术等等,很多。
考虑到客户的浏览器是Google浏览器或FireFox浏览器,所以使用HTML5的技术,因为告警数据是从服务器推送过来的,不需要在接收到后进行处理然后回送的情况,所以使用半双工方式的server-sent events来实现。
编译环境使用VS2010,语言C#
以下是采用该技术的验证过程:
首先新建一个名称为TieTaOMC的ASP.net工程
然后打开Global.asax.cs文件

在文件内添加CSubscrib类,用来订阅推送消息

CSubscrib类里结构介绍
Dictionary<long, HttpResponse> m_Response的字典私有成员变量,又来存放订阅的Response对象,字典的Key是随机生成的长整形数,value是HttpResponse类型的Response对象。
addResponse公有方法,用来添加要订阅的Response对象
delResponse公有方法,用来删除已订阅的Response对象
sendSubscribMessage公有方法,用来向某个已订阅的Response对象推送消息
sendSubscribMessageToAllResponses公有方法,用来向所有的已订阅的Response对象推送消息

在Global类的Application_Start方法中添加如下内容

//告警更新订阅
Application.Add("AlarmsUpdateSubscrib", new CSubscrib());

//实时数据更新订阅
Application.Add("DatasUpdateSubscrib", new CSubscrib());


用来保存告警更新订阅类实例和实时数据更新订阅类实例。

以下为Global.asax.cs内容:


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

namespace TieTaOMC
{
/// <summary>
/// 订阅类
/// </summary>
public class CSubscrib
{
/// <summary>
/// 订阅响应信息对象字典
/// </summary>
private Dictionary<long, HttpResponse> m_Response;

/// <summary>
/// 订阅类实例
/// </summary>
public CSubscrib()
{
m_Response = new Dictionary<long, HttpResponse>();
}

/// <summary>
/// 添加订阅
/// </summary>
/// <param name="lResponseID">响应信息对象编号</param>
/// <param name="htpResponse">响应信息对象</param>
/// <returns>是否成功</returns>
public bool addResponse(long lResponseID, HttpResponse htpResponse)
{
bool bReturn = false;

if (0 < lResponseID && null != htpResponse)
{
if (!m_Response.ContainsKey(lResponseID))
{
m_Response.Add(lResponseID, htpResponse);
bReturn = true;
}
}
return bReturn;
}

/// <summary>
/// 删除订阅
/// </summary>
/// <param name="lResponseID">响应信息对象编号</param>
/// <returns>是否成功</returns>
public bool delResponse(long lResponseID)
{
bool bReturn = false;

if (0 < lResponseID)
{
if (m_Response.ContainsKey(lResponseID))
{
//m_Response[lResponseID].Close();
bReturn = m_Response.Remove(lResponseID);
}
}
return bReturn;
}

/// <summary>
/// 发送订阅信息
/// </summary>
/// <param name="lResponseID">响应信息对象编号</param>
/// <param name="sMessage">信息字符串</param>
/// <param name="bCheckExsist">是否检查存在,默认为不检查</param>
/// <returns>是否成功</returns>
public bool sendSubscribMessage(long lResponseID, string sMessage, string sObjID, bool bCheckExsist = false)
{
bool bReturn = false;

if (0 < lResponseID && null != sMessage && 0 < sMessage.Length)
{
bool bExsist = true;
if (bCheckExsist)
{
bExsist = m_Response.ContainsKey(lResponseID);
}
if (bExsist)
{
try
{
HttpResponse htpResponse = m_Response[lResponseID];
htpResponse.Write("data: " + sObjID + "\n");
htpResponse.Write("data: " + sMessage + "\n\n");
htpResponse.Flush();
}
catch (System.Exception ex)
{
string sError = ex.Message;
sError = "";
}
bReturn = true;
}
}
return bReturn;
}

/// <summary>
/// 向所有的订阅响应信息对象发送信息
/// </summary>
/// <param name="sMessage"></param>
/// <returns></returns>
public bool sendSubscribMessageToAllResponses(string sMessage, string sObjID)
{
bool bReturn = false;

if (null != sMessage && 0 < sMessage.Length)
{
bReturn = true;
foreach (long lResponseID in m_Response.Keys)
{
sendSubscribMessage(lResponseID, sMessage, sObjID);
}
}
return bReturn;
}
}

public class Global : System.Web.HttpApplication
{

void Application_Start(object sender, EventArgs e)
{
// 在应用程序启动时运行的代码

//告警更新订阅
Application.Add("AlarmsUpdateSubscrib", new CSubscrib());

//实时数据更新订阅
Application.Add("DatasUpdateSubscrib", new CSubscrib());
}

void Application_End(object sender, EventArgs e)
{
// 在应用程序关闭时运行的代码

}

void Application_Error(object sender, EventArgs e)
{
// 在出现未处理的错误时运行的代码

}

void Session_Start(object sender, EventArgs e)
{
// 在新会话启动时运行的代码

}

void Session_End(object sender, EventArgs e)
{
// 在会话结束时运行的代码。
// 注意: 只有在 Web.config 文件中的 sessionstate 模式设置为
// InProc 时,才会引发 Session_End 事件。如果会话模式设置为 StateServer
// 或 SQLServer,则不会引发该事件。

}
}
}

...全文
5531 5 打赏 收藏 转发到动态 举报
写回复
用AI写文章
5 条回复
切换为时间正序
请发表友善的回复…
发表回复
编程有钱人了 2016-12-16
  • 打赏
  • 举报
回复
webscoket简单多了
sheseido 2016-12-15
  • 打赏
  • 举报
回复
群主,好像要点击两次才能把前一次提交的数据更新掉啊。不知道你有没有发现这个问题
bluebaby88 2015-09-22
  • 打赏
  • 举报
回复
在工程内添加新建项,选择web窗体,名称为AlarmList.aspx 在本页面中使用jquery处理获取到的web推送消息,添加一行到表格中,以后可以再进行扩展增加更新,删除等功能 AlarmList.aspx内容

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AlarmList.aspx.cs" Inherits="TieTaOMC.Subscrib.AlarmList" %>


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>获得服务器更新</title>
    <script language="javascript" type="text/javascript" src="Scripts/jquery-1.4.1.min.js"></script>
</head>
<body>
    <h1>获得服务器更新</h1>
    <asp:Label ID="SessionKey" runat="server" Text="Label"></asp:Label>
        
        <div id="Table_Div" style="width: 100%">
            <table id="alarmsListTable" width="100%" border="1" cellspacing="0" cellpadding="2">
                <tr align="center">
                    <td nowrap>告警序号</td>
                    <td nowrap>告警站点编号</td>
                    <td nowrap>告警设备</td>
                    <td nowrap>告警编号</td>
                    <td nowrap>告警级别</td>
                    <td nowrap>告警内容</td>
                    <td nowrap>告警时间</td>
                    <td width="100%"> </td>
                </tr>
            </table>
        </div>
        <div id="resultDiv">aaaaa</div>
        <div id="errorDiv">aaaaa</div>
        <script language="javascript" type="text/javascript">
            var iAlarmsCount = 0;
            if (typeof (EventSource) !== "undefined") {
                var sURL = "AlarmsUpdateSubscrib.ashx";
                var source = new EventSource(sURL);
                source.onmessage = function (event) {
                    var sData = "";
                    $("#errorDiv").html("");
                    sData = event.data;
                    $("#resultDiv").text(sData);
                    var sArray = sData.split('\n');
                    var sID = "#" + sArray[0] + " tr:eq(0)";
                    $(sArray[1]).insertAfter($(sID));
                };
                source.onerror = function (event) {
                    $("#errorDiv").html("<br />错误:<br />");
                };
            }
            else {
                $("#errorDiv").html("错误:<br />对不起,您的浏览器不支持“HTML5 服务器发送事件(server-sent event)允许网页获得来自服务器的更新”。<br />");
            }
        </script>
</body>
</html>
编译工程,然后运行工程 打开的浏览器地址栏中输入http://localhost:xxxx/AddAlarm.aspx,然后回车,其中“xxxx”为工程调试时的服务端口(我的为4239),定义为告警添加页面。 然后另外打开一个页面,在地址栏中输入http://localhost:xxxx/AlarmList.aspx,然后回车,其中“xxxx”同上,定义为告警列表页面,也可以在局域网中的另一台计算机上进行本操作,效果一样。 在告警添加页面中填写如下图所示的内容: 然后点击“提交”按钮 然后查看告警列表画面,画面如下图所示: 如果是本机运行两个页面的话,通过cmd的netstat命令我们可以发现其中两行内容 TCP [::1]:4239 lizhimin-PC:6945 ESTABLISHED TCP [::1]:6945 lizhimin-PC:4239 ESTABLISHED 如下图所示: 说明server-sent events在告警列表页面通过var source = new EventSource(sURL);产生的客户端端口为“6945”,请求的服务端AlarmsUpdateSubscrib.ashx端口为4239,状态为ESTABLISHED表明该socket连接正在存在,是个长连接,客户端一直等待服务端推送数据。 当然在不同的计算机上,计算机名称可能不一样,端口号不一样但是肯定会出现形如以上的两行内容。 在此有个问题,如果客户端太多,服务器所建立的长连接太多,这样会耗费很多资源,怎么解决这个问题呢?是不是要定时删除几个Response,然后重新连接?如果数据量很大的话,这样做的代价和一直保持连接,孰轻孰重?有待于研究。
bluebaby88 2015-09-22
  • 打赏
  • 举报
回复
在工程内添加新建项,选择web窗体,名称为AddAlarm.aspx AddAlarm.aspx内容

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AddAlarm.aspx.cs" Inherits="TieTaOMC.AddAlarm" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="alarmForm" runat="server">
    <asp:Table ID="alarmTable" runat="server">
        <asp:TableRow>
            <asp:TableCell Wrap="False">告警流水号:
            </asp:TableCell>
            <asp:TableCell>
                <asp:TextBox ID="txt_AlarmSerialNo" runat="server"></asp:TextBox>
            </asp:TableCell>
            <asp:TableCell>站点编号:
            </asp:TableCell>
            <asp:TableCell>
                <asp:TextBox ID="txt_FSUNo" runat="server"></asp:TextBox>
            </asp:TableCell>
            <asp:TableCell Wrap="False">子设备名称:
            </asp:TableCell>
            <asp:TableCell>
                <asp:TextBox ID="txt_DevName" runat="server"></asp:TextBox>
            </asp:TableCell>
            <asp:TableCell Width="100%"> 
            </asp:TableCell>
        </asp:TableRow>
        <asp:TableRow>
            <asp:TableCell Wrap="False">告警编号:
            </asp:TableCell>
            <asp:TableCell>
                <asp:TextBox ID="txt_AlarmNo" runat="server"></asp:TextBox>
            </asp:TableCell>
            <asp:TableCell Wrap="False">告警级别:
            </asp:TableCell>
            <asp:TableCell>
                <asp:TextBox ID="txt_AlarmLevel" runat="server"></asp:TextBox>
            </asp:TableCell>
            <asp:TableCell Wrap="False">告警描述:
            </asp:TableCell>
            <asp:TableCell>
                <asp:TextBox ID="txt_AlarmDesc" runat="server"></asp:TextBox>
            </asp:TableCell>
            <asp:TableCell> 
            </asp:TableCell>
        </asp:TableRow>
        <asp:TableRow>
            <asp:TableCell Wrap="False" ColumnSpan="7">
                <asp:Button ID="but_OK" runat="server" Text="提交" OnClick="but_Add_Click" />
            </asp:TableCell>
        </asp:TableRow>
    </asp:Table>
    </form>
</body>
</html>
AddAlarm.aspx.cs内容

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

namespace TieTaOMC
{
    public partial class AddAlarm : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }


        protected void but_Add_Click(object sender, EventArgs e)
        {
            string sAlarmSerialNo = "", sFSUNo = "", sDevName = "", sAlarmNo = "", sAlarmLevel = "", sAlarmDesc = "";

            sAlarmDesc = txt_AlarmDesc.Text;
            sAlarmLevel = txt_AlarmLevel.Text;
            sAlarmSerialNo = txt_AlarmSerialNo.Text;
            sDevName = txt_DevName.Text;
            sFSUNo = txt_FSUNo.Text;
            sAlarmNo = txt_AlarmNo.Text;
            
            if(
                0 < sAlarmDesc.Length ||
                0 < sAlarmLevel.Length ||
                0 < sAlarmNo.Length ||
                0 < sAlarmSerialNo.Length ||
                0 < sDevName.Length ||
                0 < sFSUNo.Length)
            {
                //告警更新订阅
                CSubscrib AUpdateSubscrib = Application.Get("AlarmsUpdateSubscrib") as CSubscrib;
                if (null != AUpdateSubscrib)
                {
                    string sTRS = "<tr>", sTDs = "<td nowrap> ", sTDE = "</td>", sTRE = "</tr>";
                    string sHTML = sTRS;

                    sHTML += sTDs + sAlarmSerialNo + sTDE;					//告警序号
                    sHTML += sTDs + sFSUNo + sTDE;						//告警站点编号
                    sHTML += sTDs + sDevName + sTDE;						//告警设备
                    sHTML += sTDs + sAlarmNo + sTDE;						//告警编号
                    sHTML += sTDs + sAlarmLevel + sTDE;						//告警级别
                    sHTML += sTDs + sAlarmDesc + sTDE;						//告警内容
                    sHTML += sTDs + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + sTDE;	//告警时间
                    sHTML += sTDs + sTDE;
                    sHTML += sTRE;
                    AUpdateSubscrib.sendSubscribMessageToAllResponses(sHTML, "alarmsListTable");
                }
            }
        }
    }
}
bluebaby88 2015-09-22
  • 打赏
  • 举报
回复
在工程中添加新建项,选择“一般处理程序”文件名称为“AlarmsUpdateSubscrib.ashx”。 打开AlarmsUpdateSubscrib.ashx.cs文件。 修改类AlarmsUpdateSubscrib的ProcessRequest方法内容如下:

	public void ProcessRequest(HttpContext context)
        {
            HttpApplicationState App = context.Application;
            if (null != App)
            {
                HttpResponse response = context.Response;
                if (null != response)
                {
                    long lngSessionID = 1;
                    int iSleepSpace = 1000/*while循环线程睡眠毫秒数*/;
                    Random ra = new Random();

                    lngSessionID += ra.Next();
                    response.ContentType = "text/event-stream";

                    //告警更新订阅
                    CSubscrib AUpdateSubscrib = App.Get("AlarmsUpdateSubscrib") as CSubscrib;
                    if (null != AUpdateSubscrib && AUpdateSubscrib.addResponse(lngSessionID, response))
                    {
                        while (response.IsClientConnected)
                        {
                            Thread.Sleep(iSleepSpace);
                        }
                    }
                    response.End();
                    response.Close();
                }
            }
        }
AlarmsUpdateSubscrib.ashx.cs内容

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

namespace TieTaOMC
{
    /// <summary>
    /// 告警更新订阅类
    /// </summary>
    public class AlarmsUpdateSubscrib : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            HttpApplicationState App = context.Application;
            if (null != App)
            {
                HttpResponse response = context.Response;
                if (null != response)
                {
                    long lngSessionID = 1;
                    int iSleepSpace = 1000/*while循环线程睡眠毫秒数*/;
                    Random ra = new Random();

                    lngSessionID += ra.Next();
                    response.ContentType = "text/event-stream";

                    //告警更新订阅
                    CSubscrib AUpdateSubscrib = App.Get("AlarmsUpdateSubscrib") as CSubscrib;
                    if (null != AUpdateSubscrib && AUpdateSubscrib.addResponse(lngSessionID, response))
                    {
                        while (response.IsClientConnected)
                        {
                            Thread.Sleep(iSleepSpace);
                        }
                    }
                    response.End();
                    response.Close();
                }
            }
        }

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

62,025

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术交流专区
javascript云原生 企业社区
社区管理员
  • ASP.NET
  • .Net开发者社区
  • R小R
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

.NET 社区是一个围绕开源 .NET 的开放、热情、创新、包容的技术社区。社区致力于为广大 .NET 爱好者提供一个良好的知识共享、协同互助的 .NET 技术交流环境。我们尊重不同意见,支持健康理性的辩论和互动,反对歧视和攻击。

希望和大家一起共同营造一个活跃、友好的社区氛围。

试试用AI创作助手写篇文章吧