基于AJAX技术的聊天室

繁简对译:[]  字体设置:[] 2008-09-09  作者:李刚  来源:电子工业出版社  阅读  次

Ajax聊天室

针对JSP聊天室存在的问题,Ajax聊天室做出了相应的改进。正如前面提到的:Ajax并不是取代B/S结构的应用,而是更好地完善了传统的Web应用。

针对JSP存在的两个问题,Ajax都有非常好的解决方案:Ajax使用XMLHttpRequest异步发送请求,Ajax的服务器响应的仅是必需的数据,而不再是整个页面,必需的数据通过JavaScript在视图中显示。使用Ajax可提高页面的复用:浏览器从服务器下载一个页面后,不是一旦提交就丢弃该页面,立即进入下个页面——这种代价相当大,用户需要频繁下载完整页面;使用Ajax,则可以长时间地使用同一个页面,客户端可以很好地复用一个已下载的页面。

1  异步发送请求

异步发送请求是Ajax最核心的内容,Ajax中的第一个字母就是Asynchronous(异步)的缩写,这也正说明了Ajax的核心。Ajax使用XMLHttpRequest对象异步发送请求。在某种程度上,XMLHttpRequest对象就是Ajax的核心,也是Ajax技术中唯一的新概念。Ajax正是XMLHttpRequest这个新对象结合JavaScript,DOM和CSS后组成的新技术。

与JavaScript相似的语言还有JScript和ECMAscript。它们的核心语法相似,作用也相似,只是在适应的浏览器以及各自的特性上存在小小的区别。XMLHttpRequest在不同浏览器中的实现也不相同,因而创建XMLHttpRequest对象的方法也存在区别。

关于XMLHttpRequest更详细的信息,请参看第9章。为了使用XMLHttpRequest对象,必须先创建XMLHttpRequest对象。创建该对象的代码如下:

//创建XMLHttpRequest对象      

function createXMLHttpRequest()

{

    //对于Mozilla浏览器

    if(window.XMLHttpRequest)

    {

        //直接使用XMLHttpRequest函数来创建XMLHttpRequest对象

        XMLHttpReq = new XMLHttpRequest();

    }

    //对于IE浏览器

    else if (window.ActiveXObject)

    {

        try

        {

            //使用AcitveXObject函数创建浏览器

            XMLHttpReq = new ActiveXObject("Msxml2.XMLHTTP");

        }

        catch (e)

        {

            //如果出现异常,再次尝试以如下方式创建XMLHttpRequest对象

            try

            {

                XMLHttpReq = new ActiveXObject("Microsoft.XMLHTTP");

            }

            catch (e)

            {

            }

        }

    }

}

前面已经讲过,XMLHttpRequest在不同浏览器中的实现机制不同,因而在不同的浏览器中创建XMLHttpRequest对象的方式也不相同。虽然上面的代码尽量兼顾不同浏览器的实现,但不排除有些浏览器不支持上面的创建方法。

一旦XMLHttpRequest对象创建成功,系统便可以使用XMLHttpRequest发送请求。XMLHttpRequest请求与传统请求不同,传统上发送请求需要提交表单或者加载新的地址,而XMLHttpRequest发送请求则完全通过JavaScript代码完成,从而避免了页面的刷新——这也是异步发送请求的核心。

XMLHttpRequest对象包含send方法,用于发送请求。在发送请求之前,应先与请求的URL取得连接,XMLHttpRequest通过open方法打开与请求URL的连接。下面是使用XMLHttpRequest发送请求的JavaScript代码:

function sendRequest()

