Web頁面實時刷新技術探討

一、總述

隨着網絡技術的飛速發展,使用B/S結構來實現項目應用已經越來越多,而實時監控一直都是多數行業軟件所必備的功能,由此使用Web頁面來實現實時監控成了一種必然的需求。

 

二、實時刷新技術

1、傳統的頁面刷新方式

傳統的頁面刷新方式很多,常見的有頁面間隔一定的時間自動刷新、ActiveX控件、Applet等。

採用頁面間隔一定的時間自動刷新的方式,是在網頁的頭部加入一下代碼:

<meta http-equiv="refresh" content="20;url=newPage">

這裏是經過20秒跳轉到一個新頁面,可以將“newPage”設置爲本頁面即爲刷新本頁面,刷新間隔時間可以修改“20”爲任意時間。通過這種方式如果併發和訪問量較大,服務器就有可能承受不了這種壓力,從而造成服務器死機。

使用ActiveX控件的方式需要每個客戶端下載安裝ActiveX控件,並且客戶端瀏覽器只能使用Windows的IE瀏覽器。

同樣使用Applet需要客戶端安裝Java運行時。

這些傳統的頁面刷新方式都或多或少的存在着一些確定,在Web項目應用中的使用也越來越少。

 

2、Ajax輪詢

Ajax輪詢方式是使用客戶端腳本,通過XMLHttpRequest來定時發送請求,從而查詢頁面數據的更新情況。通過這種方式,程序實現方便簡捷,但客戶端頻繁的發送請求會給服務器帶來很大的壓力和客戶端處理器負載,如果服務器端沒有更新時,這種輪詢訪問服務器便是無意義的,並且耗費了網絡資源與CPU處理資源。

實例說明:服務器端通過手動控制按鈕產生一張圖片,客戶端顯示最新圖片及圖片的信息內容。

服務器端通過一個按鈕btnGet產生圖片,按鈕事件代碼如下所示。

代碼清單1:

protected void btnGet_Click(object sender, EventArgs e)

{

    //通過改寫一張父圖片上的文字來產生新圖片

    System.Drawing.Image image = System.Drawing.Image.FromFile(HttpContext.Current.Server.MapPath("parent.jpg"));

    string currTime = System.DateTime.Now.ToString("yyMMddHHmmssffffff");

 

    Graphics g = Graphics.FromImage(image);

    g.DrawImage(image, 0, 0, image.Width, image.Height);

    g.DrawString(currTime, new Font("Arial", 28), new SolidBrush(Color.Red), 10, 10);

    g.Dispose();

 

    string savePath = "Pic/" + currTime + ".jpg";

    image.Save(HttpContext.Current.Server.MapPath(savePath));

 

    //將最新圖片文件名寫入到XML文件中

    XmlDocument xmlDoc = new XmlDocument();

    xmlDoc.Load(HttpContext.Current.Server.MapPath("newPic.xml"));

    XmlNodeList nodeList = xmlDoc.SelectSingleNode("Items").ChildNodes;

 

    XmlElement element = (XmlElement)nodeList[0];

    element.SetAttribute("code", currTime);

    xmlDoc.Save(HttpContext.Current.Server.MapPath("newPic.xml"));

}

顯示圖片頁面通過兩個頁面分別顯示圖片信息與圖片內容,顯示圖片頁面內容如下所示。

代碼清單2:

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

    <title></title>

    <meta http-equiv="Content-Type" content="text/html; Charset=gb2312" />

    <script type="text/javascript">

        var xmlHttp;

        function CreateXMLHttp() {

            if(window.XMLHttpRequest) {

                xmlHttp = new XMLHttpRequest();

            }

            else if (window.ActiveXObject) {

                try { xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); }

                catch(e) {

                    try { xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); }

                    catch(e) { }

                }

            }

            xmlHttp = new ActiveXObject("Msxml2.XMLHTTP.5.0");

        }

 

        function startXMLHttp() {

            CreateXMLHttp();

            xmlHttp.onreadystatechange =retDeal;

            xmlHttp.open("post","imgInfo.aspx",true);

            xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded charset=gb2312");

            xmlHttp.send();

        }       

     

         function retDeal() {

           if(xmlHttp.readystate==4) {

             if(xmlHttp.status==200) {

               hid1.value = xmlHttp.responseText;

               if(hid1.value != hid2.value) {

                 hid2.value = hid1.value;

                 ifrImg.location.reload();

                 document.getElementById("Content").innerHTML = hid1.value;

               }

             }

             setTimeout(startXMLHttp,2000);

          }

        }

    </script>

</head>

