RPC,Remote Procedure Call,远程过程调用,是一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。在 OSI 网络通信模型中,RPC 跨越了传输层(第四层,传输协议 TCP/UDP,即通过 ip+port 进行通信)和应用层(第七层,传输协议有 HTTP、HTTPS、FTP 等)。RPC 使得开发分布式系统应用变得更加容易。
RPC 采用 C/S 模式。请求程序就是 Client,而服务提供程序就是 Server。首先,Client 发送一个带有请求参数的调用请求到 Server,然后等待响应。在 Server 端,进程一直处于睡眠状态直到接收到 Client 的调用请求。当一个调用请求到达,Server 会根据请求参数进行计算,并将计算结果发送给 Client,然后等待下一个调用请求。Client 接收到响应信息,即获取到调用结果,然后根据情况继续发出下一次调用。
我们这里要定义一个 RPC 框架,这个框架提供给用户后,用户只需要按照使用步骤就可以完成 RPC 远程调用。我们现在给出用户对于该 RPC 框架的使用步骤:
该 api 工程中用于存放业务接口、常量类、工具类等将来服务端与客户端均会使用到的一个接口与类。
创建工程rpc-api,并导入
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lhl</groupId>
<artifactId>rpc-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>rpc-api</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
定义业务接口
package com.lhl.rpc_api.service;
import com.lhl.rpc_api.service.databean.User;
/**
* 用户处理业务Service
*
* @author LIUHL
*
*/
public interface UserService {
/**
* 新增用户接口
*
* @param user
* 用户信息实体类
* @return 添加结果
*/
public String addUser(User user);
}
定义实体类信息
package com.lhl.rpc_api.service.databean;
import lombok.Data;
/**
* 用户信息
*
* @author LIUHL
*
*/
@Data
public class User {
// 注意一定要实现Serializable接口否则netty会报xception in thread "main" io.netty.handler.codec.EncoderException: java.io.NotSerializableException: com.lhl.rpc_api.service.databean.User
/*
* 用户名称
*/
private String userName;
}
这是定义了客户端请求服务端的调用信息
package com.lhl.rpc_api.invoke;
import java.io.Serializable;
/**
* 客户端调用信息
*
* @author LIUHL
*
*/
public class InvokeMessage implements Serializable {
/**
* 接口名,即微服务名称
*/
private String className;
/**
* 要远程调用的方法名
*/
private String methodName;
/**
* 参数类型列表
*/
private Class<?>[] paramTypes;
/**
* 参数值列表
*/
private Object[] paramValues;
}
该工程实现了API中定义的业务接口,并且创建netty服务器。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lhl</groupId>
<artifactId>rpc-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>rpc-server</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
<!-- 导入API工程 -->
<dependency>
<groupId>com.lhl</groupId>
<artifactId>rpc-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 导入netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.36.Final</version>
</dependency>
</dependencies>
</project>
package com.lhl.rpc_server.provider;
import com.lhl.rpc_api.service.UserService;
import com.lhl.rpc_api.service.databean.User;
/**
* 用户服务实现
*
* @author LIUHL
*
*/
public class UserServiceImpl implements UserService {
/**
* 添加用户并返回结果
*/
public String addUser(User user) {
return user.getUserName() + "注册成功";
}
}
package com.lhl.rpc_server.server;
import java.util.Map;
import com.lhl.rpc_api.invoke.InvokeMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* RPC框架服务端处理器,负责接收和处理客户端发来的调度
* SimpleChannelInboundHandler 泛型中定义的是服务端-客户端之间的消息通信类型
*
* @author LIUHL
*
*/
public class RpcServerHandler extends SimpleChannelInboundHandler<InvokeMessage> {
// 扫描过后的的服务名与对应的处理类
private Map<String, Object> registerMap;
public RpcServerHandler(Map<String, Object> registerMap) {
this.registerMap = registerMap;
}
/**
* 接受客户端发送过来的消息,并通过服务名称找到对应的实现类,通过反射机制执行对应业务,并将结果返回到客户端
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, InvokeMessage msg) throws Exception {
Object result = "没有该提供者,或没有该方法";
if (registerMap.containsKey(msg.getClassName())) {
// 从注册表中获取接口对应的实现类实例
Object invoker = registerMap.get(msg.getClassName());
result = invoker.getClass().getMethod(msg.getMethodName(), msg.getParamTypes()).invoke(invoker,
msg.getParamValues());
}
// 将运算结果返回给client
ctx.writeAndFlush(result);
ctx.close();
}
}
package com.lhl.rpc_server.server;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
/**
* 该类有以下两种功能
* 1.业务实现类的解析器 2.定义netty的服务端信息并提供启动方法
*
* @author LIUHL
*
*/
public class RpcServer {
// 注册表
private Map<String, Object> registerMap = new HashMap<String, Object>();
// 用于存放指定包中的业务接口的实现类名
private List<String> classCache = new ArrayList<String>();
// 发布服务:将指定包中的业务接口实现类实例写入到注册表
public void publish(String basePackage) throws Exception {
// 将指定包中的业务接口实现类名写入到classCache中
cacheClassCache(basePackage);
// 将指定包中的业务接口实现类实例写入到注册表
doRegister();
}
// 将指定包中的业务接口实现类名写入到classCache中
private void cacheClassCache(String basePackage) {
// 获取指定包目录中的资源
URL resource = this.getClass().getClassLoader()
// com.lhl.rpc_server.provider => com/lhl/rpc_server/provider
.getResource(basePackage.replaceAll("\\.", "/"));
// 若指定的目录中没有资源,则直接返回
if (resource == null) {
return;
}
File dir = new File(resource.getFile());
// 遍历指定目录中的所有文件
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
// 若当前file为目录,则递归
cacheClassCache(basePackage + "." + file.getName());
} else if (file.getName().endsWith(".class")) {
// 去掉文件名后的.class后辍
String fileName = file.getName().replace(".class", "").trim();
// 将类的全限定性类名写入到classCache
classCache.add(basePackage + "." + fileName);
}
}
// System.out.println(classCache);
}
// 将指定包中的业务接口实现类实例写入到注册表
// 注册表是一个map
// key为业务接口名,即微服务名称
// value为该业务接口对应的实现类实例
private void doRegister() throws Exception {
if (classCache.size() == 0) {
return;
}
for (String className : classCache) {
// 将当前遍历的类加载到内存
Class<?> clazz = Class.forName(className);
registerMap.put(clazz.getInterfaces()[0].getName(), clazz.newInstance());
}
}
// 启动服务器
public void start() throws InterruptedException {
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(parentGroup, childGroup)
// 用于指定当Server的连接请求处理线程全被占用时,
// 临时存放已经完成了三次握手的请求的队列的长度。
// 默认是50
.option(ChannelOption.SO_BACKLOG, 1024)
// 指定使用心跳机制来保证TCP长连接的存活性
.childOption(ChannelOption.SO_KEEPALIVE, true).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ObjectEncoder());
pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
pipeline.addLast(new RpcServerHandler(registerMap));
}
});
// 不使用同步的话 这一点不会阻塞会执行下面的关闭方法
ChannelFuture future = bootstrap.bind(8888).sync();
System.out.println("服务端已启动,监听的端口为:8888");
// 不使用同步的话 这一点不会阻塞会执行下面的关闭方法
future.channel().closeFuture().sync();
} finally {
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
}
}
package com.lhl.rpc_server.server;
/**
* 服务器启动类
*
* @author LIUHL
*
*/
public class RpcStarter {
public static void main(String[] args) throws Exception {
RpcServer server = new RpcServer();
// 指定业务实现类包路径
server.publish("com.lhl.rpc_server.provider");
server.start();
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lhl</groupId>
<artifactId>rpc-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>rpc-client</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
<!-- 导入API工程 -->
<dependency>
<groupId>com.lhl</groupId>
<artifactId>rpc-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 导入netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.36.Final</version>
</dependency>
</dependencies>
</project>
package com.lhl.rpc_client.client;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* 客户端处理类
*
* @author LIUHL
*
*/
public class RpcClientHandler extends SimpleChannelInboundHandler<Object> {
private Object result;
public Object getResult() {
return result;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
this.result = msg;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
package com.lhl.rpc_client.client;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.lhl.rpc_api.invoke.InvokeMessage;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
/**
* RPC框架的核心
* 使用动态代理生成业务接口的实现类,远程调用服务端取得结果
*
* @author LIUHL
*
*/
public class RpcProxy {
@SuppressWarnings("unchecked")
public static <T> T create(final Class<?> clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 若调用的是Object的方法,则直接进行本地调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 远程调用在这里发生
return rpcInvoke(clazz, method, args);
}
});
}
/**
* 服务端远程调用
*
* @param clazz
* @param method
* @param args
* @return
* @throws InterruptedException
*/
private static Object rpcInvoke(Class<?> clazz, Method method, Object[] args) throws InterruptedException {
final RpcClientHandler handler = new RpcClientHandler();
NioEventLoopGroup loopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(loopGroup).channel(NioSocketChannel.class)
// Nagle算法开关
.option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ObjectEncoder());
pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
pipeline.addLast(handler);
}
});
ChannelFuture future = bootstrap.connect("localhost", 8888).sync();
// 形成远程调用的参数实例
InvokeMessage invocation = new InvokeMessage();
invocation.setClassName(clazz.getName());
invocation.setMethodName(method.getName());
invocation.setParamTypes(method.getParameterTypes());
invocation.setParamValues(args);
// 将参数实例发送给Server
future.channel().writeAndFlush(invocation).sync();
future.channel().closeFuture().sync();
} finally {
loopGroup.shutdownGracefully();
}
return handler.getResult();
}
}
package com.lhl.rpc_client.consumer;
import com.lhl.rpc_api.service.UserService;
import com.lhl.rpc_api.service.databean.User;
import com.lhl.rpc_client.client.RpcProxy;
/**
* 用户Service的消费者
* @author LIUHL
*
*/
public class UserConsumer {
public static void main(String[] args) {
UserService service = RpcProxy.create(UserService.class);
User user = new User();
user.setUserName("张三");
System.out.println(service.addUser(user));
}
}
运行client工程的UserConsumer的main方法,出现下面结果
就算是成功了。
通过这个RPC框架也为了后续学习Dubbo打下基础
https://download.csdn.net/download/baidu_29609961/12323282