一、RPC是什么
RPC的全称是Remote Procedure call,是进程间通信方式。
他允许程序调用另一个地址空间的过程或者函数,不用去关注此过程或函数的实现细节。比如两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数或者方法,由于不在一个内存空间,不能直接调用,这时候需要通过就可以应用RPC框架的实现来解决。
二、RPC的实现
RPC有很多开源的框架实现这里主要介绍java自带的RMI
1、RMI是什么
RMI全称是Remote Method Invocation-远程方法调用,Java RMI在JDK1.1中实现的,其威力就体现在它强大的开发分布式网络应用的能力上,是纯Java的网络分布式应用系统的核心解决方案之一。其实它可以被看作是RPC的Java版本。要求客户端和服务端都要用java实现
2、RMI简单实例
(1)服务端代码实现:
IHello类实现:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IHello extends Remote{
/**
* 简单的返回“Hello World!"字样
* @return 返回“Hello World!"字样
* @throws java.rmi.RemoteException
*/
public String helloWorld() throws RemoteException;
/**
* 一个简单的业务方法,根据传入的人名返回相应的问候语
* @param someBodyName 人名
* @return 返回相应的问候语
* @throws java.rmi.RemoteException
*/
public String sayHelloToSomeBody(String someBodyName) throws RemoteException;
}
HelloImpl实现IHello实现:
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class HelloImpl extends UnicastRemoteObject implements IHello{
protected HelloImpl() throws RemoteException {
super();
}
@Override
public String helloWorld() throws RemoteException {
return "Hello Word";
}
@Override
public String sayHelloToSomeBody(String someBodyName) throws RemoteException {
return "你好," + someBodyName + "!";
}
}
HelloServer:
import java.net.MalformedURLException;
import java.nio.channels.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
/**
* Created by IntelliJ IDEA.
* Date: 2008-8-7 22:03:35
* 创建RMI注册表,启动RMI服务,并将远程对象注册到RMI注册表中。
*/
public class HelloServer {
public static void main(String args[]) throws java.rmi.AlreadyBoundException {
try {
//创建一个远程对象
IHello rhello = new HelloImpl();
//本地主机上的远程对象注册表Registry的实例,并指定端口为8888,这一步必不可少(Java默认端口是1099),必不可缺的一步,缺少注册表创建,则无法绑定对象到远程注册表上
LocateRegistry.createRegistry(8888);
//把远程对象注册到RMI注册服务器上,并命名为RHello
//绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的)
Naming.bind("rmi://localhost:8888/RHello",rhello);
// Naming.bind("//localhost:8888/RHello",rhello);
System.out.println(">>>>>INFO:远程IHello对象绑定成功!");
} catch (RemoteException e) {
System.out.println("创建远程对象发生异常!");
e.printStackTrace();
} catch (AlreadyBoundException e) {
System.out.println("发生重复绑定对象异常!");
e.printStackTrace();
} catch (MalformedURLException e) {
System.out.println("发生URL畸形异常!");
e.printStackTrace();
}
}
}
(2)客户端代码实现:
新建客户端工程GiveMeWords,客户端需要将服务端的IHello接口拷贝过来,并且必须和服务器端包名相同。否则会报如下错误(本人亲测):
java.lang.ClassNotFoundException: test.rmi.IHello (no security manager: RMI class loader)
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class HelloClient {
public static void main(String args[]){
try {
//在RMI服务注册表中查找名称为RHello的对象,并调用其上的方法
IHello rhello =(IHello) Naming.lookup("rmi://localhost:8888/RHello");
System.out.println(rhello.helloWorld());
System.out.println(rhello.sayHelloToSomeBody("熔岩"));
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
服务端工程结构截图
客户端工程结构截图
先运行服务器端代码,在运行客户端代码。运行结果:
三、Web Service
1、WebService是一种跨编程语言和跨操作系统平台的远程调用技术。
XML+XSD,SOAP和WSDL就是构成WebService平台的三大技术。Web Service采用http协议传输,数据格式为特定格式的XML。
SOAP协议=HTTP协议+XML协议
WSDL(Web Service Description Language)基于XML语音的,用于描述Web Service及其函数、参数和返回值。它是WebService客户端和服务器端都能理解的标准格式。WSDL文件保存在Web服务器上,通过一个url地址就可以访问到它。客户端要调用一个WebService服务之前,要知道该服务的WSDL文件的地址。WebService服务提供商可以通过两种方式来暴露它的WSDL文件地址:1.注册到UDDI服务器,以便被人查找;2.直接告诉给客户端调用者。也就是说我们要进行Web Service开发,通过服务器端的WSDL文件,我们就可以编写客户端调用代码。
服务端代码:
import javax.jws.WebService;
import javax.xml.ws.Endpoint;
@WebService
public class Function {
public String transWords(String words){
String res = "";
for(char ch : words.toCharArray()){
res += "\t" + ch + "\t";
}
return res;
}
public static void main(String[] args){
Endpoint.publish("http://localhost:9001/Service/Function", new Function());
System.out.println("publish success");
}
}
运行成功后访问http://localhost:9001/Service/Function?wsdl。wsdl文件如下:
<!--
Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.2.4-b01.
-->
<!--
Generated by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.2.4-b01.
-->
<definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://webService.test/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://webService.test/" name="FunctionService">
<types>
<xsd:schema>
<xsd:import namespace="http://webService.test/" schemaLocation="http://localhost:9001/Service/Function?xsd=1"/>
</xsd:schema>
</types>
<message name="transWords">
<part name="parameters" element="tns:transWords"/>
</message>
<message name="transWordsResponse">
<part name="parameters" element="tns:transWordsResponse"/>
</message>
<portType name="Function">
<operation name="transWords">
<input wsam:Action="http://webService.test/Function/transWordsRequest" message="tns:transWords"/>
<output wsam:Action="http://webService.test/Function/transWordsResponse" message="tns:transWordsResponse"/>
</operation>
</portType>
<binding name="FunctionPortBinding" type="tns:Function">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="transWords">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="FunctionService">
<port name="FunctionPort" binding="tns:FunctionPortBinding">
<soap:address location="http://localhost:9001/Service/Function"/>
</port>
</service>
</definitions>
WSDL 文档在Web服务的定义中使用下列元素:
- Types - 数据类型定义的容器,它使用某种类型系统(一般地使用XML Schema中的类型系统)。
- Message - 通信消息的数据结构的抽象类型化定义。使用Types所定义的类型来定义整个消息的数据结构。
- Operation - 对服务中所支持的操作的抽象描述,一般单个Operation描述了一个访问入口的请求/响应消息对。
- PortType - 对于某个访问入口点类型所支持的操作的抽象集合,这些操作可以由一个或多个服务访问点来支持。
- Binding - 特定端口类型的具体协议和数据格式规范的绑定。
- Port - 定义为协议/数据格式绑定与具体Web访问地址组合的单个服务访问点。
- Service- 相关服务访问点的集合。
然后在客户端项目下运行如下命令 wsimport -s Documents/workspace/GiveMeWords/src -p com.shu.service -keep http://localhost:9001/Service/Function?wsdl 即可自动生成客户端代码
Documents/workspace/GiveMeWords/src代码位置
com.shu.service包名
测试代码:
public class Test {
/**
* 测试webservice此作为客户端
* test项目中的webService包下面的Function作为服务端
*wsimport -s Documents/workspace/GiveMeWords/src -p com.shu.service -keep http://localhost:9001/Service/Function?wsdl
* @param args
*/
public static void main(String[] args){
Function fu = new FunctionService().getFunctionPort();
String str = fu.transWords("get my words");
System.out.println(str);
}
}
运行即可调用服务端的远程方法transWords()方法。但是客户端怎么知道服务端暴露出来的服务就是transWords呢,还有参数返回值这些客户端是怎么知道的?我们回到上面的WSDL文件:
<portType name="Function">
<operation name="transWords">
<input wsam:Action="http://webService.test/Function/transWordsRequest" message="tns:transWords"/>
<output wsam:Action="http://webService.test/Function/transWordsResponse" message="tns:transWordsResponse"/>
</operation>
</portType>
operation表情表明方法暴露服务的方法名是transWords。<input>标签标示输入参数,<output>函数返回值。这样我们就得到了我们想要的接口了。总之通过WSDL文件我们就可以进行编程。