applet + xml-rpc + hsqldb + xml实现一个聊天室

昨天公司前辈看我没什么事就托我给他做个小聊天室,估计是别人托他做的L,某个在校学生的作业(其实我也刚从学校里出来,呵呵)。没办法,做吧,其实前辈给了我一个网上下的源程序让我改一下。我看了看,其中有些错误,改了以后,前辈又说要加些功能,可是原来的那个源码使用socket做的,要实现这样的功能,有些麻烦,功能其实很简单,不如自己做个玩玩。于是想到了,以前看到的一些技术和类库,就当练练手吧。

首先要解决的问题是applet如何与server进行通信。发放有很多,但是对于这样的小东西,最好用一个轻量级的实现起来简单的技术,于是我想到了xml-rpc,以前做个用javascript通过xml-rpc与服务端通信(我以前的文章中写过),现在客户端也是Java实现起来就容易多了。Xml-rpc是个技术规范,有很多实现,现在客户端和服务器都用Java,当然要用apache的实现。

接下来要解决的问题是用户数据库如何实现,这样一个小程序就不用精通mysqlsqlserver的“大驾”了。本想用hsqldb一起实现的了,但是想了想,不易管理,用户要想更改用户,hsqldb没有一个很好的工具,而且,用户数据库,其实就是一张用户表,有没有大的访问量,完全可以用xml来储存。于是我还是决定用dom4j来操作揖个user.xml来实现。

下面是聊天信息的中转,和在线状态的维护。其实这个自己写一个类来实现也可以,无非就是维护一个map,但是想到自己还是没有那么高的水平一定能把这个做好,莫不如用内存数据库来实现,效率也不错,操作也方便。这时hsqldb就派上用场了。其实hsqldb的用途还是很大的。

好了下面是总体框架
design.jpg

对了,还有一个monitor,是用来维护用户在线状态的,因为用户很有可能不是正常退出,所以这个monitor作为一个单独的线程对超时用户进行处理。

ServiceChatService.java)是主业务类,其实它也只是一个代理类,实际业务是由ChatEngine.javahsql)和XmlHelper.javaxml)完成的。

先看一下截图吧
appletrpc.jpg
下面详细的介绍一下,每部分的实现

首先是业务逻辑类ChatService.java

package org.mstar.appletrpc.rpcserver;

import java.util.*;

public class ChatService {
    
private XmlHelper helper;
    
private ChatEngine engine;
    
public ChatService(XmlHelper helper,ChatEngine engine){
        
this.helper = helper;
        
this.engine = engine;
    }
    
public boolean validateUser(String username,String password){
        User user 
= helper.getUser(username);
        
if(user!=null&&user.getPassword()!=null&&user.getPassword().equals(password)){
            
return true;
        } 
else {
            
return false;
        }
    }

    
public boolean existUser(String username){
        
return helper.existUser(username);
    }

    
public boolean addUser(String username,String password,String nickname){
        User user 
= new User();
        user.setUsername(username);
        user.setPassword(password);
        user.setNickname(nickname);
        
return helper.addUser(user);
    }

    
public boolean addMessage(String sender, String receiver, String content) {
        
return engine.addMessage(sender,receiver,content);
    }

    
public Map getMessage(String receiver) {
        
return engine.getMessage(receiver);
    }

    
public boolean logon(String user){
        
return engine.logon(user);
    }

    
public Vector getOnline(){
        
return engine.getOnline();
    }
    
public boolean logoff(String user){
        
return engine.logoff(user);
    }

    
/**
     * checkOnline
     
*/
    
public void checkOnline() {
        engine.checkOnline();
    }
}
这个类是个很普通的类,为底层的业务逻辑实现者做个代理。这里要注意的是由于xml-rpc支持的Type很有限,所以,很多是由返回值得类型要注意一下,数组要用Vector,对象要用HashTable或Map,不能直接返回一个复杂对象类型,如User之类的。
具体实现类就不说了,不复杂,看源码应该看得懂。
写完Service,要做一个RpcServer,其实是个Servlet
RpcServer.java
package org.mstar.appletrpc.rpcserver;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import org.apache.xmlrpc.XmlRpcServer;
import org.dom4j.*;
import java.sql.SQLException;

