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將結果對象進行序列化,傳輸至調用端。

簡單的原理圖:

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)。代理是位於客戶端虛擬機中的一個對象,對於客戶端程序來說,看起來就像是要訪問的遠程對象一樣。客戶調用此代理時,只需進行常規的方法調用。而客戶端代理負責使用網絡協議與服務進行聯繫。 同樣,服務端的程序員也不希望因與客戶端之間的通信被絆住。解決方法:在服務端也安裝一個代理。該服務端代理與客戶端代理進行通信,並且它將以常規方式調用服務器對象上的方法。

代理之間是如何通信的呢?通常有三種選擇:
 Corba:通用的對象請求代理架構,支持任何語言編寫對象之間的方法調用。

              Corba使用二進制的Internet Inter-         ORB協議(IIOP)來實現對象間的通信.
    Web:Web服務架構是一個協議集,有時統一描述爲WS-*它也是獨立於編程語言的,不過它使用基於XML的通信格式.用於傳輸對象的格式則是簡單對象訪問協議.
     RMI:Java的遠程方法調用技術,支持Java的分佈式對象之間的方法調用.

分佈式編程的關鍵是遠程方法調用。在一臺計算機上(客戶端)上的某些代碼希望調用另一臺計算機(服務端)上的某個對象的一個方法.要實現這一點,方法的參數必須以某種方式傳遞到另外一臺機器上,而服務器必須得到通知,去定位遠程對象並執行要調用的方法,並且必須將返回值傳遞回去.
 

方法調用的關鍵元素:存根與參數編組.當客戶端代碼要在遠程對象上調用一個遠程方法時,實際上調用的是代理對象上的一個普通的方法,我們稱此代理對象爲存根(stub).
 存根位於客戶端機器上,而非服務器上,它知道如何通過網絡與服務器聯繫。存根會將遠程方法所需的參數打包成一組字節。對參數編碼的過程稱作參數編組,參數編組的目的是將參數轉換爲適合在虛擬機之間進行傳遞的格式.獲取客戶端存根的細節依賴採用的具體分佈式技術.

Hessian和Spring、hibernate、Struts整合見下篇博文!歡迎一起交流學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章