<body onload='Javascript:startXMLHttp()'>

    <div></div>

    <span id="Content"></span>

    <input type="hidden" id="hid1" /><input type="hidden" id="hid2" />

    <iframe id="ifrImg" src="img.aspx" width="800" height="500"></iframe>

</body>

</html>

 

3、DWR服務器Push

DWR的反轉AJAX功能允許我們從服務器端來控制客服端,而不需要客戶端的請求,服務器可以自動把消息發給指定的客戶端。DWR的Push技術是讓服務器每次發送廣播時,把這個廣播推送給客戶端,而不用客戶端去刷新,DWR的推送是基於長連接的,性能優越。

以服務器端通過手動控制按鈕產生一張圖片,客戶端顯示最新圖片及圖片的信息內容作爲實例加以說明。

服務器端通過一個按鈕產生圖片,頁面代碼如下所示。

代碼清單3:

<%@ page language="java" pageEncoding="UTF-8"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

<title></title>

<script type='text/javascript'src='<%=request.getContextPath()%>/dwr/interface/getPic.js'></script>

<script type='text/javascript' src='<%=request.getContextPath()%>/dwr/engine.js'></script>

<script type='text/javascript' src='<%=request.getContextPath()%>/dwr/util.js'></script>

<script type="text/javascript">

    Date.prototype.format = function(format) {

    var o = {

        "M+" : this.getMonth()+1, //month

        "d+" : this.getDate(),    //day

        "h+" : this.getHours(),   //hour

        "m+" : this.getMinutes(), //minute

        "s+" : this.getSeconds(), //second

        "q+" : Math.floor((this.getMonth()+3)/3), //quarter

        "S" : this.getMilliseconds() //millisecond

        }

    if(/(y+)/.test(format)) format=format.replace(RegExp.$1,

        (this.getFullYear()+"").substr(4 - RegExp.$1.length));

    for(var k in o)if(new RegExp("("+ k +")").test(format))

        format = format.replace(RegExp.$1,

            RegExp.$1.length==1 ? o[k] :

                ("00"+ o[k]).substr((""+ o[k]).length));

    return format;

    }

 

    function getNewPic() {

        var currTime = new Date().format("yyMMddhhmmssS");

        var currPath = "D:/Program/Java/JavaSpace/ajaxTest/WebContent/";

       getPic.createStringMark(currPath+"parent.jpg",currTime,currPath+"Pic/"+currTime+".jpg");

        getPic.getNewPicId(currTime);

        }

</script>

</head>

<body>

    <input type="button" value="產生新圖片" onclick="getNewPic();" />

</body>

</html>

顯示圖片頁面通過兩個頁面分別顯示圖片信息與圖片內容,顯示圖片頁面內容如下所示。

代碼清單4:

<%@ page language="java" pageEncoding="UTF-8"%>

<jsp:useBean id="aGetNewPic" scope="page" class="com.getNewPic" />

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

<title></title>

<script type='text/javascript'src='<%=request.getContextPath()%>/dwr/interface/getPic.js'></script>

<script type='text/javascript' src='<%=request.getContextPath()%>/dwr/engine.js'></script>

<script type='text/javascript' src='<%=request.getContextPath()%>/dwr/util.js'></script>

<script type="text/javascript">

    function init() {

        dwr.engine.setActiveReverseAjax(true);    //激活反轉

        }

    window.onload = init;//頁面初始化方法

    function refreshImg() {

        ifrImg.location.reload();

        }

</script>

</head>

<body>

    <div style=" float:left">最新圖片:</div>

    <span id="divNewPicId"><%= aGetNewPic.currPicId %></span>

    <iframe id="ifrImg" src="img.jsp?id=<%= aGetNewPic.currPicId %>" width="800"height="500"></iframe>

</body>

</html>

另外,getNewPic類用於產生新圖片、Push處理。

代碼清單5:

public class getNewPic {

      

       public static String currPicId = "100413101427820";

      

       public String getNewPicId(String picId, HttpServletRequest request){

              if(currPicId == picId)return currPicId;

              if(picId != null) currPicId = picId;

             

              //獲得DWR上下文

              ServletContext sc = request.getSession().getServletContext();

              ServerContext sctx = ServerContextFactory.get(sc);

              //獲得當前瀏覽 client.jsp 頁面的所有腳本session

              Collection sessions = sctx.getScriptSessionsByPage("/ajaxTest/client.jsp");

              Util util = new Util(sessions);

              //處理這些頁面中的一些元素

              util.setValue("divNewPicId", currPicId);

              util.addFunctionCall("refreshImg",null);

             

              return currPicId;

       }

      

       public boolean createStringMark(String filePath,String markContent,String savePath)

