package github.com.AllenDuke.rpc.customer;

import github.com.AllenDuke.rpc.netty.NettyClient;
import github.com.AllenDuke.rpc.publicSource.Calculator;
import github.com.AllenDuke.rpc.publicSource.HelloService; /**
* @description 客户端启动类,从NettyClient处获取代理对象,发起RPC
* @contact AllenDuke@163.com
* @since 2020/2/11
*/
public class ClientBootstrap { //netty消费者
private static final NettyClient customer = new NettyClient(); //自定义的消息协议为: #interfaceName#methodName#2#arg1#arg2
//这里用了fastjson public static void main(String[] args) throws Exception { //创建代理对象
HelloService service = (HelloService) customer.getServiceImpl(HelloService.class);
String res = service.hello("Allen","Duke",2);
Calculator calculator=(Calculator) customer.getServiceImpl(Calculator.class);
calculator.add(1,2);
calculator.multipy(3,6);
}
}
package github.com.AllenDuke.rpc.netty;

import com.alibaba.fastjson.JSON;
import github.com.AllenDuke.concurrentTest.threadPoolTest.ThreadPoolService;
import github.com.AllenDuke.rpc.publicSource.Message;
import io.netty.bootstrap.Bootstrap;
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.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder; import java.lang.reflect.Proxy;
import java.util.concurrent.FutureTask; /**
* @description netty消费者
* @contact AllenDuke@163.com
* @since 2020/2/11
*/
public class NettyClient { //创建自己的线程池
private static ThreadPoolService executor = new ThreadPoolService(); public static NettyClientHandler clientHandler; /**
* @description: 返回一个代理对象,其中该代理类中的InvokeHandler中的invoke方法(jdk动态代理的知识)的作用是,
* 将调用信息封装成任务,提交到线程池,任务的返回值为 netty线程接收到的返回值
* @param serivceClass 要实现的接口
* @return: java.lang.Object
* @date: 2020/2/12
*/
public Object getServiceImpl(final Class<?> serivceClass) {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{serivceClass}, (proxy, method, args) -> {
//lamda表达式,匿名内部类实现InvokeInhandler接口,重写invoke方法 if(method.getName().equals("toString")) {
return "这是代理类的toString方法,避免递归调用";
}
if(clientHandler==null) init();//懒加载
String className = serivceClass.getName();
className=className.substring(className.lastIndexOf(".")+1)+"Impl";//去掉包名 // //自定义消息协议
// String msg=className+"#"+method.getName()+"#"+args.length;
// for (Object arg : args) {
// if(arg!=null) msg+="#"+arg;
// } //利用fastjson
Message message=new Message(className,method.getName(),args);
FutureTask<Object> task=new FutureTask<Object>(message);
executor.execute(task);
Object result=task.get();//主线程在这阻塞,等待结果
return JSON.parseObject((String) result,method.getReturnType());//将json文本转为对象
});
} //初始化netty客户端
private static void init() {
clientHandler=new NettyClientHandler();
//创建EventLoopGroup
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(
new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());//inbound编码器
pipeline.addLast(new StringEncoder());//outbound解码器
pipeline.addLast(clientHandler);//业务处理器
}
}
); try {
bootstrap.connect("127.0.0.1", 7000).sync();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package github.com.AllenDuke.rpc.netty;

import com.alibaba.fastjson.JSON;
import github.com.AllenDuke.rpc.publicSource.Message;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; /**
* @description netty消费者的业务处理器
* @contact AllenDuke@163.com
* @since 2020/2/11
*/
public class NettyClientHandler extends ChannelInboundHandlerAdapter{ private ChannelHandlerContext context; private Object result; //与服务器的连接创建后,就会被调用, 这个方法是第一个被调用
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
this.context=ctx;
} //netty线程收到消息,调用handler的chaanneltRead方法,唤醒线程池中的工作线程
@Override
public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
result = msg;
notify(); //唤醒等待的工作线程
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
} public synchronized void sendMsg(Message message) throws InterruptedException {
context.writeAndFlush(JSON.toJSONString(message));//netty线程发送json文本
wait(); //工作线程阻塞wait,等待channelRead方法获取到服务器的结果后,唤醒
System.out.println("服务端返回结果:"+result);
} public Object getResult(){
return result;
} }
package github.com.AllenDuke.rpc.provider;

