用RMI构建聊天应用程序: 其基本思想是:多个客户通过APLLET进行聊天,客户的聊天内容分别显示在各自的 TextArea 内,要做到这些,需要做到: 1、客户首先向服务器注册,告知服务器它在监听某主题; 2、客户注册之后,向服务器发送消息; 3、服务器再把消息发送给所有监听此主题的客户;
需要的文件有:
- Chat.java: 客户端远程接口.
- ChatImpl.java: 实现聊天远程接口的APPLET.
- ChatServer.java: 服务器端远程接口.
- ChatServerImpl.java:实现聊天服务器端远程接口的应用程序.
- Message.java: 一个消息对象.
- ServerTalker.java: 缓冲客户和服务器通信的线程.
- Talker.java: 缓冲服务器和客户通信的线程.
- NameDialog.java: 输入客户名字的对话框.
文件包结构: (除NameDialog.java放到$home/java/examples/util里外,其余的都放到$home/java/examples/chat)
· $home/java/examples/chat
· $home/java/examples/util
用RMI构建聊天应用程序实现过程的三个步骤:
· 定义远程接口.
· 服务器类聊天服务器的实现.
· 服务器类客户端的实现.
一、定义远程接口
1、定义聊天服务器远程接口: package examples.chat; import java.rmi.*;
public interface ChatServer extends Remote { // register chatter with server public void register(Chat c, String name) throws RemoteException;
// unregister chatter with server public void unregister(String name) throws RemoteException;
// post messages to the server for broadcast public void postMessage(Message m) throws RemoteException;
// list chatter names currently logged into server public String[] listChatters() throws RemoteException; }
2、定义客户端远程接口: package examples.chat; import java.rmi.*;
public interface Chat extends Remote { public void chatNotify(Message m) throws RemoteException; }
二、服务器类聊天服务器的实现: 1、声明实现远程接口 2、定义远程对象的构造函数 3、实现能远程调用的方法 4、创建一个远程对象的实例并注册
package examples.chat; import java.rmi.*; import java.rmi.server.*; import java.rmi.registry.*; import java.util.*;
public class ChatServerImpl extends UnicastRemoteObject implements ChatServer { private Vector chatters = new Vector();
public ChatServerImpl() throws RemoteException { System.out.println("Initializing Server."); } public static void main(String args[]) { Registry reg;
//Set security manager to allow stub loading over the network System.setSecurityManager(new RMISecurityManager()); try { ChatServerImpl cs = new ChatServerImpl();
//create registry running on port 5050 reg = LocateRegistry.createRegistry(5050); // CREATE REGISTRY
//bind cs in registry reg.bind("ChatServerImpl", cs); System.out.println("Server Ready."); } catch (AlreadyBoundException e) { System.out.println("Name is already bound: " + e); System.exit(0); } catch (RemoteException e) { System.out.println("General Server Error: " + e); System.exit(0); } } synchronized public void register(Chat c, String name) { chatters.addElement(new Talker(c, name)); } synchronized public void unregister(String name) { Talker c; for (int i = 0; i < chatters.size(); i++) { c = (Talker) chatters.elementAt(i); if (name.equals(c.getChatterName())) { chatters.removeElementAt(i); return; } } } public String[] listChatters() { String list[] = new String[chatters.size()]; Talker c;
for (int i = 0; i < list.length; i++) { c = (Talker) chatters.elementAt(i); list[i] = c.getChatterName(); } return list; } synchronized public void postMessage(Message m) { Talker t;
for (int i = 0; i < chatters.size(); i++) { t = (Talker) chatters.elementAt(i); if (!t.addMessage(m)) //remove Talker, if add failed chatters.removeElementAt(i); } } }
下面是Talker ,是个线程类, ChatServerImpl.java 使用它来实现消息异步通信
package examples.chat; import java.util.*; import java.rmi.*;
public class Talker extends Thread { private Vector messages = new Vector(); private Chat c; boolean isActive = true; private String name;
public Talker(Chat C, String N) { c = C; name = N; start(); } public boolean addMessage(Message e) { if (!isActive) return false; synchronized (messages) { messages.addElement(e); } resume(); return true; } public void run() { while (true) { try { if (messages.isEmpty()) suspend(); synchronized (messages) { c.chatNotify((Message) messages.elementAt(0)); messages.removeElementAt(0); } } catch (RemoteException e) { //connection down; kill thread System.out.println("Removing " + name); isActive = false; // why is this necessary? stop(); } yield(); // let other threads compete for resources } } public String getChatterName() { return name; } }
下面是Message类,必须实现序列化
package examples.chat; import java.io.*;
public class Message implements Serializable { private String sender; private String message;
public Message(String sender, String message) { this.sender = sender; this.message = message; } public String getSender() { return sender; } public String getMessage() { return message; } }
三、服务器类客户端的实现
这是客户端类,同时也是SERVER类 package examples.chat; import java.rmi.*; import java.rmi.server.*; import java.net.*; import java.awt.*; import java.util.*; import java.applet.*; import java.awt.event.*; // ActionListener interface import examples.util.*; // For NameDialog class
public class ChatImpl extends Applet implements Chat, ActionListener { private TextArea ta; //the main text window private TextField tf; //message input area private ChatServer cs; //reference to Chat method server private String name; //User's name private NameDialog nd; //pop-up to request user name private ServerTalker st; //Thread for handling message sending
public ChatImpl() throws RemoteException { System.out.println("Starting up Chatter."); } public void init() { //set up applet's layout manager this.setLayout(new BorderLayout());
// create Panel for Buttons Panel p = new Panel(); p.setLayout(new FlowLayout()); // set its layout manager
//add buttons to panel Button dc = new Button("Disconnect"), //disconnect from server lt = new Button("List"), //list users ct = new Button("ClearText"); //clear TextField dc.setBackground(Color.pink); //for dramatic effect p.add(lt); p.add(ct); p.add(dc);
//create text widgets & drawing window ta = new TextArea(4, 40); //message window ta.setEditable(false); //read-only window tf = new TextField(40); // text entry field
//add widgets to applet add(ta, "Center"); add(tf, "South"); add(p, "North"); //add button panel //register applet as listener for widget actions lt.addActionListener(this); ct.addActionListener(this); dc.addActionListener(this); tf.addActionListener(this);
//create dialog box for user name nd = new NameDialog( new Frame("Enter Name"), "Enter your name", false); registerChatter(); //register the applet with server } public void registerChatter() { name = nd.getName(); //get name from NameDialog nd.setVisible(false); //get rid of NameDialog nd = null; try { //export our remote methods UnicastRemoteObject.exportObject(this);
//lookup the server's remote object cs = (ChatServer) Naming.lookup( "rmi://lysander.cs.ucsb.edu:5050/ChatServerImpl" ); //register applet with server cs.register(this, name); //start a communication thread st = new ServerTalker(cs, name); } catch (RemoteException e) { System.out.println("Couldn't locate registry."); System.exit(0); } catch (MalformedURLException e) { System.out.println("Bad binding URL: " + e); System.exit(0); } catch (NotBoundException e) { System.out.println("Service not bound."); System.exit(0); } } public void actionPerformed(ActionEvent e) { String s = tf.getText().trim(); if (!s.equals("")) //message entered? { if (!st.addMessage(new Message(name, s))) //failed? ta.append("***Server Error***\n");
tf.setText(""); } else if (e.getActionCommand().equals("ClearText")) ta.setText(""); else if (e.getActionCommand().equals("Disconnect")) { st.addMessage(new Message("*** " + name, "Logged off. Bye.")); try { cs.unregister(name); } catch (RemoteException x) { System.out.println(name + "'s unregister failed:" + x); System.exit(0); } cs = null; System.exit(0); } else if (e.getActionCommand().equals("List")) getUserList(); } public void getUserList() { String users[] = null;
try { users = cs.listChatters(); } catch (RemoteException e) { System.out.println(e); users = new String[1]; users[0] = "***Error"; } //add user names to TextArea for (int i = 0; i < users.length; i++) ta.append("***" + users[i] + "\n"); } public synchronized void chatNotify(Message m) throws RemoteException { ta.append(m.getSender() + ": " + m.getMessage() + "\n"); } }
这是ServerTalker 线程类,负责把消息(messages)发送到聊天服务器:
package examples.chat; import java.util.*; import java.rmi.*;
class ServerTalker extends Thread { private Vector messages = new Vector(); private ChatServer cs;
public ServerTalker(ChatServer cs, String name) { this.cs = cs; //Send a welcome message messages.addElement(new Message("SYSTEM", "Connected " + name)); this.start(); } public boolean addMessage(Message e) { if (cs == null) { System.out.println("Server reference is null."); return false; } resume(); // resume thread, if suspended messages.addElement(e); return true; } public void run() { while (true) { try { if (messages.isEmpty()) suspend(); cs.postMessage((Message) messages.elementAt(0)); messages.removeElementAt(0); } catch (RemoteException e) { System.out.println("Error: Server down? " + e); cs = null; this.stop(); } yield(); } } }
最后,NameDialog工具类,处理事件,这是个比较过时的APPLET,有兴趣的朋友可以改写一下:
package examples.util; import java.awt.*;
public class NameDialog extends Dialog { private TextField tf = new TextField(20); private String value;
public NameDialog(Frame p, String t, boolean modal) { super(p, t, modal); setLayout(new FlowLayout()); this.add(new Label("Enter your name:")); this.add(tf); this.add(new Button("OK")); this.pack(); this.show(); } public boolean handleEvent(Event e) { if (e.target instanceof Button) { if (tf.getText().length() > 1) value = tf.getText().trim(); return true; } return false; } public String getName() { while (value == null) try { Thread.sleep(1); } catch (InterruptedException exception) { System.err.println("Exception: " + exception.toString()); } return value; } }
四、编译和部署: 1、 用javac编译源文件,注意你必须在类路径下编译:
javac -d $HOME/class *.java 2、 用 rmic 产生skeletons and stubs,skeletons and stubs封装了客户和服务器的通讯细节:
rmic -d $HOME/class examples.chat.ChatImpl examples.chat.ChatServerImpl
3、启动服务器和客户端: ·创建HTML文件:
<html> <applet code="examples.chat.ChatImpl.class" width=800 height=400 > </applet> </html> ·启动服务器 windows下: java examples.chat.ChatServerImpl ·运行客户端 一旦启动了服务器,就可以运行客户端,在 $HOME/class/examples/chat:
appletiewer ChatApplet.html ·OK!输入您的名字,就可以聊侃了,祝贺你!

|