用的netty实现,没搭建注册中心,就是只有客户端和服务端,实现客户端远程调用服务端的sevice
仓库地址,https://github.com/wuhene/rpc-demo 结合代码看文章会更好理解,点个star支持下八~
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-allartifactId>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.83version>
dependency>
dependencies>
所有的网络传输涉及到的消息对象都实现Message接口
这样抽象出来的作用是为了让每个rpc消息都能经过我们自定义的编/解码处理器
public interface Message {
public byte getType();
}
MessageType类:定义消息类型和消息类型映射的class对象,用于decode时反序列化
public class MessageType {
public static final byte RPC_REQUEST = 1;
public static final byte RPC_RESPONSE = 2;
private static Map<Byte,Class> map = new HashMap<>();
static {
map.put(RPC_REQUEST, RpcRequest.class);
map.put(RPC_RESPONSE, RpcResponse.class);
}
/**
* 消息类型与class类型映射转换
* @param type 消息类型
* @return class类型
*/
public static Class type2Class(byte type){
return map.get(type);
}
}
RpcRequest:客户端发起Rpc请求消息
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RpcRequest implements Message {
/**
* 请求id,必须保证唯一,接口结果才不会出现并发问题
*/
private long requestId;
/**
* 接口全限定名
*/
private String interfaceName;
/**
* 调用的方法名
*/
private String methodName;
/**
* 方法所需参数的类型
*/
private Class[] parameterTypes;
/**
* 方法传递进去的具体参数值
*/
private Object[] parameterValues;;
@Override
public byte getType() {
return MessageType.RPC_REQUEST;
}
}
RpcResponse:服务端响应给客户端这次远程调用的结果
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RpcResponse implements Message {
/**
* 请求id,必须保证唯一,接口结果才不会出现并发问题 由请求端传过来
*/
private long requestId;
/**
* 结果
*/
private Object res;
/**
* 是否调用成功
*/
private boolean isSuccess;
/**
* 异常信息
*/
private Exception exception;
@Override
public byte getType() {
return MessageType.RPC_RESPONSE;
}
}
客户端和服务器端消息通讯的处理器,所以应该既是入站处理器也是出站处理器;入站就作为首个处理器进行解码,出站就作为最后一个处理器进行编码
@ChannelHandler.Sharable
@Component
@Slf4j
public class MessageCodec extends MessageToMessageCodec<ByteBuf, Message> {
private byte[] procotl = new byte[]{'w','h','r','p','c'};
}
encode方面:
一次请求包含以下内容:请求头(协议、消息类型、内容大小)和消息体;填充内容只是为了把请求头填满16字节,强迫症
实际内容序列化为json,目前没写其他类型
//编码后把结果放进list,会往上传递给接下来的处理器
@Override
protected void encode(ChannelHandlerContext ctx, Message message, List<Object> list) throws Exception {
try {
ByteBuf byteBuf = ctx.alloc().buffer();
byteBuf.writeBytes(procotl);//5个字节的协议头
byteBuf.writeByte(message.getType());//1字节消息类型
byte[] content = JSONUtils.toJson(message).getBytes();
byteBuf.writeBytes(new byte[]{'h','c','s','c','s','s'});//6字节填充,正好把请求头填满16字节
byteBuf.writeInt(content.length);//4字节内容大小
byteBuf.writeBytes(content);
list.add(byteBuf);
} catch (Exception e){
log.error("编码异常:",e);
}
}
decode方面:
根据encode的内容进行解码,将消息体反序列化为对象传递给下一逻辑处理器
@Override
//解码后把结果放进list,会往下传递给接下来的处理器
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
try {
byte[] protocol = new byte[5];
byteBuf.readBytes(protocol);//5字节协议头
if (!protocolCheck(protocol)) return;//协议校验不通过
byte type = byteBuf.readByte();//1字节消息类型
byteBuf.readBytes(new byte[6]);//6字节填充
int length = byteBuf.readInt();//4字节内容大小
byte[] content = new byte[length];//实际内容
byteBuf.readBytes(content);
Object obj = JSONUtils.fromJson(new String(content), MessageType.type2Class(type));
list.add(obj);
} catch (Exception e) {
log.error("编码异常:",e);
}
}
/**
* 协议头校验
* @param bytes 协议头内容
* @return 布尔
*/
private boolean protocolCheck(byte[] bytes){
for (int i = 0; i < procotl.length; i++) {
if (bytes[i] != procotl[i]) return false;
}
return true;
}
这个就是定义出remote接口,用来测试客户端能否根据接口调用到服务端的具体实现类
public interface UserServiceRemote {
User getUser(long userId);
void saveUser(User user);
}
public class JSONUtils {
public static String toJson(Object object){
return JSON.toJSONString(object);
}
/**
* 反序列化json
* @param json json串
* @param tClass 对象class类型
* @param 泛型
* @return 对象
*/
public static <T> T fromJson(String json,Class<T> tClass){
return JSON.parseObject(json,tClass);
}
/**
* 反序列化json为list
* @param json json传
* @param tClass 对象class类型
* @param 泛型
* @return list集合
*/
public static <T> List<T> fromJson2List(String json,Class<T> tClass){
return JSON.parseArray(json,tClass);
}
}
用于其他服务引入此公共包时扫描作用,主要也就是扫一个编/解码处理器
@ComponentScan(basePackages = {"com.wuhen.common"})
public class Config {
@Bean
public LoggingHandler loggingHandler(){
return new LoggingHandler();
}
}
为容器导入配置类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(Config.class)
public @interface EnableRpc {
}
<dependencies>
<dependency>
<groupId>com.wuhengroupId>
<artifactId>rpc-commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
根据客户端提供的接口全限定名获取服务端具体的实现类实例
public class ServiceFactory {
private static final Map<String,Object> SERVICE_MAP = new ConcurrentHashMap<>();
/**
* 将实例service注册到工厂中
* @param bean 实例service
*/
public static void register(Object bean){
SERVICE_MAP.put(bean.getClass().getInterfaces()[0].getName(),bean);
}
/**
* 获取service实例
* @param interfaceName 接口全限定名
* @return 对象
*/
public static Object getService(String interfaceName){
return SERVICE_MAP.get(interfaceName);
}
}
@Slf4j
@ChannelHandler.Sharable
@Component
@Order(1)
public class RpcRequestHandler extends SimpleChannelInboundHandler<RpcRequest> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, RpcRequest rpcRequest) throws Exception {
log.info("rpc 请求信息:{}", JSONUtils.toJson(rpcRequest));
RpcResponse response = new RpcResponse();
try {
Object service = ServiceFactory.getService(rpcRequest.getInterfaceName());
if (service == null) throw new Exception("未找到service对象");
Method method = service.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParameterTypes());
Class[] parameterTypes = rpcRequest.getParameterTypes();
Object[] parameterValues = rpcRequest.getParameterValues();
for (int i = 0; i < parameterTypes.length; i++) {
parameterValues[i] = JSONUtils.fromJson(JSONUtils.toJson(parameterValues[i]),parameterTypes[i]);
}
response.setRequestId(rpcRequest.getRequestId());
response.setRes(method.invoke(service, rpcRequest.getParameterValues()));
response.setSuccess(true);
} catch (Exception e) {
response.setSuccess(false);
response.setException(e);
log.error("rpc请求发生异常,请求id:{}, 异常信息为:",rpcRequest.getRequestId(),e);
}
log.info("rpc 响应信息:{}", JSONUtils.toJson(response));
channelHandlerContext.channel().writeAndFlush(response);
}
}
就是测试用的类,随便写写逻辑
@Service
@Slf4j
public class UserServiceRemoteImpl implements UserServiceRemote {
@PostConstruct
public void postConstruct(){
ServiceFactory.register(this);
}
@Override
public User getUser(long userId) {
User user = new User();
user.setUserId(userId);
user.setUsername("紧张的无痕");
return user;
}
@Override
public void saveUser(User user,long pid) {
log.info("保存用户:{}", JSONUtils.toJson(user));
log.info("视频pid:{}",pid);
}
}
@ConfigurationProperties(prefix = "rpc.server")
@Component
@Slf4j
@Data
public class Server {
private int port;
private int maxFrameLength;
@Autowired
private LoggingHandler loggingHandler;
@Autowired
private List<SimpleChannelInboundHandler> hanlders;
@Autowired
private MessageCodec messageCodec;
@PostConstruct
public void start(){
new Thread(() -> {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
try {
bootstrap.group(boss,worker);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel sc) throws Exception {
sc.pipeline().addLast(new LengthFieldBasedFrameDecoder(maxFrameLength,
12,4,0,0));
sc.pipeline().addLast(loggingHandler);
sc.pipeline().addLast(messageCodec);
for (SimpleChannelInboundHandler hanlder : hanlders) {
sc.pipeline().addLast(hanlder);
}
}
});
log.info("启动RPC服务端...");
ChannelFuture channelFuture = bootstrap.bind(8080).sync();
log.info("RPC服务端启动成功,port:{}",port);
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
log.error("发生异常,信息为:",e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
},"rpc-server").start();
}
}
rpc:
server:
port: 8080 #端口号
maxFrameLength: 102400 #最大帧长度 单位byte
@EnableRpc
@SpringBootApplication
public class ServerApp {
public static void main(String[] args) {
SpringApplication.run(ServerApp.class,args);
}
}
<dependencies>
<dependency>
<groupId>com.wuhengroupId>
<artifactId>rpc-commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
根据接口的interfaceClass创建代理对象,主类RpcProxyFactory
RpcProxyFactory
@Slf4j
public class RpcProxyFactory {
private static final AtomicLong ID_GENERATOR = new AtomicLong(0);//请求id生成器
private static final Map<Long,Promise<Object>> PROMISE_MAP = new ConcurrentHashMap<>();//存储请求id与接收promise的映射
private static Channel channel;
/**
* 获取代理对象
* @param interfaceClass 接口class
* @param
* @return 代理对象
*/
public static <T> T getProxy(Class<T> interfaceClass){
Object o = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass},
new RpcProxyHandler(interfaceClass.getName()));
return (T)o;
}
/**
* 设置通信channel
* @param channel
*/
public static void setChannel(Channel channel) {
RpcProxyFactory.channel = channel;
}
/**
* promise不可复用,一次请求一个
* @param requestId
* @return
*/
public static Promise<Object> getPromise(long requestId) {
return PROMISE_MAP.remove(requestId);
}
}
RpcProxyHandler:RpcProxyFactory的静态内部类,InvocationHandler实现类,主要方法调用逻辑在这,发起rpc请求后,创建一个Promise,然后等待后端响应后唤醒
static class RpcProxyHandler implements InvocationHandler {
private String interfaceName;
public RpcProxyHandler(String interfaceName) {
this.interfaceName = interfaceName;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RpcRequest rpcRequest = new RpcRequest();
long requestId = ID_GENERATOR.incrementAndGet();
rpcRequest.setRequestId(requestId);
rpcRequest.setInterfaceName(interfaceName);
rpcRequest.setMethodName(method.getName());
rpcRequest.setParameterTypes(method.getParameterTypes());
rpcRequest.setParameterValues(args);
channel.writeAndFlush(rpcRequest);
DefaultPromise<Object> promise = new DefaultPromise<>(channel.eventLoop());
PROMISE_MAP.put(requestId,promise);
promise.await();
if (promise.isSuccess()){
Object res = promise.getNow();
/**
* fastjson的一个问题,就是会把object类型序列化成JSONObject类型
* 所以这里反序列化出来是一个JSONObject类型,返回给consumer会报类型转换异常
* 所以在这里我们要手动toString再反序列为consumer要的类型
*/
res = JSONUtils.fromJson(JSONUtils.toJson(res),method.getReturnType());
log.info("远程调用结果为:{}",res);
return res;
}
log.info("远程调用失败,异常为:",promise.cause());
return null;
}
}
@ChannelHandler.Sharable
@Component
@Slf4j
@Order(1)
public class RpcResponseHandler extends SimpleChannelInboundHandler<RpcResponse> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, RpcResponse rpcResponse) throws Exception {
log.info("远程调用结果,response:{}", JSONUtils.toJson(rpcResponse));
Promise<Object> promise = RpcProxyFactory.getPromise(rpcResponse.getRequestId());
if (promise == null) return;
if (!rpcResponse.isSuccess()) {
promise.setFailure(rpcResponse.getException());
}else {
promise.setSuccess(rpcResponse.getRes());
}
}
}
生成代理对象注入到spring容器中,这里后面可以换成递归加载包的方式,获取到class对象,然后调用工厂方法创建,再注入bean,就真的可以提供成包投入使用了
@Configuration
public class ClientConfig {
@Bean
public UserServiceRemote userServiceRemote(){
return RpcProxyFactory.getProxy(UserServiceRemote.class);
}
}
就是我们最后调用这个类进行测试,能否远程调用server端的userServiceRemote成功
@Component
@Slf4j
public class VideoServiceImpl implements VideoService {
@Autowired
private UserServiceRemote userServiceRemote;
@Override
public void get() {
User user = userServiceRemote.getUser(10001);
log.info("VideoSevice get Result:{}",JSONUtils.toJson(user));
}
@Override
public void save(User user,long pid) {
userServiceRemote.saveUser(user,pid);
}
}
@ConfigurationProperties(prefix = "rpc.client")
@Component
@Data
@Slf4j
public class Client {
private String host;
private int port;
private int maxFrameLength;
@Autowired
private LoggingHandler loggingHandler;
@Autowired
private List<SimpleChannelInboundHandler> handlers;
@Autowired
private MessageCodec messageCodec;
@Autowired
private VideoService videoService;
@PostConstruct
public void start(){
new Thread(() -> {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel sc) throws Exception {
sc.pipeline().addLast(new LengthFieldBasedFrameDecoder(maxFrameLength,
12,4,0,0));
sc.pipeline().addLast(loggingHandler);
sc.pipeline().addLast(messageCodec);
for (SimpleChannelInboundHandler handler : handlers) {
sc.pipeline().addLast(handler);
}
//这里省略了段代码,加下面第二点
}
});
log.info("启动RPC客户端...");
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
log.info("RPC客户端启动成功,host:{},port:{}",host,port);
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
log.error("发生异常,信息为:",e);
} finally {
worker.shutdownGracefully();
}
},"rpc-client").start();
}
}
这段代码就是我们测试的起点了,键盘输入1就请求videoService的get,2就请求save,这两个方法都会远程调用UserServiceRemote
sc.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("连接成功");
RpcProxyFactory.setChannel(ctx.channel());
new Thread(() -> {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String s = scanner.nextLine();
if (s.equals("1")){
videoService.get();
}else if (s.equals("2")){
videoService.save(new User(10011,"saber"),26);
}
}
}).start();
}
});
rpc:
client:
host: 127.0.0.1
port: 8080
maxFrameLength: 102400 #最大帧长度 单位byte
@EnableRpc
@SpringBootApplication
public class ClientApp {
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(ClientApp.class, args);
}
}