RPC框架-1
定义
RPC(Remote Procedure Call,远程过程调用)是一种协议或技术,它允许一个程序(客户端)通过网络向另一个程序(服务器)发送请求,并且接收这个请求的程序能够像处理本地调用一样处理它,然后返回结果。RPC 是构建分布式系统和网络应用程序的基础技术之一。
个人理解:
客户端通过网络通信调用另外一台机器的进程并获取进程执行返回结果
客户端中远程接口的实现类名和方法名跟服务器中的接口实现类和方法名保持一致,方法中与服务器建立连接,发送调用方法名和方法参数给服务器并接收服务器发送的返回值,在主方法中进行调用时,就像直接调用了服务端中实现类中的方法
一、客户端
HelloWordImpl为与服务器HelloWord接口实现类名保持一致的客户端类
类中方法做的事情:
1.建立与服务器连接
2.发送方法名和方法参数
3.接收服务器发送的执行结果
Client.java
public class Client {
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(500);
String res = new HelloWorldImpl().sayHello("chatgpt");
System.out.println(res);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
HelloWordImpl.java
import java.net.Socket;
public class HelloWorldImpl {
//发送方法名和方法参数
//发送请求到服务器
//接收返回值
String sayHello(String name){
System.out.println("客户端方法参数为:"+name);
String ret = null ;
try {
Socket socket = new Socket("localhost",1234);
// System.out.println("客户端ois"+ois);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
// System.out.println("客户端oos"+oos);
oos.writeUTF("sayHello");
oos.writeUTF(name);
oos.flush();
ret = ois.readUTF();
// System.out.println("客户端接收到的返回结果为:"+ret);
} catch (IOException ioException) {
ioException.printStackTrace();
}
return ret;
}
}
注意点:
客户端新建Socket后,需要先获取输出流,再获取输入流,否则会导致连接无响应,连接不成功
网上找的可能原因:
在socket编程中,如果先获取输入流(InputStream)再获取输出流(OutputStream),可能会导致连接无响应的问题。这通常是因为在某些情况下,如果先获取了输入流,输入流的构造方法可能会进行一些预处理,如建立与远程服务器的连接或者进行握手等。如果紧接着获取输出流,而此时输出流还没准备好,可能会导致输出流的构造方法阻塞,因为它需要等待输入流的预处理完成。
解决方法是:
先获取输出流(OutputStream),然后再获取输入流(InputStream)。
如果使用的是基于TCP/IP的socket编程,可以在创建socket之后立即设置socket的TCP参数(如keep-alive、超时时间等),这样可以在获取流之前对socket进行一些预处理。
如果是使用高级的网络库(如Java中的HttpURLConnection),则应当遵循库的规定顺序来获取输入输出流。
客户端文件层级
接口类与服务器一致,实现类需要自己实现方法,通过网络通信获取服务器方法执行返回结果
客户端执行结果
二、服务器
服务器维护方法和实现类对象的map
服务端有一个配置文件维护方法名和实现类全名映射
服务端启动时读取文件内容,并根据反射获取类对象添加至map中
随后服务器监听连接,连接成功后接收客户端发送的方法名和方法参数
利用反射获取方法,执行方法,将执行结果发送回客户端
RpcInterface所有接口的父类,代表rpc接口
RpcInteface.java
public interface RpcInterface {
}
HelloWord.java
具体的rpc的接口
public interface HelloWorld extends RpcInterface {
String sayHello(String name);
}
HelloWordImpl.java
服务端接口实现,最后执行的
Server.java
服务端代码
package com.rpc.server;
import com.rpc.server.lib.RpcInterface;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
public class Server {
public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
// 启动时初始化调用列表
HashMap<String, RpcInterface> methodMap = new HashMap<>();
// methodMap.put("sayHello", new HelloWorldImp());
// methodMap.put("call", new CallImp());
// 读取方法表 配置文档
File file = new File("RPCServer (1)/config/methods.txt");
FileReader fileReader = new FileReader(file);
BufferedReader br = new BufferedReader(fileReader);
String listStr = null;
while ((listStr = br.readLine()) != null) {
System.out.println(listStr);
// 解析单行数据 方法名 类名
String[] methodMsg = listStr.split(" ");
String methodName = methodMsg[0];
String className = methodMsg[1];
// 反射获取类对象
Class<?> aClass = Class.forName(className);
// 加载类实例
Object object = aClass.newInstance();// 实例化
// 存储方法名-类实例
methodMap.put(methodName, (RpcInterface) object);
// System.out.println(listStr);
}
//启动服务
ServerSocket serverSocket = new ServerSocket(12345);
System.out.println("Server started");
while (true) {
try (Socket socket = serverSocket.accept();
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream())) {
ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
// 读第一句话 方法名
String methodName = input.readUTF();
if (methodMap.containsKey(methodName)) {
// 根据方法名 获取实例
RpcInterface rpcInterface = methodMap.get(methodName);
// 反射类
Class rpcClass = rpcInterface.getClass();
// 加载方法
Method[] methods = rpcClass.getMethods();
// 读第二句话 参数
String name = input.readUTF();
// 调用方法 获取返回结果
Object obj = methods[0].invoke(rpcInterface, name);
// 发送给调用者结果
output.writeUTF(obj.toString());
output.flush();
}
// if ("sayHello".equals(methodName)) {
读第二句话 参数
// String name = input.readUTF();
// String res = new HelloWorldImp().sayHello(name);
// output.writeUTF(res);
// output.flush();
// }
} catch (IOException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
注意点:
idea相对路径的根路径为当前项目路径,注意文件路径不要写错,写错会报java.io.FileNotFoundException,系统找不到指定的路径
methods.txt
服务器配置文件
sayHello com.rpc.HelloWorldImp
服务器文件层级
三、总结
上述为简单的远程过程调用示例代码
如需要在服务器中添加服务(即新接口及其实现类),需要编写接口和实现类代码,并手动在methods.txt文件中添加方法名和类全名
客户端如需要调用新增加的服务,需要维护接口及自己实现接口实现类