applet + xml-rpc + hsqldb + xml實現(xiàn)一個聊天室

昨天公司前輩看我沒什么事就托我給他做個小聊天室,估計是別人托他做的L,某個在校學(xué)生的作業(yè)(其實我也剛從學(xué)校里出來,呵呵)。沒辦法,做吧,其實前輩給了我一個網(wǎng)上下的源程序讓我改一下。我看了看,其中有些錯誤,改了以后,前輩又說要加些功能,可是原來的那個源碼使用socket做的,要實現(xiàn)這樣的功能,有些麻煩,功能其實很簡單,不如自己做個玩玩。于是想到了,以前看到的一些技術(shù)和類庫,就當(dāng)練練手吧。

首先要解決的問題是applet如何與server進行通信。發(fā)放有很多,但是對于這樣的小東西,最好用一個輕量級的實現(xiàn)起來簡單的技術(shù),于是我想到了xml-rpc,以前做個用javascript通過xml-rpc與服務(wù)端通信(我以前的文章中寫過),現(xiàn)在客戶端也是Java實現(xiàn)起來就容易多了。Xml-rpc是個技術(shù)規(guī)范,有很多實現(xiàn),現(xiàn)在客戶端和服務(wù)器都用Java,當(dāng)然要用apache的實現(xiàn)。

接下來要解決的問題是用戶數(shù)據(jù)庫如何實現(xiàn),這樣一個小程序就不用精通mysqlsqlserver的“大駕”了。本想用hsqldb一起實現(xiàn)的了,但是想了想,不易管理,用戶要想更改用戶,hsqldb沒有一個很好的工具,而且,用戶數(shù)據(jù)庫,其實就是一張用戶表,有沒有大的訪問量,完全可以用xml來儲存。于是我還是決定用dom4j來操作揖個user.xml來實現(xiàn)。

下面是聊天信息的中轉(zhuǎn),和在線狀態(tài)的維護。其實這個自己寫一個類來實現(xiàn)也可以,無非就是維護一個map,但是想到自己還是沒有那么高的水平一定能把這個做好,莫不如用內(nèi)存數(shù)據(jù)庫來實現(xiàn),效率也不錯,操作也方便。這時hsqldb就派上用場了。其實hsqldb的用途還是很大的。

好了下面是總體框架
design.jpg

對了,還有一個monitor,是用來維護用戶在線狀態(tài)的,因為用戶很有可能不是正常退出,所以這個monitor作為一個單獨的線程對超時用戶進行處理。

ServiceChatService.java)是主業(yè)務(wù)類,其實它也只是一個代理類,實際業(yè)務(wù)是由ChatEngine.javahsql)和XmlHelper.javaxml)完成的。

先看一下截圖吧
appletrpc.jpg
下面詳細(xì)的介紹一下,每部分的實現(xiàn)

首先是業(yè)務(wù)邏輯類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();
    }
}
這個類是個很普通的類,為底層的業(yè)務(wù)邏輯實現(xiàn)者做個代理。這里要注意的是由于xml-rpc支持的Type很有限,所以,很多是由返回值得類型要注意一下,數(shù)組要用Vector,對象要用HashTable或Map,不能直接返回一個復(fù)雜對象類型,如User之類的。
具體實現(xiàn)類就不說了,不復(fù)雜,看源碼應(yīng)該看得懂。
寫完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() {
    }
}
這個類主要做兩件事,一是初始化一些業(yè)務(wù)對象如ChatService和XmlHelper,ChatEngine,啟動monitor。
二是將Service放到XmlRpcServer中。使客戶端通過xml客戶遠(yuǎn)程調(diào)用。
然后就是把這個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>

還要有個用戶數(shù)據(jù)庫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中設(shè)定的。
    <init-param>

      
<param-name>user-dataparam-name>
      
<param-value>/WEB-INF/user.xmlparam-value>
    
init-param>
接下來就是用Applet調(diào)用這個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 
+ ": 您已經(jīng)登陸成功");
                        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執(zhí)行遠(yuǎn)程調(diào)用的方法。
第一個參數(shù)是方法名,第二個是這個遠(yuǎn)程方法的參數(shù),用Vector傳遞。
基本上就是這樣一個流程。一點都不復(fù)雜,有什么不明白的就給我發(fā)信,歡迎討論。
源碼下載:
http://www.tkk7.com/Files/mstar/AppletRPC.rar
項目是用JBuilder2006做的,不知道以前版本的JBuilder能不能打開啊。