import github.com.AllenDuke.rpc.netty.NettyServer;

/**
* @description 服务端启动类,启动netty服务提供者
* @contact AllenDuke@163.com
* @since 2020/2/11
*/
public class ServerBootstrap { public static void main(String[] args) { NettyServer.startServer("127.0.0.1", 7000);
}
}
package github.com.AllenDuke.rpc.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder; /**
* @description netty服务提供者
* @contact AllenDuke@163.com
* @since 2020/2/11
*/
public class NettyServer { public static void startServer(String hostName, int port) {
startServer0(hostName, port);
} private static void startServer0(String hostname, int port) { EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());//inbound编码器
pipeline.addLast(new StringEncoder());//outbound解码器
pipeline.addLast(new NettyServerHandler());//业务处理器
}
} ); ChannelFuture channelFuture = serverBootstrap.bind(hostname, port).sync();
System.out.println("服务提供方开始提供服务~~");
channelFuture.channel().closeFuture().sync();//同步方法,直到有结果才往下执行
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
} }
}
package github.com.AllenDuke.rpc.netty;

import com.alibaba.fastjson.JSON;
import github.com.AllenDuke.rpc.publicSource.Message;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map; /**
* @description netty服务提供者的业务处理器
* @contact AllenDuke@163.com
* @since 2020/2/11
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter { //实现类所在的包名,可把类都先加载到一个HashMap中
private static String packageName = "github.com.AllenDuke.rpc.serviceImpl."; //key为实现方法的全限定名
private static final Map<String, Method> serviceMap = new HashMap<>(); //key为实现类的全限定名
private static final Map<String, Class> classMap = new HashMap<>(); @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//获取客户端发送的消息,并调用服务
System.out.println("服务器收到信息:" + msg + " 准备解码,调用服务");
//将json文本转换为对象
Message message=JSON.parseObject((String) msg, Message.class); // //解析自定义消息协议
// StringBuilder message = new StringBuilder(msg.toString());
// //解析类名
// int classIndex = message.indexOf("#");
// String className = message.substring(0, classIndex) + "Impl";
// message.delete(0, classIndex + 1);
// //解析方法名
// int methodIndex = message.indexOf("#");
// String methodName = message.substring(0, methodIndex);
// message.delete(0, methodIndex + 1);
// //解析参数个数
// int argNumIndex = message.indexOf("#");
// int argNum=Integer.valueOf(message.substring(0,argNumIndex));
// message.delete(0,argNumIndex+1);
// Object[] args=new Object[argNum];
// //解析参数,类型转换?
// for (int i = 0; i < argNum; i++) {
// if(i==argNum-1) args[i]=message.toString();
// else{
// int argIndex=message.indexOf("#");
// args[i]=message.substring(0,argIndex);
// message.delete(0,argIndex+1);
// }
// } String className=message.getClassName();
String methodName=message.getMethodName();
Object[] args=message.getArgs();
Object result = null;//返回结果 Class serviceImpl = classMap.get(packageName + className);
//这里forName去寻找类,也可以一开始就把包下的类都加载进来
if(serviceImpl==null) serviceImpl= Class.forName(packageName + className);
//类中对应的方法
Method service = serviceMap.get(packageName + className + "." + methodName);
if (service == null)
for (Method method : serviceImpl.getMethods()) {
if (method.getName().equals(methodName)) {
service=method;
serviceMap.put(packageName + className + "." + methodName, method);//找到后加入hashMap
break;
} }
result = service.invoke(serviceImpl.newInstance(), args );//serviceImpl无参构造要public
ctx.writeAndFlush(JSON.toJSONString(result));//转换为json文本
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
package github.com.AllenDuke.rpc.publicSource;

import github.com.AllenDuke.rpc.netty.NettyClient;
import github.com.AllenDuke.rpc.netty.NettyClientHandler; import java.util.concurrent.Callable; /**
* @description 消息体
* @contact AllenDuke@163.com
* @since 2020/2/11
*/
public class Message implements Callable { private static NettyClientHandler handler= NettyClient.clientHandler; private String className;//要调用的类名
private String methodName;//要调用的方法名
private Object[] args;//方法的参数 public Message(String className,String methodName,Object[] args){
this.className=className;
this.methodName=methodName;
this.args=args;
} public String getClassName() {
return className;
} public void setClassName(String className) {
this.className = className;
} public String getMethodName() {
return methodName;
} public void setMethodName(String methodName) {
this.methodName = methodName;
} public Object[] getArgs() {
return args;
} public void setArgs(Object[] args) {
this.args = args;
} //封装成任务后,由线程池的工作线程调用
public Object call() throws Exception {
handler.sendMsg(this);
return handler.getResult();
} }
package github.com.AllenDuke.rpc.publicSource;

