用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è)小程序就不用精通mysql或sqlserver的“大駕”了。本想用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的用途還是很大的。
好了下面是總體框架

對(duì)了,還有一個(gè)monitor,是用來(lái)維護(hù)用戶(hù)在線狀態(tài)的,因?yàn)橛脩?hù)很有可能不是正常退出,所以這個(gè)monitor作為一個(gè)單獨(dú)的線程對(duì)超時(shí)用戶(hù)進(jìn)行處理。
Service(ChatService.java)是主業(yè)務(wù)類(lèi),其實(shí)它也只是一個(gè)代理類(lèi),實(shí)際業(yè)務(wù)是由ChatEngine.java(hsql)和XmlHelper.java(xml)完成的。
先看一下截圖吧

下面詳細(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>AppRpc< SPAN>display-name>
<welcome-file-list>
<welcome-file>index.jsp< SPAN>welcome-file>
< SPAN>welcome-file-list>
<servlet>
<servlet-name>RpcServer< SPAN>servlet-name>
<servlet-class>org.mstar.appletrpc.rpcserver.RpcServer< SPAN>servlet-class>
<init-param>
<param-name>user-data< SPAN>param-name>
<param-value>/WEB-INF/user.xml< SPAN>param-value>
< SPAN>init-param>
<load-on-startup>1< SPAN>load-on-startup>
< SPAN>servlet>
<servlet-mapping>
<servlet-name>RpcServer< SPAN>servlet-name>
<url-pattern>/xmlrpc< SPAN>url-pattern>
< SPAN>servlet-mapping>
< SPAN>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"/>
< SPAN>user-database>
注意user.xml的路徑是在web.xml中設(shè)定的。
<init-param>
<param-name>user-data< SPAN>param-name>
<param-value>/WEB-INF/user.xml< SPAN>param-value>
< SPAN>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(2000, new RefreshMessageActionListener()).
start();
new Timer(5000, new 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)啊。