{

    //input是个全局变量,对应于聊天信息的输入文本框

    //调用聊天信息输入文本框的value属性,获取文本框的内容

    var chatMsg = input.value;

    //完成XMLHttpRequest对象的初始化

    createXMLHttpRequest();

    //定义请求的URL变量

    var url = "chat.do";

    //通过open方法取得与服务器的连接

    //本系统发送POST请求

    XMLHttpReq.open("POST", url, true);

    //发送POST请求时,应该增加该文件头

    XMLHttpReq.setRequestHeader("Content-Type","application/x-www-form-

        urlencoded");

    //指定XMLHttpRequest状态改变时的处理函数

    XMLHttpReq.onreadystatechange = processResponse;

    //发送请求后,将聊天信息的输入文本框清空

    input.value="";

    //发送请求,send的参数包含许多key-value对

    //即以“请求参数名=请求参数值”的形式发送请求参数

    XMLHttpReq.send("chatMsg=" + chatMsg); // 发送请求

}

上面的代码使用open方法打开与服务器URL的连接。因为本系统采用POST发送请求参数,因此在请求里增加了Content-Type请求头,并将该请求头的值设为“application/x-www- form-urlencoded”,这是为了保证对请求参数采用合适的格式进行发送。下面是使用XMLHttpRequest发送请求的步骤:

       使用open方法连接服务器URL。

       调用setRequestHeader方法为请求设置合适的请求头。根据不同的请求,可能需要设置不同的请求头。

       使用回调函数。所谓回调函数,就是用于检测XMLHttpRequest状态的函数,当XMLHttpRequest状态发生改变时,该函数将自动执行。

       执行send方法发送请求。

2  解决多余刷新的问题

多余刷新在本聊天室的副作用还不是十分明显,因为本系统的界面修饰相当简陋,没有多余的图片等页面资源。即使对于如此简陋的界面,一样可以对比两种模式下数据的流量。

对于上面的JSP聊天室,控制器处理用户请求后,转发到另一个JSP页面来显示处理结果;而对于Ajax的应用,控制器可以不再转发请求,因为仅需要生成较少数据的响应,控制器可以自己生成响应数据,此时服务器生成的不再是页面内容,而仅是必需的数据。

Ajax主要用于改善用户体验,是一种表现层技术,并不会影响到底层的技术。对于J2EE应用而言,使用Ajax并不需要对中间层的任何组件做任何修改,更不需要对底层的DAO对象、Domain Object进行修改。使用Ajax和使用Hibernate,IBAITIS或者Spring等框架没有任何冲突,结合Ajax技术后的J2EE应用将更加完美,带给用户更好的体验。Ajax也可以与Struts,WebWork和JSF等框架结合使用。事实上,Struts和JSF将在未来的版本中提供对Ajax更好的支持。

对于本系统而言,系统的业务逻辑组件ChatService没有任何改变,此处不再赘述。控制器ChatServlet则有了简单的改变:对于Ajax系统而言,服务器响应的不再是整个页面内容,而仅是必需的数据,ChatServlet不能将请求转发到chat.jsp页面。此处,ChatServlet有两个选择:

直接生成简单的响应数据。

转向一个简单的JSP页面,使用JSP页面生成简单的响应。

本节将给出两种实现方式,用户可根据自己的需求进行选择。

2.1  直接使用控制器生成响应数据

在这种模式下,Servlet直接通过response获取页面输出流,通过输出流生成字符响应。在这种方式下,无须转发请求,系统处理更加简单:

//聊天使用的Serlvet,继承HttpServlet

public class ChatServlet extends HttpServlet

{

    //Servlet所使用的服务响应方法

    public void service(HttpServletRequest request, HttpServletResponse response)

        throws IOException, ServletException

    {

        //设置编码方式,XMLHttpRequest对象总采用UTF-8方式发送请求

        request.setCharacterEncoding("UTF-8");

        //获取请求参数:聊天信息

        String msg = request.getParameter("chatMsg");

        //如果聊天信息不为空

        if ( msg != null && !msg.equals(""))

        {

            //通过session获取当前的聊天用户

              String user = (String)request.getSession(true).getAttribute("user");

            //将聊天信息添加到系统当前的聊天记录中

              ChatService.instance().addMsg(user, msg);

        }

        //设置中文输出流

        response.setContentType("text/html;charset=GBK");

        //获取页面输出流

        PrintWriter out =  response.getWriter();

        //将当前系统的聊天记录输出到页面

        out.println(ChatService.instance().getMsg());

    }

}

该Servlet是一个非常简单的Servlet,获取请求参数,调用ChatService对象的业务方法,输出所有的聊天记录,但请注意该Servlet与完全生成视图的Servlet的区别:该Servlet没有生成任何HTML标签,没有生成任何页面效果。在这种情况下,也可以使用Servlet来生成客户响应。上面的代码有两个值得注意的地方:

Ajax使用XMLHttpRequest发送请求,XMLHttpRequest发送请求时,所有参数都以UTF-8编码方式发送,因此request的setCharacterEncoding方法设置解码方式。通过设置UTF-8的解码方式,才可以正确获取所有的请求参数。

生成响应时,一定要使用response的setContentType方法设置页面内容和编码方式。尤其值得注意的是:不能仅使用response.setHeader("Charset","GBK")语句。仅使用该语句,系统采用GBK编码,但并没有确保页面是普通的HTML页面。因为本书是在简体中文Windows环境下开发本系统的,故采用GBK编码方式。

对于上面的控制器而言,虽然生成了表现层内容,但并未完整地生成JSP页面,而是返回了模型数据,因而无须使用额外的JSP页面。

因为该响应数据是普通文本数据,而且相当简单,因而可以直接使用控制器生成客户端响应。但如果需要生成的响应非常复杂,即响应生成的内容量大,而且具有丰富的表现格式,则应该考虑将请求转发到JSP页面,让JSP页面负责生成响应。对于是否需要由JSP生成响应,不可一概而论,而应取决于响应的数据量以及表现格式。

2.2  控制器转发到简单JSP页面生成响应

对于当前范例,这种做法是多此一举,控制器将请求转发到另外的JSP页面,而JSP页面仅负责输出聊天信息,下面是这种用法下的控制器代码:

public class ChatServlet  extends HttpServlet

{

    public void service(HttpServletRequest request, HttpServletResponse

        response) throws IOException, ServletException

    {

        //设置解码格式

        request.setCharacterEncoding("UTF-8");

        //读取用户发送的聊天信息

        String msg = request.getParameter("chatMsg");

        //如果发送的信息不为空

        if ( msg != null && !msg.equals(""))

        {

              String user = (String)request.getSession(true).getAttribute("user");

              ChatService.instance().addMsg(user, msg);

        }

        //将聊天记录设置成request属性

        request.setAttribute("chatList", ChatService.instance().getMsg());

        //转发请求

        forward("/chatreply.jsp", request, response);

    }

    //用于控制forward请求的私有函数

    private void forward(String url, HttpServletRequest request,

        HttpServletResponse response) throws ServletException, IOException

    {

        ServletContext sc = getServletConfig().getServletContext();

        RequestDispatcher rd = sc.getRequestDispatcher(url);

        rd.forward(request,response);

    }

}

控制器将聊天信息设置成request属性,然后在JSP页面中输出该聊天信息。JSP代码如下:

<%@ page contentType="text/html;charset=GBK" errorPage="error.jsp"%>

//输出当前的聊天信息

${requestScope.chatList}

这个JSP页面的作用也相当有限,仅完成简单的输出,因此使用JSP页面并不是十分必要。

3  解析服务器响应

服务器响应生成简单的文本,而XMLHttpRequest包含一个属性:responseText,该属性对应服务器响应生成的文本。在解析服务器响应之前,必须先判断服务器响应是否完成以及响应是否正确,例如,生成404等错误响应是没有意义的。为了判断服务器响应是否完成,响应是否正确,XMLHttpRequest同样提供了两个属性。

readyState:判断服务器响应的状态,其中4表明响应完成。

status:判断服务器响应对应的状态码,其中200表明响应正常,而404表明资源丢失,500表明内部错误等。关于XMLHttpRequest的详细介绍可以参考第9章。

判断完响应状态后,可以使用responseText方法获取服务器响应文本,并将该文本输出到页面显示。下面是解析、处理服务器响应的代码:

//用于处理服务器响应的程序

function processResponse()

{

    //如果服务器响应已经完成

    if (XMLHttpReq.readyState == 4)

    {

        // 判断对象状态,如果服务器生成了正常响应

        if (XMLHttpReq.status == 200)

        {

            //信息已经成功返回,开始处理信息

            //将聊天文本域的内容设置成聊天信息

            document.getElementById("chatArea").value = XMLHttpReq.responseText;

        }

        else

        {

            //页面不正常

            window.alert("您所请求的页面有异常。");

        }

    }

}

此时,浏览器的页面通过JavaScript与服务器的通信基本完成。客户端通过sendRequest函数向服务器发送请求,服务器通过ChatServlet处理用户请求,处理完用户请求后,有两种做法:Servlet直接生成响应,或者将请求转发到JSP页面生成响应。客户端通过processResponse处理服务器响应。

4 何时发送请求

虽然定义了发送请求的方法,但没有定义何时发送请求。根据聊天室的特点,请求应该是需要定时发送的,因为即使本人没有参与聊天,他也希望看到其他人的聊天记录,但该请求与前面介绍的请求存在少许差别,因为这种定时发送的请求无须读取聊天记录,无须发送聊天信息。下面是这种定时发送请求的代码:

//定义定时发送的请求

function sendEmptyRequest()

{

//创建XMLHttpRequest对象

createXMLHttpRequest();

//定义服务器响应的URL

var url = "chat.do";

//建立与服务器的连接

XMLHttpReq.open("POST", url, true);

//设置发送请求的参数格式

XMLHttpReq.setRequestHeader("Content-Type","application/x-www-form-

     urlencoded");

//指定响应处理函数

XMLHttpReq.onreadystatechange = processResponse;

//发送请求

XMLHttpReq.send(null);

setTimeout("sendEmptyRequest()", 800);

}

注意,sendEmptyRequest函数在最后调用了setTimeout("sendEmptyRequest()", 800),setTimeout是JavaScript的计时器,该代码表示系统将在0.8s后再次执行sendEmptyRequest函数。因此,该函数一旦开始执行就不会停止:因为每次函数执行结束后,都将在0.8s后再次调用该函数。自动发送的请求应在进入聊天室后立即发送,因此将该函数定义在页面加载时触发,也就是指定在body load时触发即可。

除此之外,还需要获取用户聊天信息,需发送带参数的请求。这种请求应该定义在单击“提交”按钮或在聊天文本框中按下回车键时发送。要实现按下回车键后发送请求很简单:只需要为该键定义onclick事件即可。如需在文本框中按下回车键时发送请求,则应为聊天文本框指定键盘处理函数,该函数监控文本框的所有键盘事件。键盘处理函数的代码如下:

//键盘处理函数

function enterHandler(event)

{

    //定义键盘中发出事件的键

    var keyCode = event.keyCode ? event.keyCode : event.which ? event.which :

        event.charCode;

    //回车键的代码为13,如果按下了回车键

    if (keyCode == 13)

    {

        sendRequest();

    }

}

整个聊天HTML页面的代码如下:

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

"http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

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

<title>聊天页面</title>

</head>

<!-- body的load事件中发送定时发送的请求 -->

<body onload ="sendEmptyRequest()">

<table width="780" border="1" align="center">

<tr>

<td><p align="center">聊天页面</p>

<!-- 下面定义聊天使用的文本域,该文本域用于显示当前聊天信息 -->

<p align="center">

  <textarea name="chatArea" cols="100" rows="30" readOnly></textarea>

</p>

<div align="center">

<!-- 下面是输入聊天信息所使用的文本,并为onKeyPress事件指定了监听函数 -->

<input name="chatMsg" type="text" size="90" onKeyPress="enterHandler(event);">

<!-- 下面是输入聊天信息的文本框,并为onclick事件指定了监听函数 -->

    <input type="button" name="button" value="提交" onclick="sendRequest();">

</div>

<p>&nbsp; </p>

</td>

</tr>

</table>

<script>

//将输入文本框定义成input变量

var input = document.getElementById("chatMsg");

//将焦点定位在聊天输入框内

    input.focus();

    //系统使用的XMLHttpRequest对象

    var XMLHttpReq;

//创建XMLHttpRequest对象

    function createXMLHttpRequest()

    {

        //对于Mozilla 浏览器

        if(window.XMLHttpRequest)

        {

            //直接使用XMLHttpRequest函数来创建XMLHttpRequest对象

            XMLHttpReq = new XMLHttpRequest();

        }

        //对于IE浏览器

        else if (window.ActiveXObject)

        {

            try

            {

                //使用AcitveXObject函数创建浏览器

                XMLHttpReq = new ActiveXObject("Msxml2.XMLHTTP");

            }

            catch (e)

            {

                //如果出现异常,再次尝试以如下方式创建XMLHttpRequest对象

                try

                {

                    XMLHttpReq = new ActiveXObject("Microsoft.XMLHTTP");

                }

                catch (e)

                {

                }

            }

        }

    }

    function sendRequest()

    {

        //input是个全局变量,对应聊天信息的输入文本框

        //调用聊天信息输入文本框的value属性获取文本框的内容

        var chatMsg = input.value;

        //完成XMLHttpRequest对象的初始化

        createXMLHttpRequest();

        //定义请求的URL变量

        var url = "chat.do";

        //通过open方法取得与服务器的连接

        //本系统发送POST请求

        XMLHttpReq.open("POST", url, true);

        //发送POST请求时应该增加该文件头

        XMLHttpReq.setRequestHeader("Content-Type","application/x-www-

            form-urlencoded");

        //指定XMLHttpRequest状态改变时的处理函数

        XMLHttpReq.onreadystatechange = processResponse;

        //发送请求后,将聊天信息的输入文本框清空

        input.value="";

        //发送请求,send的参数包含许多的key-value对

        //即以“请求参数名=请求参数值”的形式发送请求参数

        XMLHttpReq.send("chatMsg=" + chatMsg); // 发送请求

    }

    //定义定时发送的请求

    function sendEmptyRequest()

    {

        //创建XMLHttpRequest对象

        createXMLHttpRequest();

        //定义服务器响应的URL

        var url = "chat.do";

        //建立与服务器的连接

        XMLHttpReq.open("POST", url, true);

        //设置发送请求的参数格式

        XMLHttpReq.setRequestHeader("Content-Type","application/x-www-

            form-urlencoded");

        //指定响应处理函数

        XMLHttpReq.onreadystatechange = processResponse;

        //发送请求

        XMLHttpReq.send(null);

        setTimeout("sendEmptyRequest()", 800);

    }

//用于处理服务器响应的程序

function processResponse()

{

        //如果服务器响应已经完成

        if(XMLHttpReq.readyState == 4)

        {

            //判断对象状态,如果服务器生成了正常响应

            if (XMLHttpReq.status == 200)

            {

                //信息已经成功返回,开始处理信息

                //将聊天文本域的内容设置成聊天信息

                document.getElementById("chatArea").value = XMLHttpReq.responseText;

              }

            else

            {

                //页面不正常

                  window.alert("您所请求的页面有异常。");

              }

        }

    }

    //键盘处理函数

    function enterHandler(event)

    {

        //定义键盘中发出事件的键

        var keyCode=event.keyCode?event.keyCode:event.which?event.which:

            event.charCode;

        //回车键的代码为13,如果按下了回车键

        if (keyCode == 13)

        {

            sendRequest();

        }

    }

</script>

</body>

</html>

通过上面的设置,基于Ajax的聊天室已基本完成。Ajax聊天室的客户端请求在后台异步发送,客户端读取服务器响应也通过JavaScript完成。整个过程不会阻塞用户的聊天,即使服务器的响应变慢,客户端依然可发送请求或者查看原有的聊天记录,无须等待下载页面。图2.5显示了该聊天页面的运行效果。

图2.5  聊天页面的运行效果

5  Ajax聊天室的特点

虽然JSP聊天室和Ajax聊天室的外观差不多,但用户聊天时可以体会到区别,Ajax聊天室不会重复下载页面,因而不会看到不停下载新页面。正如图2.5所示,不管什么时候,页面的左下脚都显示“完毕”提示。

相对于传统的JSP聊天室而言,Ajax聊天室的速度更快,响应更加流畅。对于复杂页面,Ajax的优势将更加明显:Ajax聊天室只需从服务器获取必须更新的聊天记录,而无须下载整个页面。

 Ajax聊天室的最大特点是页面无须刷新,用户感觉不到页面的下载。使用Ajax聊天室时,用户感觉到仿佛在使用普通的Socket聊天室,因为聊天室的页面无须刷新,但用户的聊天信息实时更新。这一切都依赖于Ajax的异步发送请求和动态更新页面。

打印 收藏 关闭