使用RMI构建聊天应用程序的步骤
用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 classpublic 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 sendingpublic 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!输入您的名字,就可以聊侃了,祝贺你!