手写简易RPC框架
目录
简介
服务提供者
服务注册:注册中心
HttpServerHandler处理远程调用请求
consumer服务消费端
简介
RPC(Remote Procedure Call)——远程过程调用,它是一种通过网络从远程计算机程序上请求服务, 而不需要了解底层网络技术的协议,在面向对象的编程语言中,远程过程调用即是远程方法调用
基本实现思路如下:
项目结构:
- provider服务提供
- consumer服务消费
- registry注册
-
protocol协议
服务提供者
- 定义服务接口
接口HelloService
public interface HelloService {String sayHello(String message);
}
- 实现类HelloServiceImpl
public class HelloServiceImpl implements HelloService {@Overridepublic String sayHello(String name) {return name+ "调用了myRPC的服务";}
}
服务注册:注册中心
此处注册中心我们将服务注册在map集合中,结构:Map<String,Map<URL,Class>> 外边map的key存储 服务接口的全类名,URL封装了调用服务的ip和port,里边value指定指定具体实现类 注册中心类提供注册服务并暴露服务和发现服务功能:
public class URL {
private String hostname;private Integer port;@Overridepublic boolean equals(Object obj) {if(obj==null){return false;}if(!(obj instanceof URL)){return false;}URL url = (URL) obj;if(hostname.equals(((URL) obj).getHostname()) && port.intValue() == url.port.intValue()){return true;}return false;}
@Overridepublic int hashCode() {return hostname.hashCode();}
}
public class NativeRegistry {
private static Map<String, Map<URL,Class>> registCenter = new HashMap<>();
/*** 注册服务* @param url* @param interfaceName* @param implClass*/public static void regist(URL url,String interfaceName,Class implClass){
Map<URL,Class> map = new HashMap<>();map.put(url,implClass);registCenter.put(interfaceName,map);}
/*** 从注册中心获取服务* @param url* @param interfaceName* @return*/public static Class get(URL url,String interfaceName){return registCenter.get(interfaceName).get(url);}
}
- 注册服务
public class ServiceProvider {
public static void main(String[] args) {
//创建URLURL url = new URL("localhost", 8080);
//注册中心中注册服务NativeRegistry.regist(url, HelloService.class.getName(), HelloServiceImpl.class);
//启动并暴露服务HttpServer httpServer = new HttpServer();httpServer.start(url.getHostname(),url.getPort());
}
}
- 暴露服务
服务之间调用的通信协议采用http协议,所以在服务provider中启动tomcat暴露服务
添加内嵌tomcat的依赖
<!--内嵌tomcat--><dependencies><dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-core</artifactId><version>9.0.12</version></dependency></dependencies>
- 创建HttpServer
public class HttpServer {
/*** tomcat服务启动* 参考tomcat配置* <Server port="8005" shutdown="SHUTDOWN">* <Service name="Catalina">* <Connector port="8080" protocol="HTTP/1.1"* connectionTimeout="20000"* redirectPort="8443"* URIEncoding="UTF-8"/>* <Engine name="Catalina" defaultHost="localhost">* <Host name="localhost" appBase="webapps"* unpackWARs="true" autoDeploy="true">* <Context path="" doBase="WORKDIR" reloadable="true"/>* </Host>* </Engine>* </Service>* </Server>*/
/*** 启动服务* @param hostname* @param port*/public void start(String hostname,int port){// 实例一个tomcatTomcat tomcat = new Tomcat();
// 构建serverServer server = tomcat.getServer();
// 获取serviceService service = server.findService("Tomcat");
// 构建ConnectorConnector connector = new Connector();connector.setPort(port);connector.setURIEncoding("UTF-8");
// 构建EngineEngine engine = new StandardEngine();engine.setDefaultHost(hostname);
// 构建HostHost host = new StandardHost();host.setName(hostname);
// 构建ContextString contextPath = "";Context context = new StandardContext();context.setPath(contextPath);context.addLifecycleListener(new Tomcat.FixContextListener());// 生命周期监听器
// 然后按照server.xml,一层层把子节点添加到父节点host.addChild(context);engine.addChild(host);service.setContainer(engine);service.addConnector(connector);// service在getServer时就被添加到server节点了
// tomcat是一个servlet,设置路径与映射tomcat.addServlet(contextPath,"dispatcher",new DispatcherServlet());context.addServletMappingDecoded("/*","dispatcher");
try {tomcat.start();// 启动tomcattomcat.getServer().await();// 接受请求}catch (LifecycleException e){e.printStackTrace();}}
}
- DispatcherServlet
public class DispatcherServlet extends HttpServlet {
@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {new HttpServerHandler().handle(req,resp);}
}
HttpServerHandler处理远程调用请求
public class HttpServerHandler {
/*** 服务的处理* @param req* @param resp* @throws ServletException* @throws IOException*/public void handle(HttpServletRequest req, HttpServletResponse resp){try {//服务请求的处理逻辑
//1 通过请求流获取请求服务调用的参数InputStream inputStream = req.getInputStream();ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Invocation invocation = (Invocation) objectInputStream.readObject();
//2 从注册中心获取服务的列表Class implCass = NativeRegistry.get(new URL("localhost", 8080), invocation.getInterfaceName());
//3 调用服务 反射Method method = implCass.getMethod(invocation.getMethodName(),invocation.getParamTypes());
String result = (String) method.invoke(implCass.newInstance(), invocation.getParams());
//4 结果返回IOUtils.write(result,resp.getOutputStream());} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}
}
}
- 封装调用参数Invocation
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Invocation implements Serializable {
private String interfaceName;private String methodName;private Object[] params;private Class[] paramTypes;
}
- 启动服务
public class ServiceProvider {
public static void main(String[] args) {
//创建URLURL url = new URL("localhost", 8080);
//注册中心中注册服务NativeRegistry.regist(url, HelloService.class.getName(), HelloServiceImpl.class);
//启动并暴露服务HttpServer httpServer = new HttpServer();httpServer.start(url.getHostname(),url.getPort());
}
}
consumer服务消费端
- 封装HttpClient对象,发起远程调用
public class HttpClient {
/*** 远程方法调用* @param hostname :远程主机名* @param port :远程端口号* @param invocation :封装远程调用的信息*/public String post(String hostname, int port, Invocation invocation) {
try {URL url = new URL("http", hostname, port, "/client/");HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setRequestMethod("POST");connection.setDoOutput(true);// 必填项
//发送调用的信息OutputStream os = connection.getOutputStream();ObjectOutputStream oos = new ObjectOutputStream(os);oos.writeObject(invocation);oos.flush();oos.close();
// 将输入流转为字符串(此处可是java对象) 获取远程调用的结果InputStream is = connection.getInputStream();return IOUtils.toString(is);
} catch (IOException e) {e.printStackTrace();}return null;
}
}
- 调用测试
public class Consumer {public static void main(String[] args) {
//封装一个invocationInvocation invocation = new Invocation(HelloService.class.getName(), "sayHello2",new Object[]{"Test"}, new Class[]{String.class});
//远程调用服务String result = new HttpClient().post("localhost", 8080, invocation);
System.out.println("远程调用执行的结果result="+result);}
}