java RPC 初步了解
首先要了解一個概念:wsdl 協議 web service description language
使用wsdl 要定義一個接口,一個服務;目前常用的就是xml 描述,類似java中 jax-ws?
WSDL 元素? 基于XML語法描述了與服務進行交互的基本元素:
Type(消息類型):數據類型定義的容器,它使用某種類型系統(如 XSD)。
Message(消息):通信數據的抽象類型化定義,它由一個或者多個 part 組成。
Part:消息參數
Operation(操作):對服務所支持的操作進行抽象描述,WSDL定義了四種操作: 1.單向(one-way):端點接受信息;2.請求-響應(request-response):端點接受消息,然后發送相關消息;3.要求-響應(solicit-response):端點發送消息,然后接受相關消息;4.通知(notification ):端點發送消息。
Port Type(端口類型):特定端口類型的具體協議和數據格式規范。
Binding:特定端口類型的具體協議和數據格式規范。
Port:定義為綁定和網絡地址組合的單個端點。
Service:相關端口的集合,包括其關聯的接口、操作、消息等。
-------------------------------------------------------------------------------------------------------
JAX-WS(Java API for XML Web Services)規范是一組XML web services的JAVA API,JAX-WS允許開發者可以選擇RPC-oriented或者message-oriented 來實現自己的web services。
SOAP 協議:簡單對象訪問協議? simple object access protocol.
一個 SOAP 實例
在下面的例子中,一個 GetStockPrice 請求被發送到了服務器。此請求有一個 StockName 參數,而在響應中則會返回一個 Price 參數。此功能的命名空間被定義在此地址中: "http://www.example.org/stock"
SOAP 請求:
POST /InStock HTTP/1.1Host: www.example.org
Content-Type: application/soap+xml; charset=utf-8
Content-Length: nnn
<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
<soap:Body xmlns:m="http://www.example.org/stock">
? <m:GetStockPrice>
??? <m:StockName>IBM</m:StockName>
? </m:GetStockPrice>
</soap:Body>
</soap:Envelope>
SOAP 響應:
HTTP/1.1 200 OKContent-Type: application/soap+xml; charset=utf-8
Content-Length: nnn
<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
<soap:Body xmlns:m="http://www.example.org/stock">
? <m:GetStockPriceResponse>
??? <m:Price>34.5</m:Price>
? </m:GetStockPriceResponse>
</soap:Body>
</soap:Envelope>
使用JAX-WS(JWS)發布WebService,實現輕量級WebService框架--demo
我們使用JAX-WS開發WebService只需要很簡單的幾個步驟:寫接口和實現=>發布=>生成客戶端(測試或使用)。
而在開發階段我們也不需要導入外部jar包,因為這些api都是現成的。首先是接口的編寫(接口中只需要把類注明為@WebService,把 要暴露給客戶端的方法注明為@WebMethod即可,其余如@WebResult、@WebParam等都不是必要的,而客戶端和服務端的通信用RPC 和Message-Oriented兩種,區別和配置以后再說):
package service;import java.util.Date;import javax.jws.WebMethod; import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService; import javax.jws.soap.SOAPBinding;/*** 作為測試的WebService接口* * @author Johness* */ @WebService @SOAPBinding(style = SOAPBinding.Style.RPC) public interface SayHiService {/*** 執行測試的WebService方法*/@WebMethodvoid SayHiDefault();/*** 執行測試的WebService方法(有參)* * @param name*/@WebMethodvoid SayHi(@WebParam(name = "name") String name);/*** 執行測試的WebService方法(用于時間校驗)* * @param clentTime 客戶端時間* @return 0表示時間校驗失敗 1表示校驗成功*/@WebMethod@WebResult(name = "valid")int CheckTime(@WebParam(name = "clientTime") Date clientTime); } package service.imp;import java.text.SimpleDateFormat; import java.util.Date;import javax.jws.WebService; import javax.jws.soap.SOAPBinding;import service.SayHiService;/*** 作為測試的WebService實現類* * @author Johness* */ @WebService(endpointInterface = "service.SayHiService") @SOAPBinding(style = SOAPBinding.Style.RPC) public class SayHiServiceImp implements SayHiService {@Overridepublic void SayHiDefault() {System.out.println("Hi, Johness!");}@Overridepublic void SayHi(String name) {System.out.println("Hi, " + name + "!");}@Overridepublic int CheckTime(Date clientTime) {// 精確到秒String dateServer = new java.sql.Date(System.currentTimeMillis()).toString()+ " "+ new java.sql.Time(System.currentTimeMillis());String dateClient = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(clientTime);return dateServer.equals(dateClient) ? 1 : 0;}}然后是發布(一般有兩種方式):
方式一(此方式只能作為調試,有以下bug:
jdk1.6u17?以下編譯器不支持以Endpoint.publish方式發布document方式的soap,必須在service接口和實現類添加“@SOAPBinding(style = SOAPBinding.Style.RPC)”注解;訪問受限,似乎只能本機訪問(應該會綁定到publish的URL上,如下使用localhost的話就只能本機訪問)……):
package mian;import javax.xml.ws.Endpoint;import service.imp.SayHiServiceImp;public class Main {/*** 發布WebService* 簡單*/public static void main(String[] args) {Endpoint.publish("http://localhost:8080/testjws/service/sayHi", new SayHiServiceImp());}}方式二(基于web服務器Servlet方式):
以Tomcat為例,首先編寫sun-jaxws.xml文件并放到WEB-INF下:
<?xml version="1.0" encoding="UTF-8"?> <endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"version="2.0"><endpoint name="SayHiService"implementation="service.imp.SayHiServiceImpl"url-pattern="/service/sayHi" /> </endpoints>然后改動web.xml,添加listener和servlet(url-pattern要相同):
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"><listener> <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener </listener-class></listener><servlet><servlet-name>SayHiService</servlet-name> <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet </servlet-class></servlet> <servlet-mapping> <servlet-name>SayHiService</servlet-name> <url-pattern>/service/sayHi</url-pattern> </servlet-mapping><welcome-file-list><welcome-file>index.html</welcome-file><welcome-file>index.htm</welcome-file><welcome-file>index.jsp</welcome-file></welcome-file-list> </web-app>最后部署到Tomcat里,值得一提的是您可能需要添加以下jar包(因為Tomcat沒有):
啟動Tomcat。
服務端工作就完成了,注意兩個事情。
注意:項目需要使用UTF-8編碼(至少sun-jaxws.xml必須是UTF-8格式的);
對于MyEclipse的內置Tomcat,可能會出現不需要手動添加上述jar包,但獨立部署時應該添加,因為它們使用的class-path不一樣;
多個不同路徑的接口也要使用同一個WSServlet;
最好加上@SOAPBinding(style = SOAPBinding.Style.RPC)注解。
部署好了之后打開瀏覽器輸入網址:http://localhost:8080/testjws/service/sayHi?wsdl。可以看到東西就證明發布成功了。
附上項目樹狀圖:
最后是客戶端使用,由于WebService是平臺和語言無關的基于xml的,所以我們完全可以使用不同語言來編寫或生成客戶端。
一般有三種方式來使用(對于Java語言而言):
一,使用jdk自帶工具wsimport生成客戶端:
jdk自帶的wsimport工具生成,上圖我是把客戶端文件生成到了桌面src文件中(-d),并保留了源文件(-keep),指定了包名(-p)。
然后我們就可以使用生成的文件來調用服務器暴露的方法了:
值得一提的是你生成使用的jdk和你客戶端的jre需要配套!
從上面的目錄結構我們可以發現:服務端的每個webmethod都被單獨解析成為了一個類(如果使用了實體,實體也會被解析到客戶端,并且是源碼,所以建議使用實體時慎重)。
(上面的圖是舊圖,只是為了表示一下jaxws是為每個webmethod生成類的情況)
而我們的service則被生成了一個代理類來調用服務,接下來我們看看使用情況:
package test;import java.util.Date; import java.util.GregorianCalendar;import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar;import testjws.client.SayHiService; import testjws.client.SayHiServiceImpService;public class Main {public static void main(String[] args) throws DatatypeConfigurationException {// 獲取serviceSayHiService service = new SayHiServiceImpService().getSayHiServiceImpPort();// sayhiservice.sayHiDefault();service.sayHi("Ahe");// checktime// 這里主要說一下時間日期的xml傳遞,方法還略顯復雜GregorianCalendar calender = new GregorianCalendar();calender.setTime(new Date(System.currentTimeMillis()));XMLGregorianCalendar xmldate = DatatypeFactory.newInstance().newXMLGregorianCalendar(calender);System.out.println(service.checkTime(xmldate));}}看看服務器的輸出,我們是否調用成功:
成功了!
對于校驗時間的方法客戶端也收到反饋了:
二,使用諸如MyEclipse(Eclipse for Jave EE也可以)創建一個Web Service Client的項目
然后填入wsdl地址即可,后續步驟我就不貼出了。
-----------------------------------------------------------------------------------------------------------
以java rmi? 方式實現rpc
RMI遠程調用步驟:
1,客戶對象調用客戶端輔助對象上的方法
2,客戶端輔助對象打包調用信息(變量,方法名),通過網絡發送給服務端輔助對象
3,服務端輔助對象將客戶端輔助對象發送來的信息解包,找出真正被調用的方法以及該方法所在對象
4,調用真正服務對象上的真正方法,并將結果返回給服務端輔助對象
5,服務端輔助對象將結果打包,發送給客戶端輔助對象
6,客戶端輔助對象將返回值解包,返回給客戶對象
7,客戶對象獲得返回值
對于客戶對象來說,步驟2-6是完全透明的
1、創建遠程方法接口,該接口必須繼承自Remote接口
Remote 接口是一個標識接口,用于標識所包含的方法可以從非本地虛擬機上調用的接口,Remote接口本身不包含任何方法
package server; import java.rmi.Remote; import java.rmi.RemoteException; public interface Hello extends Remote { public String sayHello(String name) throws RemoteException; }由于遠程方法調用的本質依然是網絡通信,只不過隱藏了底層實現,網絡通信是經常會出現異常的,所以接口的所有方法都必須拋出RemoteException以說明該方法是有風險的
2、創建遠程方法接口實現類:
UnicastRemoteObject類的構造函數拋出了RemoteException,故其繼承類不能使用默認構造函數,繼承類的構造函數必須也拋出RemoteException
由于方法參數與返回值最終都將在網絡上傳輸,故必須是可序列化的
package server; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class HelloImpl extends UnicastRemoteObject implements Hello { private static final long serialVersionUID = -271947229644133464L; public HelloImpl() throws RemoteException{ super(); } public String sayHello(String name) throws RemoteException { return "Hello,"+name; } } 3、利用java自帶rmic工具生成sutb存根類(jdk1.5.0_15/bin/rmic)jdk1.2以后的RMI可以通過反射API可以直接將請求發送給真實類,所以不需要skeleton類了
sutb存根為遠程方法類在本地的代理,是在服務端代碼的基礎上生成的,需要HelloImpl.class文件,由于HelloImpl繼承了Hello接口,故Hello.class文件也是不可少的
Test
- - server
- - - - Hello.class
- - - - HelloImpl.class
方式一:
[name@name Test]$ cd /home/name/Test/ [name@name Test]$ rmic server.HelloImpl方式二:
[name@name Test]$ rmic -classpath /home/name/Test server.HelloImpl?運行成功后將會生成HelloImpl_Stub.class文件
4、啟動RMI注冊服務(jdk1.5.0_15/bin/rmiregistry)
方式一:后臺啟動rmiregistry服務
[name@name jdk]$ jdk1.5.0_15/bin/rmiregistry 12312 & [1] 22720 [name@name jdk]$ ps -ef|grep rmiregistry name 22720 13763 0 16:43 pts/3 00:00:00 jdk1.5.0_15/bin/rmiregistry 12312 name 22737 13763 0 16:43 pts/3 00:00:00 grep rmiregistry如果不帶具體端口號,則默認為1099
方式二:人工創建rmiregistry服務,需要在代碼中添加:
LocateRegistry.createRegistry(12312); 5、編寫服務端代碼package server; import java.rmi.Naming; import java.rmi.registry.LocateRegistry; public class HelloServer { public static void main(String[] args) { try{ Hello h = new HelloImpl(); //創建并導出接受指定port請求的本地主機上的Registry實例。 //LocateRegistry.createRegistry(12312); /** Naming 類提供在對象注冊表中存儲和獲得遠程對遠程對象引用的方法 * Naming 類的每個方法都可將某個名稱作為其一個參數, * 該名稱是使用以下形式的 URL 格式(沒有 scheme 組件)的 java.lang.String: * //host:port/name * host:注冊表所在的主機(遠程或本地),省略則默認為本地主機 * port:是注冊表接受調用的端口號,省略則默認為1099,RMI注冊表registry使用的著名端口 * name:是未經注冊表解釋的簡單字符串 */ //Naming.bind("//host:port/name", h); Naming.bind("rmi://192.168.58.164:12312/Hello", h); System.out.println("HelloServer啟動成功"); }catch(Exception e){ e.printStackTrace(); } } }
先創建注冊表,然后才能在注冊表中存儲遠程對象信息
6、運行服務端(58.164):
Test
- - server
- - - - Hello.class
- - - - HelloImpl.class
- - - - HelloServer.class
[name@name ~]$ java server.HelloServer HelloServer啟動成功當然/home/name/Test一定要在系統CLASSPATH中,否則會報找不到相應的.class文件
7、編寫客戶端代碼
package client; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; import server.Hello; public class HelloClient { public static void main(String[] args) { try { Hello h = (Hello)Naming.lookup("rmi://192.168.58.164:12312/Hello"); System.out.println(h.sayHello("zx")); } catch (MalformedURLException e) { System.out.println("url格式異常"); } catch (RemoteException e) { System.out.println("創建對象異常"); e.printStackTrace(); } catch (NotBoundException e) { System.out.println("對象未綁定"); } }8、運行客戶端(58.163):
Test
- - client
- - - - HelloClient.class
- - server
- - - - Hello.class
- - - - HelloImpl_Stub.class//服務端生成的存根文件
[name@name client]$ java client.HelloClient Hello,zx同服務器端,/home/name/Test一定要在系統CLASSPATH中
PS:
1、客戶端所在服務和服務端所在的服務器網絡一定要通(一開始浪費了很多時間,最后才發現是網絡不通)
2、所有代碼在jdk1.5.0_15,Linux服務器上調試通過
3、如果java命令運行提示找不到類文件,則為CLASSPATH配置問題
[name@name ~]$ vi .bash_profile JAVA_HOME=/home/name/jdk/jdk1.5.0_15 export JAVA_HOME PATH=$JAVA_HOME/bin:$PATH export PATH CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:/home/name/Test export CLASSPATHJAVA_HOME為jdk的根目錄
PATH為java工具類路徑(java,javac,rmic等)
CLASSPATH為java?.class文件的存放路徑,使用java命令運行.class文件時即會在該參數配置的路徑下尋找相應文件
java RMI的缺點:
1、從代碼中也可以看到,代碼依賴于ip與端口
2、RMI依賴于Java遠程消息交換協議JRMP(Java Remote Messaging Protocol),該協議為java定制,要求服務端與客戶端都為java編寫
總結
以上是生活随笔為你收集整理的java RPC 初步了解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 领导干部违规插手干预工程项目问题治理方案
- 下一篇: Maven的Archetype简介