问题描述
前几天因客户的需要,希望把原来的监控系统从c/s模式变成b/s模式,其他的都好办,但是实时告警和实时数据处理是个问题。采用以前web的轮询技术可能导致多个客户端的告警和数据不一致的问题,于是考虑采用web推送技术。web推送,在网上查了一下,有多种方式,比如HTML5的server-sentevents技术、HTML5的WebSocket技术、基于comet服务器推送技术等等,很多。考虑到客户的浏览器是Google浏览器或FireFox浏览器,所以使用HTML5的技术,因为告警数据是从服务器推送过来的,不需要在接收到后进行处理然后回送的情况,所以使用半双工方式的server-sentevents来实现。编译环境使用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",newCSubscrib());//实时数据更新订阅Application.Add("DatasUpdateSubscrib",newCSubscrib());
用来保存告警更新订阅类实例和实时数据更新订阅类实例。以下为Global.asax.cs内容:usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Web;usingSystem.Web.Security;usingSystem.Web.SessionState;namespaceTieTaOMC{///<summary>///订阅类///</summary>publicclassCSubscrib{///<summary>///订阅响应信息对象字典///</summary>privateDictionary<long,HttpResponse>m_Response;///<summary>///订阅类实例///</summary>publicCSubscrib(){m_Response=newDictionary<long,HttpResponse>();}///<summary>///添加订阅///</summary>///<paramname="lResponseID">响应信息对象编号</param>///<paramname="htpResponse">响应信息对象</param>///<returns>是否成功</returns>publicbooladdResponse(longlResponseID,HttpResponsehtpResponse){boolbReturn=false;if(0<lResponseID&&null!=htpResponse){if(!m_Response.ContainsKey(lResponseID)){m_Response.Add(lResponseID,htpResponse);bReturn=true;}}returnbReturn;}///<summary>///删除订阅///</summary>///<paramname="lResponseID">响应信息对象编号</param>///<returns>是否成功</returns>publicbooldelResponse(longlResponseID){boolbReturn=false;if(0<lResponseID){if(m_Response.ContainsKey(lResponseID)){//m_Response[lResponseID].Close();bReturn=m_Response.Remove(lResponseID);}}returnbReturn;}///<summary>///发送订阅信息///</summary>///<paramname="lResponseID">响应信息对象编号</param>///<paramname="sMessage">信息字符串</param>///<paramname="bCheckExsist">是否检查存在,默认为不检查</param>///<returns>是否成功</returns>publicboolsendSubscribMessage(longlResponseID,stringsMessage,stringsObjID,boolbCheckExsist=false){boolbReturn=false;if(0<lResponseID&&null!=sMessage&&0<sMessage.Length){boolbExsist=true;if(bCheckExsist){bExsist=m_Response.ContainsKey(lResponseID);}if(bExsist){try{HttpResponsehtpResponse=m_Response[lResponseID];htpResponse.Write("data:"+sObjID+"n");htpResponse.Write("data:"+sMessage+"nn");htpResponse.Flush();}catch(System.Exceptionex){stringsError=ex.Message;sError="";}bReturn=true;}}returnbReturn;}///<summary>///向所有的订阅响应信息对象发送信息///</summary>///<paramname="sMessage"></param>///<returns></returns>publicboolsendSubscribMessageToAllResponses(stringsMessage,stringsObjID){boolbReturn=false;if(null!=sMessage&&0<sMessage.Length){bReturn=true;foreach(longlResponseIDinm_Response.Keys){sendSubscribMessage(lResponseID,sMessage,sObjID);}}returnbReturn;}}publicclassGlobal:System.Web.HttpApplication{voidApplication_Start(objectsender,EventArgse){//在应用程序启动时运行的代码//告警更新订阅Application.Add("AlarmsUpdateSubscrib",newCSubscrib());//实时数据更新订阅Application.Add("DatasUpdateSubscrib",newCSubscrib());}voidApplication_End(objectsender,EventArgse){//在应用程序关闭时运行的代码}voidApplication_Error(objectsender,EventArgse){//在出现未处理的错误时运行的代码}voidSession_Start(objectsender,EventArgse){//在新会话启动时运行的代码}voidSession_End(objectsender,EventArgse){//在会话结束时运行的代码。//注意:只有在Web.config文件中的sessionstate模式设置为//InProc时,才会引发Session_End事件。如果会话模式设置为StateServer//或SQLServer,则不会引发该事件。}}}
解决方案
解决方案二:
在工程中添加新建项,选择“一般处理程序”文件名称为“AlarmsUpdateSubscrib.ashx”。打开AlarmsUpdateSubscrib.ashx.cs文件。修改类AlarmsUpdateSubscrib的ProcessRequest方法内容如下:publicvoidProcessRequest(HttpContextcontext){HttpApplicationStateApp=context.Application;if(null!=App){HttpResponseresponse=context.Response;if(null!=response){longlngSessionID=1;intiSleepSpace=1000/*while循环线程睡眠毫秒数*/;Randomra=newRandom();lngSessionID+=ra.Next();response.ContentType="text/event-stream";//告警更新订阅CSubscribAUpdateSubscrib=App.Get("AlarmsUpdateSubscrib")asCSubscrib;if(null!=AUpdateSubscrib&&AUpdateSubscrib.addResponse(lngSessionID,response)){while(response.IsClientConnected){Thread.Sleep(iSleepSpace);}}response.End();response.Close();}}}
AlarmsUpdateSubscrib.ashx.cs内容usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Web;usingSystem.Threading;namespaceTieTaOMC{///<summary>///告警更新订阅类///</summary>publicclassAlarmsUpdateSubscrib:IHttpHandler{publicvoidProcessRequest(HttpContextcontext){HttpApplicationStateApp=context.Application;if(null!=App){HttpResponseresponse=context.Response;if(null!=response){longlngSessionID=1;intiSleepSpace=1000/*while循环线程睡眠毫秒数*/;Randomra=newRandom();lngSessionID+=ra.Next();response.ContentType="text/event-stream";//告警更新订阅CSubscribAUpdateSubscrib=App.Get("AlarmsUpdateSubscrib")asCSubscrib;if(null!=AUpdateSubscrib&&AUpdateSubscrib.addResponse(lngSessionID,response)){while(response.IsClientConnected){Thread.Sleep(iSleepSpace);}}response.End();response.Close();}}}publicboolIsReusable{get{returnfalse;}}}}
解决方案三:
在工程内添加新建项,选择web窗体,名称为AddAlarm.aspxAddAlarm.aspx内容<%@PageLanguage="C#"AutoEventWireup="true"CodeBehind="AddAlarm.aspx.cs"Inherits="TieTaOMC.AddAlarm"%><!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><htmlxmlns="http://www.w3.org/1999/xhtml"><headrunat="server"><title></title></head><body><formid="alarmForm"runat="server"><asp:TableID="alarmTable"runat="server"><asp:TableRow><asp:TableCellWrap="False">告警流水号:</asp:TableCell><asp:TableCell><asp:TextBoxID="txt_AlarmSerialNo"runat="server"></asp:TextBox></asp:TableCell><asp:TableCell>站点编号:</asp:TableCell><asp:TableCell><asp:TextBoxID="txt_FSUNo"runat="server"></asp:TextBox></asp:TableCell><asp:TableCellWrap="False">子设备名称:</asp:TableCell><asp:TableCell><asp:TextBoxID="txt_DevName"runat="server"></asp:TextBox></asp:TableCell><asp:TableCellWidth="100%"> </asp:TableCell></asp:TableRow><asp:TableRow><asp:TableCellWrap="False">告警编号:</asp:TableCell><asp:TableCell><asp:TextBoxID="txt_AlarmNo"runat="server"></asp:TextBox></asp:TableCell><asp:TableCellWrap="False">告警级别:</asp:TableCell><asp:TableCell><asp:TextBoxID="txt_AlarmLevel"runat="server"></asp:TextBox></asp:TableCell><asp:TableCellWrap="False">告警描述:</asp:TableCell><asp:TableCell><asp:TextBoxID="txt_AlarmDesc"runat="server"></asp:TextBox></asp:TableCell><asp:TableCell> </asp:TableCell></asp:TableRow><asp:TableRow><asp:TableCellWrap="False"ColumnSpan="7"><asp:ButtonID="but_OK"runat="server"Text="提交"OnClick="but_Add_Click"/></asp:TableCell></asp:TableRow></asp:Table></form></body></html>
AddAlarm.aspx.cs内容usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Web;usingSystem.Web.UI;usingSystem.Web.UI.WebControls;namespaceTieTaOMC{publicpartialclassAddAlarm:System.Web.UI.Page{protectedvoidPage_Load(objectsender,EventArgse){}protectedvoidbut_Add_Click(objectsender,EventArgse){stringsAlarmSerialNo="",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){//告警更新订阅CSubscribAUpdateSubscrib=Application.Get("AlarmsUpdateSubscrib")asCSubscrib;if(null!=AUpdateSubscrib){stringsTRS="<tr>",sTDs="<tdnowrap> ",sTDE="</td>",sTRE="</tr>";stringsHTML=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-ddHH:mm:ss")+sTDE;//告警时间sHTML+=sTDs+sTDE;sHTML+=sTRE;AUpdateSubscrib.sendSubscribMessageToAllResponses(sHTML,"alarmsListTable");}}}}}
解决方案四:
在工程内添加新建项,选择web窗体,名称为AlarmList.aspx在本页面中使用jquery处理获取到的web推送消息,添加一行到表格中,以后可以再进行扩展增加更新,删除等功能AlarmList.aspx内容<%@PageLanguage="C#"AutoEventWireup="true"CodeBehind="AlarmList.aspx.cs"Inherits="TieTaOMC.Subscrib.AlarmList"%><!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><htmlxmlns="http://www.w3.org/1999/xhtml"><headid="Head1"runat="server"><title>获得服务器更新</title><scriptlanguage="javascript"type="text/javascript"src="Scripts/jquery-1.4.1.min.js"></script></head><body><h1>获得服务器更新</h1><asp:LabelID="SessionKey"runat="server"Text="Label"></asp:Label><divid="Table_Div"style="width:100%"><tableid="alarmsListTable"width="100%"border="1"cellspacing="0"cellpadding="2"><tralign="center"><tdnowrap>告警序号</td><tdnowrap>告警站点编号</td><tdnowrap>告警设备</td><tdnowrap>告警编号</td><tdnowrap>告警级别</td><tdnowrap>告警内容</td><tdnowrap>告警时间</td><tdwidth="100%"> </td></tr></table></div><divid="resultDiv">aaaaa</div><divid="errorDiv">aaaaa</div><scriptlanguage="javascript"type="text/javascript">variAlarmsCount=0;if(typeof(EventSource)!=="undefined"){varsURL="AlarmsUpdateSubscrib.ashx";varsource=newEventSource(sURL);source.onmessage=function(event){varsData="";$("#errorDiv").html("");sData=event.data;$("#resultDiv").text(sData);varsArray=sData.split('n');varsID="#"+sArray[0]+"tr:eq(0)";$(sArray[1]).insertAfter($(sID));};source.onerror=function(event){$("#errorDiv").html("<br/>错误:<br/>");};}else{$("#errorDiv").html("错误:<br/>对不起,您的浏览器不支持“HTML5服务器发送事件(server-sentevent)允许网页获得来自服务器的更新”。<br/>");}</script></body></html>
编译工程,然后运行工程打开的浏览器地址栏中输入http://localhost:xxxx/AddAlarm.aspx,然后回车,其中“xxxx”为工程调试时的服务端口(我的为4239),定义为告警添加页面。然后另外打开一个页面,在地址栏中输入http://localhost:xxxx/AlarmList.aspx,然后回车,其中“xxxx”同上,定义为告警列表页面,也可以在局域网中的另一台计算机上进行本操作,效果一样。在告警添加页面中填写如下图所示的内容:然后点击“提交”按钮然后查看告警列表画面,画面如下图所示:如果是本机运行两个页面的话,通过cmd的netstat命令我们可以发现其中两行内容TCP[::1]:4239lizhimin-PC:6945ESTABLISHEDTCP[::1]:6945lizhimin-PC:4239ESTABLISHED如下图所示:说明server-sentevents在告警列表页面通过varsource=newEventSource(sURL);产生的客户端端口为“6945”,请求的服务端AlarmsUpdateSubscrib.ashx端口为4239,状态为ESTABLISHED表明该socket连接正在存在,是个长连接,客户端一直等待服务端推送数据。当然在不同的计算机上,计算机名称可能不一样,端口号不一样但是肯定会出现形如以上的两行内容。在此有个问题,如果客户端太多,服务器所建立的长连接太多,这样会耗费很多资源,怎么解决这个问题呢?是不是要定时删除几个Response,然后重新连接?如果数据量很大的话,这样做的代价和一直保持连接,孰轻孰重?有待于研究。