简介
PRC是一种调用方式而不是一种协议
在本地调用方式时由于方法在同一个内存空间,所以程序中可以直接调用该方法,但是浏览器端和服务端程序是不在一个内存空间的,需要使用网络来访问,就需要使用TCP或者UDP协议,由于TPC协议是面向连接,基于字节流的,使用起来不太方便,于是在此基础上衍生了http,gprc等协议。
RPC协议底层可以使用http协议或者TCP协议。
为什么需要rpc协议:
RPC协议是主机之间的调用协议,HTTP是浏览器和主机之间的调用协议。
不同主机之间服务远程调用时由于需要通过网络,所以需要定义很多规则,RPC远程调用方式就是希望远程调用方法时像本地调用方法一样省去过多的细节。基于远程调用方式也衍生一些协议如,gPRC,thrift。
HtppClient
HttpClinet就是在服务器端通过Java代码模拟一个小型浏览器,获取获取数据后进行序列化与反序列化操作。
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
注意是Apache的HttpClinet
。
- 控制器服务
@RestController
@RequestMapping("/test")
public class TestControoler {
@GetMapping("/hello")
String Hello(){
return "Hello";
}
}
- HttpClient远程代用服务
@RestController
@RequestMapping("/http")
public class HttpCilentController {
@GetMapping("/getHello")
String gethello(){
//声明响应类
HttpResponse execute = null;
//创建http服务端实例
HttpClient client = HttpClients.createDefault();
//发送请求
HttpGet get = new HttpGet("http://localhost:8080/test/hello");
try {
execute = client.execute(get);
}catch (IOException e){ e.printStackTrace();}
//获取响应体
HttpEntity entity = execute.getEntity();
//工具了解析
String str = null;
try {
str = EntityUtils.toString(entity, "utf-8");
}catch (IOException e){e.printStackTrace();}
//这里的String就是一个json字符串,如果该字符串是一个类免责需要再次使用工具如jackson,fastjson将josn字符串转为类。
return str;
}
}
在B远程调用的A过程中,实际上是在B的服务内部实现了一个浏览器服务请求服务器返回JSON字符。
RestTemplate
RestTemplate
是基于spring封装的HttpClient。在任何Java项目导入httpclient
依赖后就可以使用。RestTemplate只能在spring项目中使用,并且spring本身封装了HttpClient,使用起来也更方便。
HttpClient是一种用于发送HTTP请求的原生Java库,Apache Commons HttpClient是建立在HttpClient基础上的第三方工具类库,RestTemplate是Spring框架中封装的HTTP请求操作类,Feign是一种声明式的Web服务客户端,Forest是一个为微服务开发而编写的客户端应用程序框架,他们的实现都是基于HttpClient的。
在后续的学习中还会接触到spring cloud的Feign。另外还有其他框架对HttpClient封装是其操作更加方便。如Forest,okhttp等。除了OkHttp只支持HTTP请求外,其他工具都支持Http和https协议。不同的是,HttpClient和Forest是对同一个底层框架的封装,在性能上比Okhttp要好很多;RestTemplate和Feign都是基于Spring框架的封装,支持并发,实现起来更易于维护。
@RestController
@RequestMapping("/template")
public class TemplateControoler {
RestTemplate restTemplate = new RestTemplate();
@GetMapping("/getHello")
String getStr(){
String str = restTemplate.getForObject("http://localhost:8080/test/hello",String.class);
return str;
}
}
在spring中只需要少量的代码就可以完成功能,更加方便简洁。
RMI
RMI: 远程方法调用(Remote Method Invocation),它支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。
前两种实现RPC的方式是基于HTTP协议的,那么就需要在服务端模拟浏览器请求。RMI是直接基于TCP协议的。
Java RMI: 用于不同虚拟机之间的通信,这些虚拟机可以在不同的主机上、也可以在同一个主机上;一个虚拟机中的对象调用另一个虚拟上中的对象的方法,只不过是允许被远程调用的对象要通过一些标志加以标识,底层是通过Socket通信来进行实现的。
- 返回类
class Person{
private String name;
private int age;
private String address;
Person(String name,int age,String address){
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
- 控制器
@RestController
@RequestMapping("/rmi")
public class RmiControoler {
@GetMapping("/hello")
String Hello(){
return "Hello";
}
@GetMapping("/person")
Person person() throws RemoteException {return new TestServiceImpl().sendPerson();}
}
- rmi注册中心
public class RegisterCenter {
public static void main(String[] args) {
try {
// 创建本机上的远程对象注册表Registry的实例,默认端口1099
LocateRegistry.createRegistry(1099);
// 创建一个对象
TestServiceImpl testService = new TestServiceImpl();
// 把远程对象注册到RMI注册服务器上,testService
//绑定的URL标准格式为:rmi://host:port/name
//registry.rebind("testService", testService);
//Naming.rebind("rmi:localhost:1099/testService",testService);
Naming.rebind("testService",testService);
System.out.println("======= 启动RMI服务成功! =======");
} catch (RemoteException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
- 服务类
//服务接口
public interface TestService extends Remote {
public String sendHello() throws RemoteException ;
public Person sendPerson() throws RemoteException ;
}
// 服务实现类
/*
服务的方法实现类必须直接或简洁继承Remote并抛出RemoteException
*/
public class TestServiceImpl extends UnicastRemoteObject implements TestService {
public TestServiceImpl() throws RemoteException {
super();
}
public String sendHello (){return "Hello";}
public Person sendPerson(){return new Person("xiaoxu",22,"北京");}
}
- 客户端远程调用
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RmiClient {
public static void main(String[] args) {
try {
//创建RMI注册中心实例(通过socket连接)
Registry registry = LocateRegistry.getRegistry(1099);
//远程调用对象的实例化
Remote testService = registry.lookup("testService");
//类型强转
Person person = (Person) testService;
System.out.println("=======> " + person + " <=======");
} catch (NotBoundException | RemoteException e) {
e.printStackTrace();
}
}
}
启动主程序和注册中心
正常访问远程服务器返回参数
java.rmi.NotBoundException: testService
如上图所示,报错了,出现该问题可以是服务为注册到注册中心,或者名称错误,检查了好几遍,像如下的格式来回改,还是没成功:
registry.rebind("testService", testService);
Naming.rebind("rmi:localhost:1099/testService",testService);
最后发现了问题所在,在注册中心上下文脱节了,并没成功注册:
重构项目,注册中心和注册方法分开,如下:
将注册中心和注册方法分离,如下:
//注册中心,功能单一生成一个注册中心
public class RegisterCenter {
public static void main(String[] args) {
try {
// 创建本机上的远程对象注册表Registry的实例,默认端口1099
LocateRegistry.createRegistry(1099);
System.out.println("======= 启动RMI服务成功! =======");
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
//注册方法,将类注册到注册中心
public class Register {
public static void main(String[] args) throws RemoteException {
//获取注册中心
Registry registry = LocateRegistry.getRegistry(1099);
// 创建一个对象
TestServiceImpl testService = new TestServiceImpl();
// 把远程对象注册到RMI注册服务器上,testService
//绑定的URL标准格式为:rmi://host:port/name
registry.rebind("testService", testService);
}
}
注意注册中心注册的是类,但是类一般都有实现方法,而在其他主机上显然是没有该类的,因为实现类的耦合度高,所以必须使用接口,让实现类实现接口,这样,其他主机上只需要实现接口,就能接受实现类了,也是面向对象多态性的体现。
//客户端远程rmi调用
public class RmiClient {
public static void main(String[] args) {
try {
//创建RMI注册中心实例(通过socket连接)
Registry registry = LocateRegistry.getRegistry(1099);
//远程调用对象的实例化
Remote obj = registry.lookup("testService");
//Remote obj = Naming.lookup("rmi://:1099/testService");
//类型强转
TestService testService = (TestService) obj;
System.out.println(testService.sendHello());
//System.out.println("=======> " + testService.sendPerson().toString() + " <=======");
} catch (NotBoundException | RemoteException e) {
e.printStackTrace();
}
}
}
先启动注册中心,在启动注册任务,最后客户端远程调用:
上述是通过Registry
对象调用的,RMI还提供了该对象的封装类,Naming
实现。
注册中心必须和注册任务在一个主机上,这样Java了被注册到注册中心以供RMI注册中心通过协议向外暴露。
注册中心功能单一就是常见注册中心服务器:
LocateRegistry.createRegistry(1099);
注册中心基于自己ip创建,作为服务器,无需指明ip地址。
注册任务程序负责获取创建的注册,并将java类注册到注册中心内:
//获取本机注册中心
Registry registry = LocateRegistry.getRegistry(1099);
//获取指定地址的注册中心
Registry registry1 = LocateRegistry.getRegistry("192.168.223.128",1099);
//注册java类(名称注册默认ip地址)
registry.rebind("testService", testService);
//指定ip地址注册
registry.bind("rmi://192.168.245.1:1099/testService",testService);
Naming实现
Naming.rebind("rmi://192.168.245.1:1099/testService",testService);
Naming.rebind("testService",testService);
Remote obj = Naming.lookup("rmi://192.168.245.1:1099/testService");
Remote obj = Naming.lookup("testService");
参考文章-分布式架构基础:Java RMI详解感谢作者 😃