public class RpcServer extends HttpServlet {
    
private ServletConfig servletConfig;
    
private XmlHelper xmlHelper;
    
private ChatEngine engine;
    
private XmlRpcServer xmlrpc;
    
//Initialize global variables
    public void init(ServletConfig servletConfig) throws ServletException {
        
this.servletConfig = servletConfig;
        String userdata 
= servletConfig.getInitParameter("user-data");
        File file 
= new File(servletConfig.getServletContext().getRealPath(userdata));
        
try {
            xmlHelper 
= new XmlHelper(file);
            engine 
= new ChatEngine();
            ChatService service 
= new ChatService(xmlHelper,engine);
            xmlrpc 
= new XmlRpcServer();
            xmlrpc.addHandler(
"ChatService",service);
            
new OnlineMonitor(service).start();
        } 
catch (DocumentException ex) {
            ex.printStackTrace();
        } 
catch (SQLException ex) {
            ex.printStackTrace();
        } 
catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }

    }

    
//Process the HTTP Post request
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws
            ServletException, IOException {
        
byte[] result = xmlrpc.execute(request.getInputStream());
        response.setContentType(
"text/xml");
        response.setContentLength(result.length);
        OutputStream out 
= response.getOutputStream();
        out.write(result);
        out.flush();
    }

    
//Clean up resources
    public void destroy() {
    }
}
这个类主要做两件事,一是初始化一些业务对象如ChatService和XmlHelper,ChatEngine,启动monitor。
二是将Service放到XmlRpcServer中。使客户端通过xml客户远程调用。
然后就是把这个Servlet注册到web.xml中
xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
  
<display-name>AppRpcdisplay-name>
  
<welcome-file-list>
    
<welcome-file>index.jspwelcome-file>
  
welcome-file-list>
  
<servlet>
    
<servlet-name>RpcServerservlet-name>
    
<servlet-class>org.mstar.appletrpc.rpcserver.RpcServerservlet-class>
    
<init-param>
      
<param-name>user-dataparam-name>
      
<param-value>/WEB-INF/user.xmlparam-value>
    
init-param>
    
<load-on-startup>1load-on-startup>
  
servlet>
  
<servlet-mapping>
    
<servlet-name>RpcServerservlet-name>
    
<url-pattern>/xmlrpcurl-pattern>
  
servlet-mapping>
web-app>

还要有个用户数据库user.xml
xml version="1.0" encoding="GBK"?>
<user-database>
    
<user id="mty1" password="123" nickname="马天一1"/>
    
<user id="mty2" password="123" nickname="马天一2"/>
    
<user id="mty3" password="123" nickname="马天一3"/>
    
<user id="mty4" password="123" nickname="马天一4"/>
user-database>
注意user.xml的路径是在web.xml中设定的。
    <init-param>

      
<param-name>user-dataparam-name>
      
<param-value>/WEB-INF/user.xmlparam-value>
    
init-param>
接下来就是用Applet调用这个Servlet了。
太长了,就不写在页面上了,源码中有,这里写一个例子,登陆的actionPerformed方法

public void actionPerformed(ActionEvent e) {
            Vector params 
= new Vector();
            params.addElement(useridTextField.getText());
            params.addElement(
new String(pwdField.getPassword()));
            Boolean result 
= new Boolean(false);
            
try {
                XmlRpcClient xmlrpc 
= new XmlRpcClient(getCodeBase().toString() +
                        
"xmlrpc");
                result 
= (Boolean) xmlrpc.execute("ChatService.validateUser",
                                                  params);
                
if (result.booleanValue()) {
                    params 
= new Vector();
                    params.addElement(useridTextField.getText());
                    result 
= (Boolean) xmlrpc.execute("ChatService.logon",
                            params);
                    
if (result.booleanValue()) {
                        userid 
= useridTextField.getText();
                        appendMessage(userid 
+ ": 您已经登陆成功");
                        resetUserList();
                        
new Timer(2000new RefreshMessageActionListener()).
                                start();
                        
new Timer(5000new RefreshListActionListener()).
                                start();
                    }
                } 
else {
                    appendMessage(
"用户名密码错误,登陆失败");
                }
            } 
catch (IOException ex) {
                ex.printStackTrace();
            } 
catch (XmlRpcException ex) {
                ex.printStackTrace();
            }
        }

xmlrpc.execute("ChatService.validateUser",params);
execute方法是xml-rpc client执行远程调用的方法。
第一个参数是方法名,第二个是这个远程方法的参数,用Vector传递。
基本上就是这样一个流程。一点都不复杂,有什么不明白的就给我发信,欢迎讨论。
源码下载:
http://www.blogjava.net/Files/mstar/AppletRPC.rar
项目是用JBuilder2006做的,不知道以前版本的JBuilder能不能打开啊。