/**
* @description sayHello服务
* @contact AllenDuke@163.com
* @since 2020/2/11
*/
public interface HelloService { String hello(String name1,String name2,Integer num);
}
package github.com.AllenDuke.rpc.publicSource;

/**
* @description 计算器服务
* @contact AllenDuke@163.com
* @since 2020/2/11
*/
public interface Calculator { int add(int a,int b);
int multipy(int a,int b);
}
package github.com.AllenDuke.rpc.serviceImpl;

import github.com.AllenDuke.rpc.publicSource.Calculator;

/**
* @description 计算器服务实现
* @contact AllenDuke@163.com
* @since 2020/2/11
*/
public class CalculatorImpl implements Calculator { @Override
public int add(int a, int b) {
return a+b;
} @Override
public int multipy(int a, int b) {
return a*b;
}
}

线程池的代码在前两篇文章中。

总结:

此次实现总体上看难度不大,但有三个点可能不容易想到。

1.动态代理,这里要站在框架使用者的角度思考,应该做到用户无感知,像是Mybatis的getMapper,还要注意invoke方法的实现。

2.一条消息也是一个任务,工作线程既是执行任务也是发送消息。将消息封装成一个任务,要从接口方法的有返回值来思考。

3.使用fastjson,一开始是自定义消息协议,然后解析,参数还要类型转换,而fastjson刚好可以做到。

当然,实现的方法有千百种,但在可以实现了的条件下,应该考虑怎样的设计看起来更合理,更容易理解,本次模拟实现肯定有很多不妥的地方,还望朋友不吝赐教,共同进步。

用上自己的线程池,实现自己的RPC框架的更多相关文章

  1. 浅谈线程池(上):线程池的作用及CLR线程池

    原文地址:http://blog.zhaojie.me/2009/07/thread-pool-1-the-goal-and-the-clr-thread-pool.html 线程池是一个重要的概念. ...

  2. JUC源码学习笔记5——线程池,FutureTask,Executor框架源码解析

    JUC源码学习笔记5--线程池,FutureTask,Executor框架源码解析 源码基于JDK8 参考了美团技术博客 https://tech.meituan.com/2020/04/02/jav ...

  3. 硬核干货:4W字从源码上分析JUC线程池ThreadPoolExecutor的实现原理

    前提 很早之前就打算看一次JUC线程池ThreadPoolExecutor的源码实现,由于近段时间比较忙,一直没有时间整理出源码分析的文章.之前在分析扩展线程池实现可回调的Future时候曾经提到并发 ...

  4. 第9章 Java中的线程池 第10章 Exector框架

    与新建线程池相比线程池的优点 线程池的分类 ThreadPoolExector参数.执行过程.存储方式 阻塞队列 拒绝策略 10.1 Exector框架简介 10.1.1 Executor框架的两级调 ...

  5. Android下基于线程池的网络访问基础框架

    引言 现在的Android开发很多都使用Volley.OkHttp.Retrofit等框架,这些框架固然有优秀的地方(以后会写代码学习分享),但是我们今天介绍一种基于Java线程池的网络访问框架. 实 ...

  6. Java并发包线程池之ForkJoinPool即ForkJoin框架(二)

    前言 前面介绍了ForkJoinPool相关的两个类ForkJoinTask.ForkJoinWorkerThread,现在开始了解ForkJoinPool.ForkJoinPool也是实现了Exec ...

  7. Java并发包线程池之ForkJoinPool即ForkJoin框架(一)

    前言 这是Java并发包提供的最后一个线程池实现,也是最复杂的一个线程池.针对这一部分的代码太复杂,由于目前理解有限,只做简单介绍.通常大家说的Fork/Join框架其实就是指由ForkJoinPoo ...

  8. java多线程系类:JUC线程池:02之线程池原理(一)

    在上一章"Java多线程系列--"JUC线程池"01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我 ...

  9. Java多线程系列--“JUC线程池”02之 线程池原理(一)

    概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...

