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

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

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

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

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

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

對(duì)了,還有一個(gè)monitor,是用來(lái)維護(hù)用戶(hù)在線狀態(tài)的,因?yàn)橛脩?hù)很有可能不是正常退出,所以這個(gè)monitor作為一個(gè)單獨(dú)的線程對(duì)超時(shí)用戶(hù)進(jìn)行處理。

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

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

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

還要有個(gè)用戶(hù)數(shù)據(jù)庫(kù)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>
接下來(lái)就是用Applet調(diào)用這個(gè)Servlet了。
太長(zhǎng)了,就不寫(xiě)在頁(yè)面上了,源碼中有,這里寫(xiě)一個(gè)例子,登陸的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(
"用戶(hù)名密碼錯(cuò)誤,登陸失敗");
                }
            } 
catch (IOException ex) {
                ex.printStackTrace();
            } 
catch (XmlRpcException ex) {
                ex.printStackTrace();
            }
        }

xmlrpc.execute("ChatService.validateUser",params);
execute方法是xml-rpc client執(zhí)行遠(yuǎn)程調(diào)用的方法。
第一個(gè)參數(shù)是方法名,第二個(gè)是這個(gè)遠(yuǎn)程方法的參數(shù),用Vector傳遞。
基本上就是這樣一個(gè)流程。一點(diǎn)都不復(fù)雜,有什么不明白的就給我發(fā)信,歡迎討論。
源碼下載:
http://www.tkk7.com/Files/mstar/AppletRPC.rar
項(xiàng)目是用JBuilder2006做的,不知道以前版本的JBuilder能不能打開(kāi)啊。