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. SpringBoot-2.1.1系列一:使用https

    1.什么是https? HTTPS中文名称:超文本传输安全协议,是以安全为目标的HTTP通道,简单讲是HTTP的安全版.即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要 ...

  2. maven中scope标签各个值的意义

    在使用maven配置时,有时候会见到scope这个标签,但是总是记不住他们所对应的含义,现在整理一下,以后忘记了再来查看. 版权声明:本文为CSDN博主「MrZhangBaby」的原创文章,遵循 CC ...

  3. AcWing 240. 食物链 | 并查集

    传送门 题目描述 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形. A吃B, B吃C,C吃A. 现有N个动物,以1-N编号. 每个动物都是A,B,C中的一种,但是我们并不知道它到底 ...

  4. Linux Cgroup浅析

    cgroup从2.6.4引入linux内核主线,目前默认已启用该特性.在cgroup出现之前,只能对一个进程做资源限制,比如通过sched_setaffinity设置进程cpu亲和性,使用ulimit ...

  5. QGIS WGS84转其它坐标系并计算坐标

    需求: 将带有经度.纬度(WGS84坐标系)坐标的文本(*.txt)转换成指定投影坐标系的shp文件并计算x,y坐标. 环境和工具: WIN10.QGIS2.16.带有经纬度坐标的文本.格式如下图: ...

  6. 20.java-JDBC连接mysql数据库详解

    1.JDBC介绍 jdbc(java database connectivity)为java开发者使用数据库提供了统一的编程接口,它由一组java类和接口组成. JDBC需要用到的类和接口有: Dri ...

  7. Scala实践3

    一.函数式对象 1.1  rational类的规格和创建 Rational类来源于有理数(rational number),来表示n(分子)/d(分母)的数字,同时对有理数的运算(加减乘除)建模,还具 ...

  8. 趣谈编程史第2期-这个世界缺少对C语言的敬畏,你不了解的C语言科普

    这是我制作的编程语言科普系列视频的第二期,博客根据视频文案整理而成,提供给有需要的朋友阅读或使用. 视频地址:https://www.bilibili.com/video/av83627932/    ...

  9. .NET为什么要使用异步(async)编程?⭐⭐⭐⭐⭐

    .NET为什么要使用异步(async)编程? 有几点坐下笔记 ⭐⭐⭐⭐: 1. 同步方法 static void Main(string[] args) { Console.WriteLine($&q ...

  10. PowerDesigner配置Oracle数据库反向工程

    PowerDesigner配置Oracle数据库反向工程 作者:Jesai 贴吧:软件频道吧 1. 前言: PowerDesigner是Sybase的企业建模和设计解决方案,采用模型驱动方法,将业务与 ...