远程方法调用(Remote Method Invocation) 只适用于java语言
在本地调用,操作的是服务器端口在本地的副本,通过stub来重放服务器的端口;服务端也设置了一个扶助对象skeleton。实质上是stub和skeleton在直接交互
因此,RMI必须将服务端的接口在本地复制一份
远程过程调用(Remote Procedure Call Protocol) 跨平台、跨语言,客户端和服务器端之间通过网络服务协议来进行请求和响应
(1) 客户端和服务器端通过socket交互信息
1. 客户端以字符串的形式定义好要请求的接口名字,然后通过反射,解析出请求的接口名、方法名、参数类型、参数值等,并将这些信息通过对象流ObjectOutputStream发送给服务端
2. 服务端逐个解析这些信息,并通过反射中的invoke()方法调用服务端上被请求的方法
(2) 服务端可能存在多个提供方法的接口,简单实现一个服务注册中心
(3)服务注册中心
(4)服务完毕后,将返回值通过对象流返回给客户端
(5)客户端需要通过动态代理来获取服务端的返回值
package rmi.server;
/**
* @ClassName: RMIService
* @Description: 服务端的服务
* @Author sunsl
* @Date 2022/9/11 21:06
* @Version 1.0
*/
public interface RMIService {
public String hello(String name);
}
package rmi.server;
/**
* @ClassName: RMIServiceImpl
* @Description: TODO
* @Author sunsl
* @Date 2022/9/11 21:46
* @Version 1.0
*/
public class RMIServiceImpl implements RMIService {
@Override
public String hello(String name) {
return "服务端已收到请求,你好," + name;
}
}
package rmi.server;
/**
* @ClassName: RMIEureka
* @Description: 简单的服务注册中心
* @Author sunsl
* @Date 2022/9/11 21:48
* @Version 1.0
*/
public interface RMIEureka {
//启动服务
public void start() ;
//关闭服务
public void stop();
//注册服务
public void register(Class service, Class serviceImpl);
}
package rmi.server;
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.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @ClassName: RMIEurekaImpl
* @Description: 服务注册中心具体实现
* @Author sunsl
* @Date 2022/9/11 21:48
* @Version 1.0
*/
public class RMIEurekaImpl implements RMIEureka {
// map:服务端的所有可供客户端访问的接口,都注册到该map中
// key:接口的名字(如"RMIService"),
// value:真正的提供服务的类(如RMIServiceImpl类)
private static HashMap<String, Class> register = new HashMap<>();
//服务端的端口号
private static int port;
//线程池,获取当前环境CPU核数
private static ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//是否开启服务
private static volatile boolean isRunning = false;
// 构造函数,传入端口号
public RMIEurekaImpl(int port) {
this.port = port;
}
// 开启服务端服务
@Override
public void start() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
// 绑定IP地址
serverSocket.bind(new InetSocketAddress(port));
} catch (IOException e) {
e.printStackTrace();
}
isRunning = true;
//客户端每次请求一次请求,服务端从线程池中启动一个线程对象去处理
while (true){
//具体的服务内容:接收客户端请求,处理请求,并返回结果
System.out.println("服务已启动...");
Socket socket = null;
try {
//The method blocks until a connection is made.
socket = serverSocket.accept();// 等待客户端连接
} catch (IOException e) {
e.printStackTrace();
}
//启动一个线程 去处理客户请求
threadPool.execute(new ServiceTask(socket));
}
}
@Override
public void stop() {
isRunning = false;
threadPool.shutdown();
}
//将接口名和接口实现类一一对应,以便于接收到请求时,能及时获取到对应的服务实现类
@Override
public void register(Class service, Class serviceImpl) {
register.put(service.getName(), serviceImpl);
}
//处理请求的线程
private static class ServiceTask implements Runnable {
private Socket socket;
public ServiceTask() {
}
public ServiceTask(Socket socket) {
this.socket = socket;
}
// 处理逻辑
@Override
public void run() {
ObjectInputStream inputStream = null;
ObjectOutputStream outputStream = null;
try {
//接收客户端请求的参数
inputStream = new ObjectInputStream(socket.getInputStream());
// 因为ObjectInputStream对发送数据的顺序有严格要求,因此必须按照发送的顺序逐个接收
// 请求的接口名
String serviceName = inputStream.readUTF();
String methodName = inputStream.readUTF();
Class<?>[] parameterTypes = ( Class<?>[]) inputStream.readObject();
Object[] args = (Object []) inputStream.readObject();
// 根据客户请求,到服务注册中心map中找到与之对应的具体接口(即RMIService)
Class ServiceClass = register.get(serviceName);
// 构造请求的方法
Method method = ServiceClass.getMethod(methodName, parameterTypes);
// 执行该服务端方法
Object result = method.invoke(ServiceClass.newInstance(), args);
// 执行完成后返回给客户端
outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(result);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (outputStream != null)
outputStream.close();
if (inputStream != null)
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
package rmi.client;
/**
* @ClassName: RMIClient
* @Description: RMI客户端,发送请求,接收服务端返回的数据(代理模式
* @Author sunsl
* @Date 2022/9/11 21:05
* @Version 1.0
*/
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.InetSocketAddress;
import java.net.Socket;
@SuppressWarnings("all")
public class RMIClient {
/**
* 获取代表服务端接口的动态代理对象(RMIService)
* @param serviceInterface 请求的接口名
* @param addr 待请求服务端的ip:端口
* @param
* @return
*/
public static <T> T getRemoteProxyObj(Class serviceInterface, InetSocketAddress addr) {
/**
newProxyInstance(a,b,c)中,前两个参数的含义如下:
a:类加载器 :需要代理那个对象的类加载器
b:用于表示需要代理的对象提供了哪些方法。Java是单继承、多实现,
因此,如果某一个对象实现了多个接口,那么该对象就拥有其全部接口的所有方法,因此是一个接口数组。
例如,如果有A implements B接口,c接口,并且B接口中有3个方法,C接口中有2个方法,那么A的对象就拥有5个方法(暂不考虑A自身提供的方法)
*/
return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(),
new Class<?>[]{serviceInterface},
new InvocationHandler() {
//proxy:代理的对象, method:哪个方法(sayHi(参数列表)), args:参数列表
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 客户端向服务端发送请求,请求某一个具体的接口
Socket socket = new Socket();
ObjectOutputStream output = null;
ObjectInputStream input = null;
try {
//请求地址
socket.connect(addr);
//通过序列化流向服务端发送请求
output = new ObjectOutputStream(socket.getOutputStream());
//发送请求的接口名、方法名、参数类型、参数值
output.writeUTF(serviceInterface.getName());
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(args);
//等待处理...
//接收处理后返回的返回值
input = new ObjectInputStream(socket.getInputStream());
return input.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
// 关闭打开的流
try {
if (output != null) output.close();
if (input != null) input.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
}
package rmi.test;
import rmi.server.RMIEurekaImpl;
import rmi.server.RMIServiceImpl;
import rmi.server.RMIServiceImpl2;
import rmi.server.RMIService;
/**
* @ClassName: TestServer
* @Description: 开启服务端
* @Author sunsl
* @Date 2022/9/11 22:35
* @Version 1.0
*/
public class TestServer {
public static void main(String[] args) {
//用线程的形式启动服务
new Thread(new Runnable() {
@Override
public void run() {
//服务中心
RMIEurekaImpl server = new RMIEurekaImpl(9999);
//将RMIService接口及实现类,注册到服务中心
server.register(RMIService.class, RMIServiceImpl.class);
server.start();
}
}).start();
}
}
package rmi.test;
import rmi.client.RMIClient;
import rmi.server.RMIService;
import java.net.InetSocketAddress;
/**
* @ClassName: TestClient
* @Description: 开启客户端
* @Author sunsl
* @Date 2022/9/11 22:27
* @Version 1.0
*/
public class TestClient {
public static void main(String[] args) throws ClassNotFoundException {
//调用远程的rmi.server.RMIService接口,并执行接口中的sayHi()方法
RMIService remoteProxyObj = RMIClient.getRemoteProxyObj(
Class.forName("rmi.server.RMIService"),
new InetSocketAddress("127.0.0.1", 9999)
);
System.out.println(remoteProxyObj.hello("sunsl"));
}
}