在Soap方式调试webservice接口实例 中主要记录了用webservice soap方式去实现服务远程调用,远程调用是个很宽的概念,RPC(远程过程调用)和HTTP(网络传输协议)是两种最常见的远程调用方式。
一、RPC的概念
RPC(Remote ProcedureCall,远程过程调用)是一个很大的概念。它是一种通过网络从远程计算机程序上跨语言跨平台的请求服务。RPC能省略部分接口代码的开发,可以跨机器之间访问对象(JavaRMI),可以有更方便的加密和更高效的数据传输性能,而不需要了解底层网络技术的协议。RPC不仅可以走HTTP/HTTPS,也可以自定义TCP协议,从而省略HTTP繁杂的规则和冗余信息。
RPC的主要作用是解决分布式系统中服务之间的调用问题,能够在远程调用时,像调用本地方法一样方便,让调用者感知不到远程调用的逻辑。RPC主要是用在大型企业里面,因为大型企业里面系统繁多,业务线复杂,而且效率优势非常重要。而由于小型企业一般业务简单,不需要进行分布式架构,小型企业主要还是使用WebService中的RESTful WebService,部分特殊业务会使用Soap WebService。
二、使用soap、restful、rpc进行服务远程调用
1、soap
2、restful
一般使用RestTemplate来实现调用,在SpringCloud一般会把Ribbon+restTemplate组合使用,不过一般用openfeign;在SprongBoot单应用使用较多(而我还在用httpclient)
HttpClient和RestTemplate的使用:微服务(2):服务的调用方式:RPC和HTTP
3、rpc
单独展开
三、RPC的原理及基于JAVA的RPC基本实现
比如我们日常从本地开发程序,写完一个service,然后本地执行main作为client来调用一下,过程非常的流畅,毕竟都在一个项目里。
而一旦踏入公司尤其是大型互联网公司就会发现,公司的系统都由成千上万大大小小的服务组成,各服务部署在不同的机器上,由不同的团队负责。这时就会遇到两个问题:1)要搭建一个新服务,免不了需要依赖他人的服务,而现在他人的服务都在远端,怎么调用?2)其它团队要使用我们的新服务,我们的服务该怎么发布以便他人调用?
问题2服务发布就不在这里写了,以前有分析过,这里就写(抄)一个最简单的基于TCP协议的RPC远程调用例子
注: RPC跟HTTP不是对立面,RPC中可以使用HTTP作为通讯协议。RPC是一种设计、实现框架 ,通讯协议只是其中一部分。
首先,基于TCP协议的话,就使用Socket做通讯,Socket 是对 TCP/IP 协议的封装,是针对TCP或UDP的具体接口实现 ,提供了在传输层进行网络编程的方法。
声明接口和实现类:
package com.jin;
/**
* @author jinyunlong
* @date 2022/1/11 22:00
* @profession ICBC锅炉房保安
*/
public interface HelloRpc {
String hello(String name);
}
package com.jin;
/**
* @author jinyunlong
* @date 2022/1/11 22:00
* @profession ICBC锅炉房保安
*/
public class HelloRpcImpl implements HelloRpc {
@Override
public String hello(String name) {
return "hello "+name;
}
}
编写RPCClient的代码,这里要用到动态代理,因为用接口实现的方式,所以这里用JDK动态代理,通过如下代码,通过Socket发起网络请求,向RPCServer发送类信息,方法信息及方法参数信息,经RPCServer解析调用反射方法后返回代理对象给RPCClient。动态代理细节可以看Spring(5)
package com.jin;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;
/**
* @author jinyunlong
* @date 2022/1/11 21:38
* @profession ICBC锅炉房保安
*/
public class RPCProxy {
@SuppressWarnings("unchecked")
public static <T> T create(Object target){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), new InvocationHandler(){
@SuppressWarnings("resource")
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Socket socket = new Socket("192.168.6.1", 8899);
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
try {
output.writeUTF(target.getClass().getName());
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(args);
ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
try {
Object result = input.readObject();
if (result instanceof Throwable) {
throw (Throwable) result;
}
return result;
} finally {
input.close();
}
} finally {
output.close();
socket.close();
}
}
});
}
}
package com.jin;
/**
* @author jinyunlong
* @date 2022/1/11 22:23
* @profession ICBC锅炉房保安
*/
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
public class RPCServer {
public static ConcurrentHashMap<String, Object> classMap = new ConcurrentHashMap<String,Object>();
public static void main(String [] args) throws Exception{
System.out.println("server start");
RPCServer.invoker(8899);
}
public static void invoker(int port) throws Exception{
ServerSocket server = new ServerSocket(port);
for(;;){
try{
final Socket socket = server.accept();
new Thread(new Runnable() {
ObjectOutputStream output = null;
@Override
public void run() {
try{
try {
output = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
String className = input.readUTF();
String methodName = input.readUTF();
Class<?>[] parameterTypes = (Class<?>[])input.readObject();
Object[] arguments = (Object[])input.readObject();
Object claszz = null;
if(!classMap.containsKey(className)){
try {
claszz = Class.forName(className).newInstance();
classMap.put(className, claszz);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
}else {
claszz = classMap.get(className);
}
Method method = claszz.getClass().getMethod(methodName, parameterTypes);
Object result = method.invoke(claszz, arguments);
output.writeObject(result);
} catch (IOException | ClassNotFoundException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
output.writeObject(e);
}finally {
output.close();
}
}catch(Exception e){
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
测试main方法:
package com.jin;
/**
* @author jinyunlong
* @date 2022/1/11 22:25
* @profession ICBC锅炉房保安
*/
public class Main {
public static void main(String [] args){
HelloRpc helloRpc = new HelloRpcImpl(); //创建原始对象(接口实现类)
System.out.println(helloRpc.hello("opopopopopopopo")); //这里为原始对象调用hello()方法
helloRpc = RPCProxy.create(helloRpc); //用JDK动态代理方式,通过原始对象生成了代理对象
System.out.println(helloRpc.hello("jyl")); //代理对象和原始对象都实现同一接口HelloRpc,这里为代理对象调用hello()方法
}
}
为了方便看再调试一下,前一条是接口实现类原始对象调用方法,后一条则是代理对象调用方法。
一个简单的RPC之Socket实现,注册中心、负载均衡啥的都没有做。总之一定要用动态代理就完事了。
比较多的场景就是服务端开发完了将接口丢给注册中心管理,然后客户端代理服务接口生成代理对象,这个过程像是在本地调用方法一样。
参考文章:
1、javaRPC原理 这篇文章写的很好啊