随机推荐

  1. 洛谷$P$3160 局部极小值 $[CQOI2012]$ 状压$dp$

    正解:状压$dp$ 解题报告: 传送门! 什么神仙题昂,,,反正我是没有想到$dp$的呢$kk$,,,还是太菜了$QAQ$ 首先看数据范围,一个4×7的方格,不难想到最多有8个局部极小值,过于显然懒得 ...

  2. linux tomcat安装

    一.下载tomcat包 下载tomcat包并上传至服务器中 解压文件: tar -zxvf apache-tomcat-8.5.47.tar.gz 为了后期程序的便于管理,我们还需要将Tomcat复制 ...

  3. 高阶函数HOF和高阶组件HOC(Higher Order Func/Comp)

    一.什么是高阶函数(组件),作用是什么? 子类使用父类的方法可以通过继承的方式实现,那无关联组件通信(redux).父类使用子类方法(反向继承)呢 为了解决类(函数)功能交叉/功能复用等问题,通过传入 ...

  4. 交换机广播风暴,STP生成树协议,端口聚合

    交换机(工作在数据链路层)具有学习功能:     一台刚重启的交换机上的mac地址表为空,根据数据包的来源,目的地来学习MAC地址与端口的映射关系映射关系,对于MAC地址表之中已有的就不管了,对未知端 ...

  5. 关于Itext 报错-java.lang.NoClassDefFoundError: org/bouncycastle/asn1/ASN1Encodable

    如果我们在用iText 做为java 为PDF 文档加水印的时候 报如下异常 java.lang.NoClassDefFoundError: org/bouncycastle/asn1/ASN1Enc ...

  6. docker发布.net core程序的坑

    docker发布遇到的两个问题 1:Could not resolve CoreCLR path. For more details, enable tracing by setting COREHO ...

  7. 超级火的java自学网站

    学靠的是毅力和自律,一定要坚持,否则就会前功尽弃,我自己也一直在边学边工作,当然自学要配合好的学习资料. 我是通过这个地方去学习的,它可以添加学习计划,从java基础到高级,从后台到前端,从细节到框架 ...

  8. pair 数组

    当有两个元素需要绑定在一起的时候可以用结构体 , 此时也可以用 pair 数组去替代结构体 . 定义 : pair<int, double> p1; //使用默认构造函数 pair< ...

  9. 《利用python进行数据分析》——Numpy基础

    一.创建数组 1.创建数组的函数 array:将输入数据(列表.元组.数组或其他序列类型)转换为ndarray,可用dtype指定数据类型. >>> import numpy as ...

  10. C# 实现验证码识别,使用AspriseOCR.dll

    验证码(Captcha)基于十道安全栅栏, 为网页.App.小程序开发者打造立体.全面的人机验证,最大程度地保护注册登录.活动秒杀.点赞发帖.数据保护等各大场景下的业务安全.要做自动化脚本程序,就要能 ...