Hessian---java远程通讯

     我们知道,大多数情况下,方法的调用都是发生在相同堆上的两个对象之间,所有组件都在同一台计算机的同一个Java虚拟机的同一个堆空间上执行是最简单的,如果用户端只是个能够执行Java的装置怎么办?如果为了安全性的理由只能让服务器上的程序存取数据库怎么办?例如,我们的BS系统、单纯的CS系统等等,但是如果我们有这样的需求:我们的项目需要调用不同机器上的对象和方法,我们应该怎么处理呢,例如下面的案例:

     大家都知道,我们的web项目在服务器上部署完后,我们可以通过在浏览器*问地址的方式进行访问,也可以通过在地址栏输入地址的方式执行里面的方法:http://localhost:8080/test/query!queryUser.do,然后在页面上处理得到我们相应的结果,如果我们的客户端不是使用浏览器访问,而是通过swing程序或者其他非浏览器访问方式,我们要在客户端程序中获取服务器里面相应的方法,得到相应的信息后,再在我们的客户端做出相应的逻辑处理,我们的客户端不与数据库打交道,无需操心将数据发送到网络上或者解析响应之类的问题,我们该如何进行处理呢?

     这就用到了我们的一种远程调用机制,我们从某一台计算机上面取得另一台计算机上的信息是通过socket的输入/输出流,打开另一台计算机的socket连接,然后取得outputStream来写入数据.但如果要调用另一台计算机上,另一个Java虚拟机上面的对象的方法,我们当然可以自己定义和设计通信协议来调用,然后通过Socket把执行结果再传回去,并且还能够像是对本机的方法调用一样,也就是说想要调用远程的对象(像是别的堆上的),却又要像是一般的调用,这就是分布式的服务调用,在分布式服务框架中,一个最基础的问题就是远程服务是怎么通讯的,在Java底层领域中有很多可实现远程通讯的技术,例如:RMI、MINA、ESB、Burlap、SOAP、EJB和JMS 等,在j2ee中,对java底层远程通讯的技术进行了封装,形成了 Hessian 、 HttpInvoker 、 XFire 、 Axis 等多种形式的远程调用技术。但对高级程序员而言仍需要掌握Java底层领域中远程通讯的技术,尤其是rmi,xml-rpc,JMS。下面我来给大家讲解一下由caucho提供的一个基于binary-RPC实现的远程通讯library的Hessian。

      Hessian是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能。 相比WebService,Hessian更简单、快捷。采用的是二进制RPC协议,因为采用的是二进制协议,所以它很适合于发送二进制数据。

<!--以上很大部分摘自网络,自己整合-->

说到底,就是我们要通过一台机器调用另一台机器上、另一个java虚拟机上的对象和方法,我们可以通过轻量级的远程通讯Hessian来实现,下面我们来搭建一个简单的helloworld:

使用Hessian,需要导入Hessian的一个支持包,可以在它的官网进行下载:http://hessian.caucho.com,也可以在我的资源库里面进行免费下载:http://download.csdn.net/detail/harderxin/7125443

好了,下面让我们开始使用Hessian,我要实现的功能是,我的客户端能够通过main方法调用服务器端的方法,并且将服务器端执行的结果返回给客户端打印出来:

一、新建web项目,我取名为HessianServer,然后将hessian-4.0.37.jar导入我们的工程中,然后建立我们的实体类User,因为我客户端就是要获取User信息,给他定义id、name、password属性:

package com.server.bean;

import java.io.Serializable;

public class User implements Serializable{
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 7175134832651443717L;
	//用户编号
	private int id;
	//用户名
	private String userName;
	//密码
	private String password;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
	
	public User(int id, String userName, String password) {
		super();
		this.id = id;
		this.userName = userName;
		this.password = password;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		result = prime * result
				+ ((password == null) ? 0 : password.hashCode());
		result = prime * result
				+ ((userName == null) ? 0 : userName.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		User other = (User) obj;
		if (id != other.id)
			return false;
		if (password == null) {
			if (other.password != null)
				return false;
		} else if (!password.equals(other.password))
			return false;
		if (userName == null) {
			if (other.userName != null)
				return false;
		} else if (!userName.equals(other.userName))
			return false;
		return true;
	}
}

定义一个UserService接口,用来处理User逻辑,也是得交给客户端去动态代理的:

package com.server.service;

import java.util.List;

import com.server.bean.User;

public interface UserService {
	
	public List<User> getUser();
	
}

定义UserService的实现类UserServiceImpl,该类实现UserService,并处理相应的逻辑:

package com.server.service.impl;

import java.util.ArrayList;
import java.util.List;

import com.server.bean.User;
import com.server.service.UserService;

public class UserServiceImpl implements UserService{

	public List<User> getUser() {
		//我们可以在这个方法中与数据库打交道
		List<User> list=new ArrayList<User>();
		list.add(new User(1,"Mary","123456"));
		list.add(new User(2,"Jack","236547"));
		list.add(new User(3,"Joy","362541"));
		return list;
	}
}


二、配置好web.xml,将Hessian与我们的项目进行整合:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
	