       {

           ImageIcon imgIcon=new ImageIcon(filePath);

           Image theImg =imgIcon.getImage();

           int width=theImg.getWidth(null);

           int height= theImg.getHeight(null);

           //System.out.println(theImg);

           BufferedImage bimage = new BufferedImage(width,height, BufferedImage.TYPE_INT_RGB);

           Graphics2D g=bimage.createGraphics();

           g.setColor(Color.red);

           g.setBackground(Color.white);

           g.drawImage(theImg, 0, 0, null );

           g.setFont(new Font("Arial",Font.PLAIN,28)); //字體、字型、字號

           g.drawString(markContent,10,10); //畫文字

           g.dispose();

           try

           {

                  FileOutputStream out=new FileOutputStream(savePath); //輸出文件名

                  JPEGImageEncoder encoder =JPEGCodec.createJPEGEncoder(out);

                  JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bimage);

                  param.setQuality(1, true);

                  encoder.encode(bimage, param);

                  out.close();

              }

           catch(Exception e)

           { return false; }

           return true;

       }

 

}

 

4、與服務端建立長連接

與服務器建立長連接,也就是在顯示數據頁面中嵌入一個隱藏頁面,該隱藏頁面主要完成取服務器端所要顯示的數據,並且將該頁面顯示數據的方法寫成一個死循環,以此來保持與服務器端的長連接。

同樣以服務器端通過手動控制按鈕產生一張圖片,客戶端顯示最新圖片及圖片的信息內容作爲實例加以說明。

服務器端通過一個按鈕btnGet產生圖片,按鈕事件代碼同代碼清單1。

顯示圖片頁面通過兩個頁面分別顯示圖片信息與圖片內容,顯示圖片頁面內容如下所示。

代碼清單6:

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title></title>

<script type="text/javascript">

    function writePicInfo(str) {

        if (window.document.getElementById("divNewPicId").innerText != str) {

            window.document.getElementById("divNewPicId").innerText = str;

            ifrImg.location.reload();

        }

    }

    function onload(){

        var ifrpush = new ActiveXObject("htmlfile"); // 創建對象

        ifrpush.open();

        var ifrDiv = ifrpush.createElement("div"); //添加一個DIV

        ifrpush.appendChild(ifrDiv); //添加到htmlfile

        ifrpush.parentWindow.writePicInfo = writePicInfo; //註冊javascript方法

        ifrDiv.innerHTML = "<iframe src='getNew.aspx'></iframe>"//在div裏添加iframe

        ifrpush.close();

    }

     onload();

    </script>

</head>

<body>

        <div style=" float:left">最新圖片:</div>

        <div id="divNewPicId"></div>

        <iframe id="ifrImg" src="img.aspx" width="800" height="500"></iframe>

</body>

</html>

其中,隱藏頁面getNew.aspx代碼如下所示。

代碼清單7:

protected override void Render(HtmlTextWriter output)

{

    string str;

    while (true)  //死循環保持長鏈接

    {

        //讀取最新圖片信息

        XmlDocument xmlDoc = new XmlDocument();

        xmlDoc.Load(HttpContext.Current.Server.MapPath("newPic.xml"));

        XmlNodeList nodeList = xmlDoc.SelectSingleNode("Items").ChildNodes;

        XmlElement element = (XmlElement)nodeList[0];

        string newPicId = element.GetAttribute("code");

 

        str = "<script >window.parent.writePicInfo('" + newPicId + "')</script>";

        this.Context.Response.Write(str);

        this.Context.Response.Flush();

        System.Threading.Thread.Sleep(2000);

    }

}

代碼中的“htmlfile”是一個類似JavaScript中Window對象的一個ActiveX Object,它內部也是DOM結構,將作爲隱藏幀的IFrame寫入這個對象中,這樣可以解決進度條一直爲讀取狀態的問題。

 

5、RTMP協議傳輸

隨着網絡技術的迅猛發展,視頻、音頻等多媒體通信需求越來越多,Adobe公司開放了RTMP(the Real-time Messaging Protocol)協議規範,RTMP協議作爲客戶端和服務器端的傳輸協議,這是一個專門爲高效傳輸視頻、音頻和數據而設計的TCP/IP 協議。其優秀產品Flex是用於構建和維護在所有主要瀏覽器、桌面和操作系統一致地部署的極具表現力的 Web 應用程序的高效率的開放源碼框架。

從目前的應用來說,RTMP主要用於音、視頻的傳輸,流視頻服務器就是FMS(Flash Media Server),其原稱爲FCS(Flash Communication Server),技術範疇能應用到諸如Flash聊天室、視頻會議等領域。

