Java Web开发——在线人数统计程序

  • 来源: 程序员之家 作者: sevenleaf   2010-04-16/11:23
  •       下面,我们利用HttpSessionBindingListener接口Java语言,编写一个在线人数统计的程序。当一个用户登录后,显示欢迎信息,同时显示出当前在线的总人数和用户名单。当一个用户退出登录或者Session超时值发生时,从在线用户名单中删除这个用户,同时将在线的总人数减1。这个功能的完成,主要是利用一个实现了HttpSessionBindingListener接口的对象,当这个对象被绑定到Session中或者从Session中被删除时,更新当前在线的用户名单。实例的开发主要有以下步骤。
          Step1:配置Web应用程序的运行目录  
          在%CATALINA_HOME%\conf\Catalina\localhost\目录下新建ch15.xml文件,输入如例15-4所示的内容。
          例15-4  ch15.xml
    <Context docBase="F:\JSPLesson\ch15" reloadable="true"/>
          Step2:编写login.html 
          将编写好的login.html文件放到F:\JSPLesson\ch15\online目录下。完整的代码如例15-5所示。 
          例15-5  login.html
    <html>
        <head>
            <title>登录页面</title>
        </head>
        <body>
            <form action="online" method="post">
                <table>
                    <tr>
                        <td>请输入用户名:</td>
                        <td><input type="text" name="user"></td>
                    </tr>
                    <tr>
                        <td>请输入密码:</td>
                        <td><input type="password" name="password"></td>
                    </tr>
                    <tr>
                        <td><input type="reset" value="重填"></td>
                        <td><input type="submit" value="登录"></td>
                    </tr>
                </table>
            </form>
        </body>
    </html>
          Step3:编写UserList.java,User.java,OnlineUserServlet.java和LogoutServlet.java 
          为了和本章其他例子中的类相区别,本例中的类定义在org.sunxin.ch15.online包中。编写UserList.java,User.java,OnlineUserServlet.java和LogoutServlet.java源文件,将编写好的源文件放到F:\JSPLesson\ch15\src\online目录下。
          UserList.java的完整代码如例15-6所示。
          例15-6  UserList.java
    1.package org.sunxin.ch15.online;
    2.
    3.import java.util.Vector;
    4.import java.util.Enumeration;
    5.
    6.public class UserList
    7.{
    8.    private static final UserList userList=new UserList();
    9.    private Vector<String> v;
    10.   
    11.   private UserList()
    12.   {
    13.        v=new Vector<String>();
    14.   }
    15.
    16.   public static UserList getInstance()
    17.   {
    18.        return userList;
    19.   }
    20.   
    21.   public void addUser(String name)
    22.   {
    23.        if(name!=null)
    24.            v.addElement(name);
    25.   }
    26.   
    27.   public void removeUser(String name)
    28.   {
    29.        if(name!=null)
    30.              v.remove(name);
    31.   }
    32.   
    33.   public Enumeration<String> getUserList()
    34.   {
    35.        return v.elements();
    36.   }
    37.   
    38.   public int getUserCount()
    39.   {
    40.        return v.size();
    41.   }
    42.}
          在UserList这个类的设计上,我们应用了单例(Singleton)设计模式,关于设计模式的知识,读者可以参看相关的书籍。UserList是一个单例类,所谓单例类,是指一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。单例类的一个最重要的特点是类的构造方法是私有的,从而避免了外部利用该类的构造方法直接创建多个实例。在代码的第8行,定义一个静态的常量userList,它表示了UserList类的一个对象。在UserList类加载的时候,这个对象就产生了。第11~14行,声明UserList类的构造方法为private,这是为了避免在外部使用UserList类的构造方法创建其对象。要注意的是,如果在类中不写构造方法,那么Java编译器就会为这个类提供一个默认的不带参数的公开的构造方法,这样,在外部就可以通过类的构造方法创建对象了,那么UserList也就不再是一个单例类了。既然UserList类的构造方法是私有的,那么在外部就不能用new去构造对象,于是在代码的第16~19行,定义了一个静态的方法getInstance(),在这个方法中,返回在类加载时创建的UserList类的对象。因为getInstance()方法本身是静态的,所以可以直接通过类名来调用。
          那么为什么要将UserList设计成单例类呢?这是因为UserList类的对象是用于存储和获取在线用户的列表,而这个用户列表对于所有的页面来说都应该是同一个,所以将UserList类设计成单例类,这样,所有的类访问的就是同一个UserList对象了。
          代码的第9行,定义了一个私有的Vector类型的变量,在UserList类的构造方法中,对Vector类型的变量v进行了初始化,用于存放String类型的对象。注意,在这个地方没有使用ArrayList,是考虑到UserList对象可能会被多个线程同时访问,因为ArrayList不是同步的,而Vector是同步的,所以采用Vector来保存用户列表。
          User.java的完整代码如例15-7所示。
          例15-7  User.java
    1. package org.sunxin.ch15.online;
    2.
    3. import javax.servlet.http.HttpSessionBindingListener;
    4. import javax.servlet.http.HttpSessionBindingEvent;
    5.
    6. public class User implements HttpSessionBindingListener
    7. {
    8.      private String name;
    9.      private UserList ul=UserList.getInstance();
    10.   
    11.     public User()
    12.     {
    13.     }
    14.     public User(String name)
    15.     {
    16.          this.name=name;
    17.     }
    18.     public void setName(String name)
    19.     {
    20.          this.name=name;
    21.     }
    22.     public String getName()
    23.     {
    24.          return name;
    25.     }
    26.     public void valueBound(HttpSessionBindingEvent event)
    27.     {
    28.          ul.addUser(name);
    29.     }
    30.     public void valueUnbound(HttpSessionBindingEvent event)
    31.     {
    32.          ul.removeUser(name);
    33.     }
    34.}
          User类实现了HttpSessionBindingListener接口,表示登录的用户。代码第9行,通过UserList类的静态方法getInstance()得到UserList类的对象,第26~29行,当User对象加入到Session中时,Servlet容器将调用valueBound()方法,我们将用户的名字保存到用户列表中。第30~33行,当User对象从Session中被删除时,Servlet容器将调用valueUnbound()方法,我们从用户列表中删除该用户。
          OnlineUserServlet.java的完整代码如例15-8所示。
          例15-8  OnlineUserServlet.java
    1.package org.sunxin.ch15.online;
    2.
    3.import javax.servlet.*;
    4.import java.io.*;
    5.import javax.servlet.http.*;
    6.import java.util.Enumeration;
    7.
    8.public class OnlineUserServlet extends HttpServlet
    9.{
    10.    public void doGet(HttpServletRequest req, HttpServletResponse resp)
    11.                     throws ServletException,IOException
    12.    {
    13.        req.setCharacterEncoding("gb2312");
    14.        String name=req.getParameter("user");
    15.        String pwd=req.getParameter("password");
    16.           
    17.        if(null==name || null==pwd || name.equals("") || pwd.equals(""))
    18.        {
    19.              resp.sendRedirect("login.html");
    20.        }
    21.        else
    22.        {
    23.              HttpSession session=req.getSession();
    24.              User user=(User)session.getAttribute("user");
    25.              if(null==user || !name.equals(user.getName()))
    26.              {
    27.                  user=new User(name);
    28.                  session.setAttribute("user",user);
    29.              }
    30.           
    31.              resp.setContentType("text/html;charset=gb2312");
    32.              PrintWriter out=resp.getWriter();
    33.           
    34.              out.println("欢迎用户<b>"+name+"</b>登录");
    35.              UserList ul=UserList.getInstance();
    36.              out.println("<br>当前在线的用户列表:<br>");
    37.              Enumeration<String> enums=ul.getUserList();
    38.              int i=0;
    39.              while(enums.hasMoreElements())
    40.              {
    41.                  out.println(enums.nextElement());
    42.                  out.println("&nbsp;&nbsp;&nbsp;&nbsp;");
    43.                  if(++i==10)
    44.                  {
    45.                       out.println("<br>");
    46.                  }
    47.              }
    48.              out.println("<br>当前在线的用户数:"+i);
    49.              out.println("<p><a href=logout>退出登录</a>");
    50.              out.close();
    51.        }
    52.    }
    53.   
    54.    public void doPost(HttpServletRequest req, HttpServletResponse resp)
    55.                    throws ServletException,IOException
    56.    {
    57.        doGet(req,resp);
    58.    }
    59.}
          OnlineUser类用于向用户显示欢迎信息、当前在线用户列表和在线用户数。代码的第24~29行,首先从Session中获取名为user的属性对象,通过判断它是否为空来判断在此次会话中用户是否已经登录。如果user对象不为null,那么接着判断在同一个会话中,用户是否更换了一个用户名登录。如果user对象为空或者当前登录的用户名和先前登录的用户名不同,则以用户的当前登录名创建一个User对象,将这个对象绑定到Session中,这个时候,Servlet容器就会调用User对象的valueBound()方法,将这个用户的名字保存在用户列表中。第35行,得到UserList类的对象。第37行,得到用户列表的枚举对象。第39~47行,循环取出在线用户的名字并输出,一旦输出超过10个用户名,就输出一个换行(<br>)。第48行,输出当前在线的用户数。第49行,输出“退出登录”的链接。
          LogoutServlet.java的完整代码如例15-9所示。
          例15-9  LogoutServlet.java
    1. package org.sunxin.ch15.online;
    2.
    3. import javax.servlet.*;
    4. import java.io.*;
    5. import javax.servlet.http.*;
    6.
    7. public class LogoutServlet extends HttpServlet
    8. {
    9.     public void doGet(HttpServletRequest req, HttpServletResponse resp)
    10.               throws ServletException,IOException
    11.   {
    12.      resp.setContentType("text/html;charset=gb2312");
    13.       
    14.      HttpSession session=req.getSession();
    15.      User user=(User)session.getAttribute("user");
    16.      session.invalidate();
    17.       
    18.      PrintWriter out=resp.getWriter();
    19.      out.println("<html><head><title>退出登录</title></head><body>");
    20.      out.println(user.getName()+",你已退出登录<br>");
    21.      out.println("<a href=login.html>重新登录</a>");
    22.      out.println("</body></html>");
    23.      out.close();
    24.   }
    25.}
          LogoutServlet类用于退出登录。代码第16行,调用HttpSession对象的invalidate()方法,使Session失效,从而删除绑定到这个Session中的User对象,Servlet容器就会调用这个User对象的valueUnbound()方法,从用户列表中删除该用户。
          Step4:编译上述四个Java源文件 
          打开命令提示符,进入到源文件所在的目录F:\JSPLesson\ch15\src\online下,然后执行
          javac -d ..\..WEB-INF\classes *.java
          在WEB-INF\classes目录下生成4个源文件对应的包和类文件。
          Step5:部署Servlet 
          编辑WEB-INF目录下的web.xml文件,添加对本例中的Servlet的配置,内容如例15-10所示。
          例15-10  web.xml

    <servlet>
        <servlet-name>OnlineUserServlet</servlet-name>
        <servlet-class>
    org.sunxin.ch15.online.OnlineUserServlet
    </servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>OnlineUserServlet</servlet-name>
        <url-pattern>/online/online</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>logout</servlet-name>
        <servlet-class>org.sunxin.ch15.online.LogoutServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>logout</servlet-name>
        <url-pattern>/online/logout</url-pattern>
    </servlet-mapping>

          实现HttpSessionBindingListener接口的监听器类不需要在web.xml中进行配置。
          Step6:运行在线人数统计程序
          启动Tomcat服务器,打开IE浏览器,首先在地址栏中输入
          http://localhost:8080/ch15/online/login.html
          出现登录页面后,输入用户名和密码,将看到如图15-1所示的页面。
          读者可以再打开一个浏览器,输入http://localhost:8080/ch15/online/login.html,然后登录,将看到如图15-2所示的页面。     

       

    图15-1  OnlineUserServlet显示一个用户在线 图15-2  OnlineUserServlet显示有两个用户在线

          读者可以退出其中一个用户的登录,然后刷新另外一个窗口中的页面,可以看到显示的在线用户数为1。读者可以多打开几个浏览器,进行测试。
          在线人数统计程序存在的问题:在第5.2.4节“Session和Cookie的深入研究”中介绍过,如果用户没有退出登录而直接关闭了浏览器,那么在服务器端的Session中,这个用户仍然是存在的,直到Session的超时值发生。所以在线人数统计只能做到在一个时间段内统计出大致的在线人数,而不能统计出精确的人数。为了提高统计的精确性,可以在客户端设置脚本,当浏览器关闭时,自动向服务器发送一个请求,服务器收到这个请求后,使Session失效。不过,这也不能做到100%的精确,因为还存在着客户端的浏览器异常终止,或者客户机器崩溃的可能。


    评论 {{userinfo.comments}}

    {{money}}

    {{question.question}}

    A {{question.A}}
    B {{question.B}}
    C {{question.C}}
    D {{question.D}}
    提交

    驱动号 更多