	<servlet>
       <servlet-name>UserService</servlet-name>	
       <!-- 配置HessianServlet-->
       <servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>
       <init-param>
          <param-name>home-class</param-name>
          <!-- 我们定义的接口实现类 -->
          <param-value>com.server.service.impl.UserServiceImpl</param-value>
       </init-param>
       <init-param>
          <param-name>home-api</param-name>
          <!-- 我们定义的接口类 -->
          <param-value>com.server.service.UserService</param-value>
       </init-param>
       <load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
	   <servlet-name>UserService</servlet-name>
	   <!-- 客户端访问路径 -->
	   <url-pattern>/us</url-pattern>
	</servlet-mapping>
	
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

三、将User和UserService打成的jar包,让客户端能够得到服务端的实体和接口,我们也可以不用这样做,但是我们得需要把服务器端的User和UserService类再复制到客户端,所以做成jar包方便我们的访问,我打的jar包名称为:project-1.0.jar
好了,我们的服务端弄好了,下面我们将服务端部署到Tomcat服务器上,然后启动tomcat服务器,我们程序的访问地址为:
http://localhost:8080/HessianServer/us

四、编写我们的客户端,我取名为HessianClient,将hessian-4.0.37.jar和我们刚刚导出的project.1.0.jar导入到我们的工程中,新建一个UserServiceTest的java类:

package com.client.test;

import java.net.MalformedURLException;
import java.util.List;

import com.caucho.hessian.client.HessianProxyFactory;
import com.server.bean.User;
import com.server.service.UserService;

public class UserServiceTest {
	public static void main(String[] args) {
		//我们的服务器访问的地址
		String url="http://localhost:8080/HessianServer/us";
		//获得HessianProxyFactory实例
		HessianProxyFactory factory=new HessianProxyFactory();
		try {
			//创建我们的接口对象
			UserService userService=(UserService)factory.create(url);
			//执行服务端方法
			List<User> users=userService.getUser();
			//遍历输出
			for(User user:users){
				System.out.println("id="+user.getId()+",name="+user.getUserName()+",pwd="+user.getPassword());
			}
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

}

执行main方法,打印出来得到的结果为:

id=1,name=Mary,pwd=123456
id=2,name=Jack,pwd=236547
id=3,name=Joy,pwd=362541
通过Hessian,我们成功的从我们的客户端(PC机1)访问到了服务器(PC机2)上的方法和对象,两个程序的运行在两个不同的java虚拟机上面,这就是一个很简单的远程通讯,下面来带大家更多的了解Hessian:

1、是基于什么协议实现的?
基于Binary-RPC协议实现。
2、怎么发起请求?
需通过Hessian本身提供的API来发起请求。
3、怎么 将请求转化为符合协议的格式的?
Hessian通过其自定义的串行化机制将请求信息进行序列化,产生二进制流。
4、使用什么 传输协议传输?
Hessian基于Http协议进行传输。
5、响应端基于什么机制来接收请求?
响应端根据Hessian提供的API来接收请求。
6、怎么将流还原为传输格式的?
Hessian根据其私有的串行化机制来将请求信息进行反序列化,传递给使用者时已是相应的请求信息对象了。
7、处理完毕后怎么回应?
处理完毕后直接返回,hessian将结果对象进行序列化,传输至调用端。

简单的原理图:

Hessian---java远程通讯

1)Hessian远程访问基于序列化和反序列化的方式。当程序运行时,程序所创建的各种对象都位于内存中,当程序运行结束,这些对象就结束了生命周期。对象的序列化主要有两种用途:

l  把对象的字节序列永久地保存到硬盘上,通常是放在一个文件中。

l  在网络上传输对象的字节序列

Hessian就是把Java对象转变成 字节序列,然后通过Http传输到 目标服务器上,目标服务器收到这个字节序列后,按照一定的协议标准进行反序列,提交给对应的服务处理。处理完成以后以同样的方式返回数据\

 

2)配置的Servlet: com.caucho.hessian.server.HessianServlet

对应的参数:接口(home-api):com.alisoft.enet.hessian.Hello

           实现(home-class): com.alisoft.enet.hessian.HelloImpl

HessianServlet 中的实现代码如下(略过部分代码):

HttpServletRequest req = (HttpServletRequest) request;

HttpServletResponse res = (HttpServletResponse) response;

InputStream is = request.getInputStream();

OutputStream os = response.getOutputStream();

//输入流

Hessian2Input in = new Hessian2Input(is);

SerializerFactory serializerFactory = getSerializerFactory();

in.setSerializerFactory(serializerFactory);

//输出流

AbstractHessianOutput out;

int major = in.read();

int minor = in.read();

out = new Hessian2Output(os);

out.setSerializerFactory(serializerFactory);

_homeSkeleton.invoke(in, out);

整个执行步骤如下:

l  接收输入流,并通过SerializerFactory转化为 Hessian 特有的 Hessian2Input

l  设置输出流,并通过SerializerFactory转化为 Hessian 特有的 Hessian2Output

l  根据配置的接口和实现参数,调用服务,并把结果写入到输出流 Hessian2Output中

l   Out.close()

 

3)Hessian 和其他远程调用实现的比较