以一個實現聊天功能的Flex程序爲例,顯示聊天內容代碼如下所示。

代碼清單8:

<?xml version="1.0" encoding="utf-8"?>

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" fontSize="12" creationComplete="init()">

 

<mx:Script>

    <![CDATA[

   

    import vo.Message;

    import mx.collections.ArrayCollection;

    import mx.controls.Alert;

   

    private var myNetConnection:NetConnection;  //flex fms 鏈接用的對象

    private var serverApp:String ="rtmp://127.0.0.1/Test";

    private var talk_so:SharedObject;  //fms 下的SharedObject 對象

      

    private function init():void

    {

       btn_send.addEventListener(MouseEvent.CLICK,btnSenClickHandler);

       myNetConnection = new NetConnection();

       myNetConnection.addEventListener(NetStatusEvent.NET_STATUS,netStatusHandler);   

       myNetConnection.connect(serverApp);

    }

   

    private function netStatusHandler(evt:NetStatusEvent):void

    {

       trace(evt.info.code);  //調試代碼用     

       if ( evt.info.code =="NetConnection.Connect.Success" )

       {

           talk_so = SharedObject.getRemote("talk",myNetConnection.uri,true);

           talk_so.addEventListener(SyncEvent.SYNC,talkSoSyncHandler);

           talk_so.connect(myNetConnection);

       }

       else

       {

           Alert.show("鏈接失敗"+evt.info.code);

       }

    }

   

    private function talkSoSyncHandler(evt:SyncEvent):void

    {

       txt_content.text="";

       if ( talk_so.data.msgList!=null )

       {

           var tmp:ArrayCollection = new ArrayCollection();

           convertArrayCollection(tmp,talk_so.data.msgList as ArrayCollection);        

           for(var i:int=0;i<tmp.length ;i++)

           {

              var message:Object = tmp.getItemAt(i);          

              varfullMsg:String=message.nickname+""+message.time.toTimeString()+":"+message.msg;          

              txt_content.text=txt_content.text+fullMsg+"/n";

           }

       }

    }

      

    private function btnSenClickHandler(evt:MouseEvent):void

    {

       var arr:ArrayCollection = new ArrayCollection();    

       if ( talk_so.data.msgList==null )

       {

           arr = new ArrayCollection();   

       }

       else

       {

           convertArrayCollection(arr,talk_so.data.msgList as ArrayCollection);

       }     

       var obj:Message = new Message();

       obj.nickname=txt_nickname.text;

       obj.msg=txt_message.text;

       obj.time = new Date();     

       arr.addItem(obj);   

       talk_so.setProperty("msgList",arr);

       /*將你更新好的聊天記錄列表寫入到公共的SharedObject對象中去即可。

           調用setProperty() 以更改數據對象的屬性。 服務器將更新這些屬性,並調度 sync 事件,並將這些屬性發回到連接的客戶端。*/

       txt_message.text="";

    }

   

    //交換數組中元素

    private functionconvertArrayCollection(arrNew:ArrayCollection,arrOld:ArrayCollection):void

    {

       arrNew.removeAll();     

       for(var i:int=0;i<arrOld.length ;i++)

       {

           arrNew.addItemAt(arrOld.getItemAt(i),i);

       }

    }     

    ]]>

</mx:Script>

 

    <mx:TextArea x="33" y="10" height="159" width="366" id="txt_content"/>

    <mx:TextInput x="33" y="177" width="62" id="txt_nickname"/>

    <mx:Label x="103" y="179" text=""/>

    <mx:TextInput x="146" y="177" width="185" id="txt_message"/>

    <mx:Button x="334" y="177" label="send" id="btn_send"/>

   

</mx:Application>

 

三、結語

從目前實際應用來說,以上四種實現Web頁面實時刷新都是可行方案,各有優缺點與適用的具體環境。

Ajax輪詢方式比較適用於需要傳輸的數據量較小的情況,可通過客戶端首先輪詢服務器端的更新標識,若有更新再下載更新數據,這樣能減小一部分服務器的壓力。

DWR反轉AJAX功能,真正實現了從服務器端將更新“推”到客戶端。

與服務器端建立長連接的方式,也是通過客戶端的請求獲取更新數據的。

通過RTMP協議傳輸,主要適用於音、視頻的數據傳輸,比較適用於視頻聊天室,目前視頻服務器FMS的費用較高(4500元100個客戶端),與服務器的連接數受到限制。

總體來看,要實現Web頁面的實時刷新,肯定是會給服務器帶來一定的壓力的,對於具體項目的不同需求,可選擇合適的方式來實現Web頁面的實時刷新。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章