最近在学RMI远程调用,今天终于把《Java高级编程:JDK5》中的范例代码调试成功了,现在把我在调试中遇到的问题总结一下,有兴趣的朋友可以看一下,本程序jdk版本为1.6,平台为WinXP。
此范例主要完成一个聊天室的功能,共有四个类、一个嵌入Applet的htm网页和一个安全策略文件,分别是:
RMIChat接口
RMIChatImpl类
ChatUser类
ChatApplet类
test.htm
rmi.policy
为了更详细的叙述,下面结合代码进行讲述,具体的步骤为:
1、定义RMIChat接口
此接口实现必须扩展java.rmi.Remote接口,并且该接口中定义的方法必须抛出RemoteException,代码如下:
import java.rmi.Remote; import java.rmi.RemoteException; import java.util.ArrayList; public interface RMIChat extends Remote{ public ChatUser logIn(String sNickName)throws RemoteException; public boolean logOut(ChatUser cu)throws RemoteException; void sendMessage(String sMessage,ChatUser cu)throws RemoteException; String getMessage()throws RemoteException; ChatUser findUser(String sNickName)throws RemoteException; ArrayList<ChatUser> getUsers()throws RemoteException; int getUserCount()throws RemoteException; }
2,实现RMIChatImpl类
该类是实现RMIChat接口的类。它定义了在RMIChat接口中存在的每一个方法,它的主要目的是作为一个服务来连接RMIClient。此类需要注册到RMI注册表。代码如下:
import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.rmi.Naming; import java.rmi.RMISecurityManager; import java.util.ArrayList; import java.security.*; public class RMIChatImpl extends UnicastRemoteObject implements RMIChat{ public RMIChatImpl()throws RemoteException{ super(); nGUID=0; m_sLastMsg=""; m_alUsers=new ArrayList<ChatUser>(); } public RMIChatImpl(String sServerName)throws RemoteException{ super(); nGUID=0; m_sLastMsg=""; m_sServerName=""; m_alUsers=new ArrayList<ChatUser>(); } public ChatUser logIn(String sNickName)throws RemoteException{ ChatUser cu=this.findUser(sNickName); if(cu!=null){ return cu; } cu=new ChatUser(sNickName,nGUID); m_alUsers.add(cu); nGUID++; return cu; } public boolean logOut(ChatUser cuUser)throws RemoteException{ if(cuUser==null){ return false; } return m_alUsers.remove(cuUser); } public void sendMessage(String sMessage,ChatUser cuUser)throws RemoteException{ if(cuUser!=null){ m_sLastMsg="<"+cuUser.getUserName()+">"+sMessage; } } public String getMessage()throws RemoteException{ return m_sLastMsg; } public ChatUser findUser(String sNickName)throws RemoteException{ if(m_alUsers!=null&&this.getUserCount()>0){ int alSize=m_alUsers.size(); ChatUser cuTemp; for(int i=0;i<alSize;i++){ cuTemp=(ChatUser)m_alUsers.get(i); if(cuTemp!=null){ String sTmp=cuTemp.getUserName(); if(sTmp.equalsIgnoreCase(sNickName)){ return cuTemp; } } } } return null; } public ArrayList<ChatUser> getUsers()throws RemoteException{ return m_alUsers; } public int getUserCount()throws RemoteException{ if(m_alUsers==null){ return 0; } return m_alUsers.size(); } public String getServerName()throws RemoteException{ return m_sServerName; } public static void main(String args[]){ if(System.getSecurityManager()==null){ System.setSecurityManager(new RMISecurityManager()); } try{ RMIChatImpl rmiObj=new RMIChatImpl(); Naming.bind("RMIChatServer",rmiObj); System.out.println("RMIChatServer registered with the RMI registry"); }catch(Exception ex){ System.out.println("RMIChatImpl error:"+ex.getMessage()); ex.printStackTrace(); } } }
这个类的main方法非常重要,他完成RMIChatImpl对象到RMI注册表的的注册,下面对其进行分析:
1、首先它设置一个用于服务器的安全管理器。可以裁剪安全管理器以适应体系结构的需要,这个简单的示例只是使用了默认的系统安全管理。
2、然后它构造一个RMIChatImpl对象,该对象将被注册到RMI注册表。
3、最后,它使用Naming.bind方法将RMIChatImpl对象绑定到注册表中的“RMIChatServer”名称上。客户端可以搜索“RMIChatSe rver”来获得一个远程RMIChatImpl对象。
注意:RMI注册表必须在上述步骤之前启动,以确保对RMI注册表的正确注册操作。下面会有体现。
3、定义ChatUser类
ChatUser类用来存储有关RMIChatImpl服务器用户的特定信息,该类是客户端和服务器端都要用到的。代码如下:
import java.io.Serializable; public class ChatUser implements Serializable{ private String m_sUserName; private int m_nUserID; protected Object clone()throws CloneNotSupportedException{ return super.clone(); } protected void finalize()throws Throwable{ super.finalize(); } public boolean equals(Object arg0){ return super.equals(arg0); } public int hashCode(){ return super.hashCode(); } public ChatUser(String sUserName,int nUserID){ m_sUserName=sUserName; m_nUserID=nUserID; } public int getUserID(){ return m_nUserID; } public void setUserID(int userID){ m_nUserID=userID; } public String getUserName(){ return m_sUserName; } public void setUserName(String userName){ m_sUserName=userName; } }
4、定义ChatApplet类
ChatApplet类是用于客户端的类,该范例中用一个Applet小程序来实现客户端的功能。ChatApplet类并不需要扩展或者实现任何特定的RMI接口或者类,我们知道RMI远程方法调用对于用户来说和调用本地类是没有任何区别的,所以客户端是一个瘦客户端。该类的代码如下:
import javax.swing.*; import java.rmi.Naming; /* * class ChatApplet */ public class ChatApplet extends JApplet implements Runnable { private javax.swing.JPanel jContentPane = null; private javax.swing.JButton jButton = null; private javax.swing.JTextField jTextField = null; private javax.swing.JButton jButton1 = null; private javax.swing.JScrollPane jScrollPane = null; private javax.swing.JList jList = null; private javax.swing.JLabel jLabel = null; private DefaultListModel listModel = null; private RMIChat m_rmiChat = null; private ChatUser m_ChatUser = null; private boolean m_bIsConnected = false; /** * void start() */ public void start() { super.start(); System.out.println("start()"); new Thread(this).start(); } /** * void stop() */ public void stop() { super.stop(); } /** * void run() */ public void run() { String sOldMsg = ""; String sNewMsg = ""; while (true) { if (this.m_bIsConnected) { try { if (this.m_rmiChat != null) { sNewMsg = this.m_rmiChat.getMessage(); if (!sNewMsg.equalsIgnoreCase(sOldMsg)) { listModel.addElement(sNewMsg); sOldMsg = sNewMsg; } } } catch (Exception e) { System.out.println(e.getMessage()); e.printStackTrace(); } } try { Thread.currentThread().sleep(500); } catch (Exception e) { System.out.println(e.getMessage()); e.printStackTrace(); } } } public ChatApplet() { super(); init(); } /** * void init() */ public void init() { this.setSize(300,200); this.setContentPane(getJContentPane()); listModel = new DefaultListModel(); listModel.addElement("Chat Client loaded successfully."); listModel.addElement("Chat messages will appear below."); jList.setModel(listModel); try { // lookup()返回与指定 name 关联的远程对象的引用(一个 stub)。 m_rmiChat = (RMIChat) Naming.lookup("RMIChatServer"); } catch (Exception ex) { System.out.println("ChatApplet error: " + ex.getMessage()); ex.printStackTrace(); } } /** * JPanel getJContentPane() */ private javax.swing.JPanel getJContentPane() { if(jContentPane == null) { java.awt.GridBagConstraints consGridBagConstraints1 = new java.awt.GridBagConstraints(); java.awt.GridBagConstraints consGridBagConstraints9 = new java.awt.GridBagConstraints(); java.awt.GridBagConstraints consGridBagConstraints4 = new java.awt.GridBagConstraints(); java.awt.GridBagConstraints consGridBagConstraints3 = new java.awt.GridBagConstraints(); java.awt.GridBagConstraints consGridBagConstraints2 = new java.awt.GridBagConstraints(); jContentPane = new javax.swing.JPanel(); jContentPane.setLayout(new java.awt.GridBagLayout()); jContentPane.setName("MainPain"); jContentPane.setToolTipText("Chat Client"); consGridBagConstraints2.gridx = 0; consGridBagConstraints2.gridy = 1; consGridBagConstraints2.anchor = java.awt.GridBagConstraints.EAST; consGridBagConstraints3.gridx = 0; consGridBagConstraints3.gridy = 1; consGridBagConstraints3.weightx = 1.0; consGridBagConstraints3.fill = java.awt.GridBagConstraints.VERTICAL; consGridBagConstraints3.gridwidth = 1; consGridBagConstraints3.anchor = java.awt.GridBagConstraints.WEST; consGridBagConstraints4.gridx = 0; consGridBagConstraints4.gridy = 4; consGridBagConstraints4.anchor = java.awt.GridBagConstraints.SOUTHWEST; consGridBagConstraints9.gridx = 0; consGridBagConstraints9.gridy = 0; consGridBagConstraints9.weightx = 1.0; consGridBagConstraints9.weighty = 1.0; consGridBagConstraints9.fill = java.awt.GridBagConstraints.NONE; consGridBagConstraints9.anchor = java.awt.GridBagConstraints.NORTHEAST; consGridBagConstraints1.gridx = 0; consGridBagConstraints1.gridy = 4; consGridBagConstraints1.anchor = java.awt.GridBagConstraints.EAST; consGridBagConstraints1.ipadx = 2; jContentPane.add(getJButton(), consGridBagConstraints2); jContentPane.add(getJTextField(), consGridBagConstraints3); jContentPane.add(getJButton1(), consGridBagConstraints4); jContentPane.add(getJScrollPane(), consGridBagConstraints9); jContentPane.add(getJLabel(), consGridBagConstraints1); } return jContentPane; } /** * JButton getJButton() */ private javax.swing.JButton getJButton() { if (jButton == null) { jButton = new javax.swing.JButton(); jButton.setText("Send"); jButton.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); jButton.setHorizontalTextPosition(javax.swing.SwingConstants.TRAILING); jButton.setPreferredSize(new java.awt.Dimension(65,25)); jButton.setName("btSend"); jButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { if (m_bIsConnected) { if (m_rmiChat != null && m_ChatUser != null) { try { m_rmiChat.sendMessage(jTextField.getText(), m_ChatUser); } catch (Exception ex) { System.out.println(ex.getMessage()); ex.printStackTrace(); } } } System.out.println("send()"); } }); } return jButton; } /** * JTextField getJTextField() */ private javax.swing.JTextField getJTextField() { if (jTextField == null) { jTextField = new javax.swing.JTextField(); jTextField.setPreferredSize(new java.awt.Dimension(236,25)); jTextField.setHorizontalAlignment(javax.swing.JTextField.LEFT); jTextField.setText("change_me"); } return jTextField; } /** * JButton getJButton1() */ private javax.swing.JButton getJButton1() { if (jButton1 == null) { jButton1 = new javax.swing.JButton(); jButton1.setText("Connect"); jButton1.setPreferredSize(new java.awt.Dimension(98,25)); jButton1.setName("btConnect"); jButton1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { if (jButton1.getText().equalsIgnoreCase("Connect")) { // Create user here m_bIsConnected = true; if (m_rmiChat != null) { String sUserName = jTextField.getText(); if (sUserName.equalsIgnoreCase("")) { sUserName = "noname"; } try { m_ChatUser = m_rmiChat.logIn(sUserName); } catch (Exception ex) { System.out.println(ex.getMessage()); ex.printStackTrace(); } } jButton1.setText("Disconnect"); } else { m_bIsConnected = false; jButton1.setText("Connect"); if (m_rmiChat != null && m_ChatUser != null) { try { m_rmiChat.logOut(m_ChatUser); } catch (Exception ex) { System.out.println(ex.getMessage()); ex.printStackTrace(); } } } System.out.println("connect()"); } }); } return jButton1; } /** * JScrollPane getJScrollPane() */ private javax.swing.JScrollPane getJScrollPane() { if (jScrollPane == null) { jScrollPane = new javax.swing.JScrollPane(); jScrollPane.setHorizontalScrollBarPolicy(javax.swing.JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); jScrollPane.setName("jsScrollText"); jScrollPane.setPreferredSize(new java.awt.Dimension(298,150)); jScrollPane.setVerticalScrollBarPolicy(javax.swing.JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); jScrollPane.setSize(298, 150); jScrollPane.setViewportView(getJList()); } return jScrollPane; } /** * JList getJList() */ private javax.swing.JList getJList() { if (jList == null) { jList = new javax.swing.JList(); jList.setName("jlListBox"); } return jList; } /** * JLabel getJLabel() */ private javax.swing.JLabel getJLabel() { if (jLabel == null) { jLabel = new javax.swing.JLabel(); jLabel.setText("Enter nickname, click connect."); jLabel.setName("jlLabel"); } return jLabel; } }
5、下面定义test.htm网页的内容
<html>
<body>
<applet CODE=ChatApplet.class width="300" height="200" >
</applet>
</body>
</html>
6、编写策略文件,命名为rmi.policy,文件内容很简单,如下:
grant {
permission java.net.SocketPermission "*:1024-65535", "accept,connect,listen,resolve"; //(1.1)
permission java.net.SocketPermission "*:80", "accept,connect,listen,resolve"; //(1.2)
//上面两行代码也可以不要,我自己试了不要也可以,但是网上有篇文章说必须在AllPermission的基础上加这两行,不知道为什么读者可以自己试一下
permission java.security.AllPermission;
};
7、编译文件并测试
7.1将上面的五个文件放到D:/chat/文件夹下面,分别编译RMIChat.java,RMIChatImpl.java,ChatUser.java和ChatApplet.java。
7.2运行java的rmic工具,在命令提示符窗口输入“rmic RMIChatImpl”单击回车,生成对应RMIChatImpl的存根文件RMIChatImpl_Stub.class。
7.3启动RMI注册表,这一步是必须的,命令格式为start rmiregistry。
7.4运行服务器端程序RMIChatImpl,命令如下:
D:/chat> java -Djava.security.policy=file:./rmi.policy -Djava.rmi.server.codebase=file:./RMIChatImpl RMIChatImpl
7.5运行test.htm文件,测试该聊天室程序。开两个命令提示符窗口,分别输入下面的命令,然后进行聊天室验证。
D:/chat>appletviewer test.htm
如果你的Applet小程序可以成功通信,恭喜,你成功了,你已经成功的创建了一个RMI系统,并且使它正确工作了。即使你运行在同一个计算机上,RMI还是使用了你的网络堆栈和TCP/IP去进行通讯,并且是运行在三个不同的Java虚拟机上。这已经是一个完整的RMI系统。
8、问题总结
8.1问题一
刚开始反复编译,出现如下错误
D:/chat> java -Djava.security.policy=file:./rmi.policy -Djava.rmi.server.codebase=file:./RMIChatImpl RMIChatImpl
RMIserver erroraccess denied (java.net.SocketPermission 127.0.0.1:1099 conne
esolve)
java.security.AccessControlException: access denied (java.net.SocketPermissi
27.0.0.1:1099 connect,resolve)
原因为我的rmi.policy策略文件配置有误,程序运行受到安全策略的限制。
8.2问题二
所有的类都编译成功之后,按照上面的步骤进行测试,但是每当运行RMIChatImpl的时候就报出下面的错误
D:/chat>java -Djava.security.policy=file:./rmi.policy -Dja va.rmi.server.codebase=file:./RMIChatImpl RMIChatImpl RMIChatImpl error:RemoteException occurred in server thread; nested exception is : java.rmi.UnmarshalException: error unmarshalling arguments; nested excep tion is: java.lang.ClassNotFoundException: RMIChatImpl_Stub java.rmi.ServerException: RemoteException occurred in server thread; nested exce ption is: java.rmi.UnmarshalException: error unmarshalling arguments; nested excep tion is: java.lang.ClassNotFoundException: RMIChatImpl_Stub at sun.rmi.server.UnicastServerRef.oldDispatch(UnicastServerRef.java:396 ) at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:250) at sun.rmi.transport.Transport$1.run(Transport.java:159) at java.security.AccessController.doPrivileged(Native Method) at sun.rmi.transport.Transport.serviceCall(Transport.java:155) at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:5 35) at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTranspor t.java:790) at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport .java:649) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExec utor.java:885) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor .java:907) at java.lang.Thread.run(Thread.java:619) at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(Unknow n Source) at sun.rmi.transport.StreamRemoteCall.executeCall(Unknown Source) at sun.rmi.server.UnicastRef.invoke(Unknown Source) at sun.rmi.registry.RegistryImpl_Stub.bind(Unknown Source) at java.rmi.Naming.bind(Unknown Source) at RMIChatImpl.main(RMIChatImpl.java:91) Caused by: java.rmi.UnmarshalException: error unmarshalling arguments; nested ex ception is: java.lang.ClassNotFoundException: RMIChatImpl_Stub at sun.rmi.registry.RegistryImpl_Skel.dispatch(Unknown Source) at sun.rmi.server.UnicastServerRef.oldDispatch(UnicastServerRef.java:386 ) at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:250) at sun.rmi.transport.Transport$1.run(Transport.java:159) at java.security.AccessController.doPrivileged(Native Method) at sun.rmi.transport.Transport.serviceCall(Transport.java:155) at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:5 35) at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTranspor t.java:790) at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport .java:649) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExec utor.java:885) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor .java:907) at java.lang.Thread.run(Thread.java:619) Caused by: java.lang.ClassNotFoundException: RMIChatImpl_Stub at java.net.URLClassLoader$1.run(URLClassLoader.java:200) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:188) at java.lang.ClassLoader.loadClass(ClassLoader.java:306) at java.lang.ClassLoader.loadClass(ClassLoader.java:251) at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:247) at sun.rmi.server.LoaderHandler.loadClass(LoaderHandler.java:434) at sun.rmi.server.LoaderHandler.loadClass(LoaderHandler.java:165) at java.rmi.server.RMIClassLoader$2.loadClass(RMIClassLoader.java:620) at java.rmi.server.RMIClassLoader.loadClass(RMIClassLoader.java:247) at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.jav a:197) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:157 5) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1 732) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351) ... 12 more
上面代码可能有点儿乱,其中心意思就是RMIChatImpl_Stub.class这个文件找不到,这究竟是什么原因呢,我明明已经成功编译出了这个文件啊?通过认真思索终于找到了原因所在,如下:
刚开始我运行RMI注册表时所在目录为:C:/Documents and Settings/Administrator>rmiregistry,在此目录下,注册表的确可以顺利启动,但是它对chat目录下的程序运行根本不起作用。然后进行改正,在chat目录下运行RMI注册表,然后再运行RMIChatImpl,可以成功运行。
8.3问题三
本示例中所有的类文件都放到了一个文件夹下(chat文件夹),但实际情况是服务器端和客户端肯定不是在一个目录下的,下面将客户端和服务器端文件进行分离,进行程序测试。
在f:/根目录下创建两个文件夹chatS和chatC,分别存放服务器端和客户端的文件,chatS文件夹下的文件为RMIChatImpl.class,RMIChat.class,ChatUser.class,rmi.policy和RMIChatImpl_Stub.class文件,chatC文件夹下的文件为ChatApplet.class , ChatApplet$1.calss , ChatApplet$2.class , ChatUser.class,RMIChat.class和test.htm文件。
依照前面的测试步骤,在chatS文件夹下RMI注册表和RMIChatImpl都可顺利执行,但是在chatC文件夹下运行ChatApplet时提示我RMIChatImpl_Stub.class文件找不到,看来此文件是客户端必须的(不论Applet还是application),然后将该stub文件复制到chatC文件夹下执行,可以顺利执行。
8.4问题四
我在另一个rmi测试程序的练习中,对编译后的文件进行rmic处理时,生成的文件有两个,一个是_Stub结尾的,另一个是_Skel结尾的,二本程序之生成了一个Stub文件。我从网上找原因,有一个答案是不同版本jdk的原因,但是我是在相同的机器上测试的,这个问题还未解决,希望高手赐教。
8.5问题五
客户端程序需不需要安全策略文件呢?我自己编写了一个rmi程序,发现直接输入“java client”客户端即可顺利执行,但是该页面的http://blog.csdn.net/goleagle/archive/2009/04/25/4107853.aspx一篇文章说到,客户端需要安全策略文件才可以执行,否则会抛出异常,看来应该和具体的配置有关系。
8.6问题六
程序的发布问题,该程序客户端是Applet程序,怎样保证从浏览器端正确运行该测试程序呢?在chat目录下直接运行test.htm肯定不行,你可以试试,此时浏览器可以显示界面,但无法和服务端连接。程序该如何发布呢?我进行了如下操作:
首先,把Chat文件夹复制到Tomcat服务器的Root目录下。
然后,将Chat 文件夹下的 ChatApplet$1.class , ChatApplet$2.class , ChatApplet.class , RMIChat.class 和RMIChatImpl_Stub.class文件打包生成test.jar文件。
其次,修改test.htm文档中的内容,如下:
<html>
<body>
<applet archive=test.jar CODE=ChatApplet.class width="300" height="200" >
</applet>
</body>
</html>
最后,运行RMI注册表和RMIChatImpl,成功运行后,在IE浏览器中输入localhost:8080/chat/test.htm,单击回车,发现返回正常的Applet页面,进行连接测试,发现仍然无法成功链接到服务器端程序,这究竟是什么原因呢?
从网上找原因,有n种答案,主要就是这两种:
1.安全性限制,需要修改客户端的安全策略文件,或者运用数字签名
2.IE不支持java2
本人进行反复实验就是无法找到本质原因,希望有谁看到这篇文章能够成功解决这一问题。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/goleagle/archive/2009/04/25/4107853.aspx