1. 常见远程通讯协议:

RMI 、 Httpinvoker 、 Hessian 、 Burlap 、 Web service

通讯效率测试结果:

RMI > Httpinvoker >= Hessian >> Burlap >> Web service

2. 各个通讯协议的分析:

RMI 是 Java 首选远程调用协议,非常高效稳定,特别是在数据结构复杂,数据量大的情况下,与其他通讯协议的差距尤为明显。

HttpInvoker 使用 java 的序列化技术传输对象,与 RMI 在本质上是一致的。从效率上看,两者也相差无几, HttpInvoker 与 RMI 的传输时间基本持平。

Hessian 在传输少量对象时,比 RMI 还要快速高效,但传输数据结构复杂的对象或大量数据对象时,较 RMI 要慢 20% 左右。但这只是在数据量特别大,数据结构很复杂的情况下才能体现出来,中等或少量数据时, Hessian 并不比 RMI 慢。 Hessian 的好处是精简高效,可以跨语言使用,而且协议规范公开,我们可以针对任意语言开发对其协议的实现。

另外, Hessian 与 WEB 服务器结合非常好,借助 WEB 服务器的成熟功能,在处理大量用户并发访问时会有很大优势,在资源分配,线程排队,异常处理等方面都可以由成熟的 WEB 服务器保证。而 RMI 本身并不提供多线程的服务器。而 且, RMI 需要开防火墙端口, Hessian 不用。

Burlap 采用 xml 格式传输。仅在传输 1 条数据时速度尚可,通常情况下,它的毫时是 RMI 的 3 倍。

Web Service 的效率低下是众所周知的,平均来看, Web Service 的通讯毫时是 RMI 的 10 倍。

至于RMI、Web service不了解的可以自己在网上下载相应的文档进行查看学习

 

4)分布式编程的定义和理解:

基本思想
 所有分布编程技术的基本思想都很简单:
     1.客户端通过网络将请求发送到服务端.
     2.服务端处理请求返回相应.
     3.客户端完成后续处理.

定义声明:
 这些请求和响应不是在Web 应用程序中看到的请求和响应,这里的客户端并非是Web浏览器,它可以是执行具有任意复杂度业务规则的任何应用程序。(例如Swing用户界面等等)用于请求和响应数据的协议允许传递任意对象,而传统的Web应用程序只限于对请求使用Http协议,对响应使用HTML.

我们需要什么样的分布式模式?
 开发中我们正真需要的这样一种机制:客户端程序员以常规的方式进行方法调用,而无需操心将数据发送到网络上或者解析响应之类的问题。解决方法:在客户端为远程对象安装一个代理(Proxy)。代理是位于客户端虚拟机中的一个对象,对于客户端程序来说,看起来就像是要访问的远程对象一样。客户调用此代理时,只需进行常规的方法调用。而客户端代理负责使用网络协议与服务进行联系。 同样,服务端的程序员也不希望因与客户端之间的通信被绊住。解决方法:在服务端也安装一个代理。该服务端代理与客户端代理进行通信,并且它将以常规方式调用服务器对象上的方法。

Hessian---java远程通讯

代理之间是如何通信的呢?通常有三种选择:
 Corba:通用的对象请求代理架构,支持任何语言编写对象之间的方法调用。

              Corba使用二进制的Internet Inter-         ORB协议(IIOP)来实现对象间的通信.
    Web:Web服务架构是一个协议集,有时统一描述为WS-*它也是独立于编程语言的,不过它使用基于XML的通信格式.用于传输对象的格式则是简单对象访问协议.
     RMI:Java的远程方法调用技术,支持Java的分布式对象之间的方法调用.

分布式编程的关键是远程方法调用。在一台计算机上(客户端)上的某些代码希望调用另一台计算机(服务端)上的某个对象的一个方法.要实现这一点,方法的参数必须以某种方式传递到另外一台机器上,而服务器必须得到通知,去定位远程对象并执行要调用的方法,并且必须将返回值传递回去.
 

方法调用的关键元素:存根与参数编组.当客户端代码要在远程对象上调用一个远程方法时,实际上调用的是代理对象上的一个普通的方法,我们称此代理对象为存根(stub).
 存根位于客户端机器上,而非服务器上,它知道如何通过网络与服务器联系。存根会将远程方法所需的参数打包成一组字节。对参数编码的过程称作参数编组,参数编组的目的是将参数转换为适合在虚拟机之间进行传递的格式.获取客户端存根的细节依赖采用的具体分布式技术.

Hessian和Spring、hibernate、Struts整合见下篇博文!欢迎一起交流学习

Hessian---java远程通讯,布布扣,bubuko.com

Hessian---java远程通讯

上一篇:c++中new容易出现的问题


下一篇:Java Socket编程入门