applet与servlet之间的联系一般是间接的,即页面请求由浏览器发送给servlet,作为回答,servlet将结果生成html文档发送给浏览器。很多时候在servlet与applet之间建立直接的联系是没有必要的,但如果要显示一些实时的动态数据,如聊天室窗口、新闻显示、股市行情等,建立两者之间的直接联系就很有用了。特别地,在较为复杂的分布式java应用中,这种通讯更是必不可少。在客户端java程序与服务器端java程序之间建立这种复杂的交互方式仅仅使用servlet与applet api是不够的,本文为你介绍和比较四种通讯方式:通过html页面传递参数,用java.net包的网络功能建立直接网络连接,远程方法调用(rmi)与corba。
一、通过html页面传递applet参数
通过html页面传递参数是在servlet与applet之间建立联系最为常用的方法。servlet只要把传递给applet的参数写入html页面就可以了。这种通讯是单向的,用于实现servlet对applet的控制。其实现方法类如:
//doget()方法生成一个包含applet的html页面,
// 该applet需要一个data参数
public void doget(httpservletrequest request,httpservletresponse response)
throws servletexception, ioexception { java天堂
response.setcontenttype("text/html");
printwriter out = new printwriter ( response.getoutputstream());
out.write("< html>< head>< title> html example< /title> < /head>< body>");
out.write("servlet通过param标记传递参数给applet. ");
out.write("< applet code="simpleapplet" width="225" height="149">");
// 写入param标记
out.write("< param name="data" value="");
out.write(java.text.dateformat.getdateinstance().format(new java.util.date()
));
out.write("">< /applet>< /body>< /html>");
out.close();
}
|
这种方法很简单,但它有两个缺点。首先,所有参数都是静态的,即一旦html页面发送完成,要更新其中部分数据就很困难了。虽然可以使用http的刷新机制,但这种刷新需要重新下载整个页面,效率是很低的。第二,如果需要传递给applet的数据量很大或数据结构很复杂,就会使得html页面过于庞大或复杂了。
二、用java.net包建立双向通讯
第二种方法是利用java.net包提供的网络能力。以有连接流通讯方式为例,在服务器端的操作为一般为:
⑴ 创建一个serversocket对象,在指定端口监听客户端发来的请求。
⑵ 在接收到请求时accept()方法将返回一个socket对象。
⑶ 用上述socket对象创建输入、输出流对象。
⑷ 通过输入、输出流与客户交互。
⑸ 交互完毕,关闭输入、输出流与socket。
⑹ 服务程序运行结束,关闭serversocket。
实现代码代码类如:
try{
boolean flag=true;
socket clientsocket=null;
serversocket serversocket = new serversocket(0);
system.out.println("server listen on: " +serversocket.getlocalport());
while(flag){
clientsocket=serversocket.accept();
datainputstream is=new datainputstream( new bufferedinputstream(clientsocket
.getinputstream()));
printstream os=new printstream( new bufferedoutputstream(clientsocket.getout
putstream()));
// 处理applet请求
os.close();
is.close();
clientsocket.close();
}
serversocket.close();
}catch( ioexception e){
system.err.println(" exception: "+e);
}
|
在客户端的操作为:
⑴ 创建socket对象建立与服务器的连接。
⑵ 用该socket对象创建输入、输出流。
⑶ 与服务器交互。
⑷ 交互完毕,关闭输入、输出流与socket。
实现代码类如:
try {
socket clientsocket =new socket("servername",7);
outputstream os=clientsocket.getoutputstream();
datainputstream is=new datainputstream( clientsocket.getinputstream());
// 其它操作.
os.close();
is.close();
clientsocket.close();
}catch(exception e){
system.err.println("exception:"+e);
}
|
这种方法只依赖于标准的java网络支持,不需要用到附加的软件包或工具, 因此显得相当简洁和灵活,易于实现某些特殊的需要。但在另一方面,服务器端对进入请求以及客户端对返回结果的分析工作量较大。如果服务器端要执行大量的操作,可以考虑使用rmi。
三、远程方法调用rmi
java的rmi技术显著地改善了客户程序操作复杂的服务器端对象的能力。对applet来说,服务器端对象所提供的远程方法可以象普通的客户端句柄一样调用。使用rmi时应先定义远程接口,即一个继承自java.rmi.remote的公用接口。在该接口中定义要求在服务器上运行的方法,所有这些方法的throws子句至少应该声明java.rmi.remoteexcepti on异常。
接下来是实现远程接口,远程接口的实现类应该:
⑴ 指定所实现的一个或多个接口。
⑵ 定义远程对象的构造方法。
⑶ 提供所有可供远程调用的方法的实现。
⑷ 创建和安装安全管理器。
⑸ 创建一个或多个远程对象实例。
⑹ 注册至少一个远程对象。
下面的代码实现一个返回日期字符串的getdata()远程方法,该方法由接口rmiserv2app定义:
// 本类继承自unicastremoteobject,实现接口serv2app
package com.servletsolutions.serv2app.rmi;
import java.rmi.*;
import java.rmi.server.*;
public class rmiimplementation extends unicastremoteobject
implements rmiserv2app{
public rmiimplementation() throws remoteexception{}
public string getdata() throws remoteexception{
return java.text.dateformat.getdateinstance().format(
new java.util.date());
}
public static void main(string[] args){
// 安全管理器
system.setsecuritymanager(new rmisecuritymanager());
try{
// 远程对象实例
rmiimplementation bootstrap = new rmiimplementation();
// 注册
naming.bind(args[0], bootstrap);
system.out.println(args[0] + " 注册成功");
}catch(exception e){
system.err.println("注册失败.");
system.err.println(e);
}
}
}
|
在客户端,applet对接口rmiserv2app的引用通过调用naming.lookup(string)获得,如下所示:
rmiserv2app obj = (rmiserv2app)naming.lookup("//" + getcodebase().gethost() + "/servername");
除了编译java代码之外,还要用rmic编译器生成远程对象的存根和骨架。存根(stub)是远程对象在客户端的代理,它将rmi调用传递给服务器端的骨架(skeleton),后者负责将该调用传递给实际的远程方法。例如执行命令rmic com.servletsolutions.rmiimplementation,则生成的两个类文件分别为rmiimplementation_stub.class 和rmiimplementation_stub.class,前者封装入applet且运行于客户端,后者封装入servlet且运行与服务器端。
当大量的数据是由服务器端的对象动态提供时使用rmi是非常合适的。虽然rmi支持面向对象的分布式编程,但它要求客户端和服务器端都是java程序。因此,对于使用混合语言开发的应用来说,corba比rmi更为适用。
四、使用corba
corba即公用对象请求代理体系(common object request broker architecture),它的使用方法类似于rmi:定义接口,使用某个工具创建stub和skeleton,实现接口,在服务器上注册后客户就可以像本地对象一样访问远程对象了。两者主要的不同之处在于,corba使用一种语言中立的接口定义语言(idl)来定义接口,idl是一种可用于混合语言环境的更为广泛的协议集。corba最大的缺点在于客户本身必须具备调用orb的能力,这往往意味着增加一个体积较大的jar文件。
下面的corbaserv2app.idl是上述getdata()接口的idl描述,它定义了一组与java包名字对应的嵌套模块:
module com {
module servletsolutions {
module serv2app {
module corba {
interface corbaserv2app{
string getdata();
};
};
};
};
};
|
可以用idl2java从idl文件生成对应的java文件。其中_corbaserv2appimplbase.j
ava包含了描述该接口的java抽象类,下面的corbaimplementation类继承自抽象类_corbaserv2appimplbase并具体实现getdata()方法:
package com.servletsolutions.serv2app.corba;
public class corbaimplementation extends _corbaserv2appimplbase {
corbaimplementation(string s){ super(s); }
public string getdata(){
return java.text.dateformat.getdateinstance().format(new java.util.date());
}
}
|
为了让该类可以被客户访问,servlet中还应该创建其实例。代码如下所示:
// 在servlet的init()方法中初始化corbaimplementation对象
// 其中boa(basic object adapter)的作用类似于rmi中的注册服务
public class corbaservlet extends httpservlet{
string corbaname;
public void init(
servletconfig config) throws servletexception {
super.init(config);
try {
corbaname = getinitparameter("corbaname");
org.omg.corba.orb orb = org.omg.corba.orb.init();
org.omg.corba.boa boa = orb.boa_init();
com.servletsolutions.serv2app.corba.corbaserv2app implobject =
new corbaimplementation(corbaname);
boa.obj_is_ready(implobject);
boa.impl_is_ready();
}
catch (exception e) {
e.printstacktrace();
}
}
|
idl2java同时为客户端生成corbaserv2apphelper类,applet通过该类与服务器交互。stub与skeletion的作用类似于它们在rmi中,不同之处在于本地orb接口负责所有与 远程orb之间的连接。
如果服务器端的数据计算量非常大(比如数据库操作),或在未知客户端应用程序的情况下需要从服务器端引出大量的功能,rmi和corba较为合适。考虑到已有大量可用的orb以及corba规范的成熟,在大多数情况下corba更为可取。但如果希望减小客户端的复杂程度,则rmi是最好的方法。