原文出自:http://blog.csdn.net/zhtang0526/article/details/4788879
一. 远程通讯协议的基本原理
网络通信需要做的就是将流从一台计算机传输到另外一台计算机,基于传输协议和网络 IO 来实现,其中传输协议比较出名的有 http 、 tcp 、 udp 等等, http 、 tcp 、 udp 都是在基于 Socket 概念上为某类应用场景而扩展出的传输协议,网络 IO ,主要有 bio 、 nio 、 aio 三种方式,所有的分布式应用通讯都基于这个原理而实现,只是为了应用的易用,各种语言通常都会提供一些更为贴近应用易用的应用层协议。
二. 应用级协议 Binary-RPC
Binary-RPC 是一种和 RMI 类似的远程调用的协议,它和 RMI 的不同之处在于它以标准的二进制格式来定义请求的信息 ( 请求的对象、方法、参数等 ) ,这样的好处是什么呢,就是在跨语言通讯的时候也可以使用。
来看下 Binary -RPC 协议的一次远程通信过程:
1 、客户端发起请求,按照 Binary -RPC 协议将请求信息进行填充;
2 、填充完毕后将二进制格式文件转化为流,通过传输协议进行传输;
3 、接收到在接收到流后转换为二进制格式文件,按照 Binary -RPC 协议获取请求的信息并进行处理;
4 、处理完毕后将结果按照 Binary -RPC 协议写入二进制格式文件中并返回。
问题总结:
1 、传输的标准格式是?
标准格式的二进制文件。
2 、怎么样将请求转化为传输的流?
将二进制格式文件转化为流。
3 、怎么接收和处理流?
通过监听的端口获取到请求的流,转化为二进制文件,根据协议获取请求的信息,进行处理并将结果写入 XML 中返回。
4 、传输协议是?
Http 。
三. Hessian ——一种实现远程通讯的 library
Hessian 是由 caucho 提供的一个基于 binary-RPC 实现的远程通讯 library 。
1 、是基于什么协议实现的?
基于 Binary-RPC 协议实现。
2 、怎么发起请求?
需通过 Hessian 本身提供的 API 来发起请求。
3 、怎么将请求转化为符合协议的格式的?
Hessian 通过其自定义的串行化机制将请求信息进行序列化,产生二进制流。
4 、使用什么传输协议传输?
Hessian 基于 Http 协议进行传输。
5 、响应端基于什么机制来接收请求?
响应端根据 Hessian 提供的 API 来接收请求。
6 、怎么将流还原为传输格式的?
Hessian 根据其私有的串行化机制来将请求信息进行反序列化,传递给使用者时已是相应的请求信息对象了。
7 、处理完毕后怎么回应?
处理完毕后直接返回, hessian 将结果对象进行序列化,传输至调用端。
四. Hessian 源码分析
以 hessian 和 spring dm server 整合环境为例。
1. 客户端发起请求
Hessian 的这个远程过程调用,完全使用动态代理来实现的。有客户端可以看出。
除去 spring 对其的封装,客户端主要是通过 HessianProxyFactory 的 create 方法就是创建接口的代理类,该类实现了接口, JDK 的 proxy 类会自动用 InvocationHandler 的实现类(该类在 Hessian 中表现为HessianProxy )的 invoke 方法体来填充所生成代理类的方法体。
客户端系统启动时:
根据 serviceUrl 和 serviceInterface 创建代理。
HessianProxyFactoryBean 类
HessianClientInterceptor 类
createHessianProxy(HessianProxyFactory proxyFactory)
HessianProxyFactory 类
public Object create(Class api, String urlName)
客户端调用 hessian 服务时:
HessianProxy 类的 invoke(Object proxy, Method method, Object []args) 方法
String methodName = method.getName();// 取得方法名
Object value = args[0]; // 取得传入参数
conn = sendRequest(mangleName, args) ; // 通过该方法和服务器端取得连接
httpConn = (HttpURLConnection) conn;
code = httpConn.getResponseCode(); // 发出请求
// 等待服务器端返回相应…………
is = conn.getInputStream();
Object value = in.readObject(method.getReturnType()); // 取得返回值
HessianProxy 类的 URLConnection sendRequest(String methodName, Object []args) 方法:
URLConnection conn = _factory.openConnection(_url); // 创建 URLConnection
OutputStream os = conn.getOutputStream();
AbstractHessianOutput out = _factory.getHessianOutput(os); // 封装为 hessian 自己的输入输出API
out.call(methodName, args);
return conn;
2. 服务器端接收请求并处理请求
服务器端截获相应请求交给:
org.springframework.remoting.caucho.HessianServiceExporter
具体处理步骤如下:
a) HessianServiceExporter 类
(HessianExporter) invoke(request.getInputStream(), response.getOutputStream());
b) HessianExporter 类
(Hessian2SkeletonInvoker) this.skeletonInvoker.invoke(inputStream, outputStream);
c) Hessian2SkeletonInvoker 类
将输入输出封转化为转化为 Hessian 特有的 Hessian2Input 和 Hessian2Output
Hessian2Input in = new Hessian2Input(isToUse);
in.setSerializerFactory(this.serializerFactory);
AbstractHessianOutput out = null;
int major = in.read();
int minor = in.read();
out = new Hessian2Output(osToUse);
out = new HessianOutput(osToUse);
out.setSerializerFactory(this.serializerFactory);
(HessianSkeleton) this.skeleton.invoke(in, out);
d) HessianSkeleton 类
读取方法名
String methodName = in.readMethod();
Method method = getMethod(methodName);
读取方法参数
Class []args = method.getParameterTypes();
Object []values = new Object[args.length];
执行相应方法并取得结果
result = method.invoke(service, values);
结果写入到输出流
out.writeObject(result);
总结: 由上面源码分析可知,客户端发起请求和服务器端接收处理请求都是通过 hessian 自己的 API 。输入输出流都要封装为 hessian 自己的 Hessian2Input 和 Hessian2Output ,接下来一节我们将去了解 hessian 自己封装的输入输出到底做了些什么!
五. Hessian 的序列化和反序列化实现
hessian 源码中 com.caucho.hessian.io 这个包是 hessian 实现序列化与反序列化的核心包。其中AbstractSerializerFactory , AbstractHessianOutput , AbstractSerializer , AbstractHessianInput ,AbstractDeserializer 是 hessian 实现序列化和反序列化的核心结构代码。
1. AbstractSerializerFactory ,它有 2 个抽象方法:
根据类来决定用哪种序列化工具类
abstract public Serializer getSerializer(Class cl) throws HessianProtocolException;
根据类来决定用哪种反序列化工具类
abstract public Deserializer getDeserializer(Class cl) throws HessianProtocolException;
2. SerializerFactory 继承 AbstractSerializerFactory 。
在 SerializerFactory 有很多静态 map 用来存放类与序列化和反序列化工具类的映射,这样如果已经用过的序列化工具就可以直接拿出来用,不必再重新实例化工具类。
在 SerializerFactory 中,实现了抽象类的 getSerializer 方法,根据不同的需要被序列化的类来获得不同的序列化工具,一共有 17 种序列化工具, hessian 为不同的类型的 java 对象实现了不同的序列化工具,默认的序列化工具是 JavaSerializer 。
在 SerializerFactory 中,也实现了抽象类的 getDeserializer 方法,根据不同的需要被反序列化的类来获得不同的反序列化工具,默认的反序列化工具类是 JavaDeserializer 。
3. HessianOutput 继承 AbstractHessianOutput 成为序列化输出流的一种实现。
它会实现很多方法,用来做流输出。
需要注意的是方法,它会先调用 serializerFactory 根据类来获得 serializer 序列化工具类
public void writeObject(Object object)
throws IOException
{
if (object == null) {
writeNull();
return;
}
Serializer serializer;
serializer = _serializerFactory.getSerializer(object.getClass());
serializer.writeObject(object, this);
}
4. 现在我们来看看 AbstractSerializer 。
其 writeObject 是必须在子类实现的方法, AbstractSerializer 有 17 种子类实现, hessian 根据不同的java 对象类型来实现了不同的序列化工具类,其中默认的是 JavaSerializer 。
而 JavaSerializer 的 writeObject 方法的实现,遍历 java 对象的数据成员,根据数据成员的类型来获得各自的 FieldSerializer ,一共有 6 中默认的 FieldSerializer 。
拿默认的 FieldSerializer 举例,还是调用 AbstractHessianOutput 的子类来 writeObject ,这个时候,肯定能找到相应的 Serializer 来做序列化
同理可以反推出 hessian 的反序列化机制。 SerializerFactory 可以根据需要被反序列化的类来获得反序列化工具类来做反序列化操作。
总结:得益于 hessian 序列号和反序列化的实现机制, hessian 序列化的速度很快,而且序列化后的字节数也较其他技术少。
参考文献:
1. 《 Java 远程通讯可选技术及原理》 http://java.chinaitlab.com/base/740383.html
2. 《 Hessian-3.2.0 源码》
3. 《 hessian 序列化实现初探》 http://www.javaeye.com/topic/245238
4. 《 Hessian 2.0 序列化协议规范》
http://blog.csdn.net/xpspace/archive/2007/10/05/1811603.aspx
一. Hessian 简介
Hessian 是由 caucho 提供的一种开源的远程通讯协议。 Hessian 采用二进制 RPC 协议,基于 HTTP 传输,服务器端不用开放防火墙端口。 Hessian 协议的规范是公开的,可以用于任意语言。
二. Hessian 实现
a) 基本实现
服务器端:
定义接口: Hello, World API
public interface BasicAPI {
public String hello();
}
接口实现: Hello, World Service
public class BasicService implements BasicAPI {
private String _greeting = "Hello, world";
public void setGreeting(String greeting)
{
_greeting = greeting;
}
public String hello()
{
return _greeting;
}
}
配置 WEB-INF.xml 部署到 Web 容器中:
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>
<init-param>
<param-name>home-class</param-name>
<param-value>com.xiaonei.jebe.hessian.BasicService</param-value>
</init-param>
<init-param>
<param-name>home-api</param-name>
<param-value>com.xiaonei.jebe.hessian.BasicAPI</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello.htm</url-pattern>
</servlet-mapping>
客户端:
String url = "http://hessian.caucho.com/hessian/hello.htm";
HessianProxyFactory factory = new HessianProxyFactory();
BasicAPI basic = (BasicAPI) factory.create(BasicAPI.class, url);
System.out.println("hello(): " + basic.hello());
b) 与 spring 的整合
Hessian 通过 Servlet 提供远程服务。需要将匹配某个模式的请求映射到 Hessian 服务。 Spring 的DispatcherServlet 可以完成该功能, DispatcherServlet 可将匹配模式的请求转发到 Hessian 服务, web.xml 只是定义了“请求转发器”,该转发器将匹配 /remoting/* 的请求截获,转发给 context 的 bean 处理。而HessianServiceExporter 提供 bean 服务。
可以总结为两步:
1. 拦截 url 请求
2. HessianServiceExporter 提供 bean 服务, Spring 使用 HessianServiceExporter ,将一个常规 bean 导出成 Hessian 服务。
服务器端设置:
web.xml 的详细配置
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/remoting/*</url-pattern>
</servlet-mapping>
配置 remoting-servlet.xml 文件
<!-- 定义普通 bean 实例 -->
<bean id="hello" class=" com.xiaonei.jebe.hessian.BasicService "/>
<!-- 使用 HessianServiceExporter 将普通 bean 导出成 Hessian 服务 -->
<bean name="/hello" class="org.springframework.remoting.caucho.HessianServiceExporter">
<!-- 需要导出的目标 bean-->
<property name="service" ref="hello"/>
<!-- Hessian 服务的接口 -->
<property name="serviceInterface" value=" com.xiaonei.jebe.hessian.BasicAPI "/>
</bean>
客户端设置:
客户端定义一个 remoting-client.xml 文件
<bean id="myServiceClient" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceUrl">
<value> http://hessian.caucho.com/hessian/hello.htm</value>
</property>
<property name="serviceInterface">
<value> com.xiaonei.jebe.hessian.BasicAPI </value>
</property>
</bean>
c) 与 OSGi ( spring dm server )的整合
请参见文档 《 Hessian与 OSGi( spring dm server)的整合》
三. Hessian 原理
请参见文档 《 Hessian原理分析》
四. 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 倍。
五. Hessian 使用感受
在本次的系统中大量使用 hessian 进行远程通讯。系统环境是 OSGi 实现 spring dm server 。
在使用中,我们发现 hessian 的确精简很容易使用,而且效率很高,基本可以满足系统需求。但是我们也发现了很多不足,有 hessian 特有的也有 hessian 和 OSGi ( spring dm server )整合后造成的。
优点:
1. 简单易用
2. 效率高
缺点:
1. 报错机制不够完善。在开发过程中发现 hessian 总是报一些错误,错误原因也是千奇百怪。当然,有可能是前期大家对 hessian 理解不够深刻,但是也确实暴露出来 hessian 的一些问题。
解决方案有:加深对 hessian 实现机制的理解;总结 hessian 出错的原因,每种错误可以列举出几种原因,便于以后查找。
2. 事务处理。至今还没有针对 hessian 服务的有效的事务处理机制,大家提出了几种实现方案,但都不是很合适,需要继续研究。
3. 版本问题:在开发过程中,发现 springsource 的 bundle 库中最新的版本是 3.2.1 ,但是 3.2.1 和spring2.5 存在兼容问题,现在可用的版本有 3.1.5 、 3.1.6 、 3.2.0 。而在 hessian 官网发现最新版本为4.0.1 ,但 4.0.1 没有 bundle 形式 jar 包。
参考文献:
1. 《 hessian binary web service protocol 》 http://hessian.caucho.com/
2. 《几种通讯协议的比较》 http://javag.javaeye.com/blog/319285
3. 《 Spring 2.0 宝典》 14.5.1 Hessian 介绍
http://book.csdn.net/bookfiles/126/1001264302.shtml
4. 《 Hessian 学习笔记》 http://www.alisdn.com/wordpress/?p=478
5. 《 Hessian 学习记录》 http://www.javaeye.com/topic/212028