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. js的class基础

    title: js的class基础 date: 2020-01-04 13:34:44 tags: --- 基本写法 let log = console.log; class people { con ...

  2. Go并发编程

    概述 简而言之,所谓并发编程是指在一台处理器上"同时"处理多个任务. 随着硬件的发展,并发程序变得越来越重要.Web服务器会一次处理成千上万的请求.平板电脑和手机app在渲染用户画 ...

  3. getopt命令

    最近学习了一下getopt(不是getopts)命令来处理执行shell脚本传入的参数,在此记录一下,包括长选项.短选项.以及选项的值出现的空格问题,最后写了个小的脚本来处理输入的参数 首先新建一个t ...

  4. docker操作

    Redis docker run -itd --name myredis -v /dockerdata/redis/config/redis.conf:/etc/redis/redis.conf  - ...

  5. 最新idea注册激活(永久使用,亲测可用)

    IDEA破解 一.2018版本 首先下载此破解jar包:破解jar包,将其放到合适的文件夹(首选IDEA的同级目录)进行管理: 进入IDEA的根目录,打开bin文件夹中的idea.exe.vmopti ...

  6. centos7 编译安装 php7.4

    1. 下载安装编译工具 yum groupinstall 'Development Tools' 2.安装依赖包 yum install libxml2 libxml2-devel openssl o ...

  7. POJ 3304 Segments(判断直线与线段是否相交)

    题目传送门:POJ 3304 Segments Description Given n segments in the two dimensional space, write a program, ...

  8. 低副瓣阵列天线综合1 matlab HFSS

    车载雷达天线多采用微带贴片天线,贴片振子的形状多种多样,较常用的是矩形: 组阵时多采用先串馈再把串馈好的行或列单元采取并馈的方式组阵,无论是串馈或并馈,想要获得较低的副瓣效果,都需要采取电流幅度加权的 ...

  9. es lucene搜索及聚合流程源码分析

    本文以TermQuery,GlobalOrdinalsStringTermsAggregator为例,通过代码,分析es,lucene搜索及聚合流程.1:协调节点收到请求后,将search任务发到相关 ...

  10. vue报错 [Intervention] Ignored attempt to cancel a touchmove event with cancelable

    在vue开发中使用vue-awesome-swiper制作轮播图,手动拖动时会报错,解决方案: 需要滑动的标签 { touch-action: none; } -------------------- ...