代码地址如下:
http://www.demodashi.com/demo/13448.html

可以给你提供思路

也可以让你学到Netty相关的知识

当然,这只是一种实现方式

需求

看下图,其实这个项目就是为了做这样一件事。

有一个公共服务ServerA,它提供了一个名为getUserName的服务。

现在有多个类似ServerB的Web应用服务器。

当客户想通过ServerB要请求getUserName服务时,由于ServerB服务中因为没有UserService的实现类,导致不能正常提供服务的问题。

预期结果

可以看到,在Client项目中,UserService没有实现类,但是返回了正常的结果。

项目结构

整个项目分为三个部分,Server端、Client端以及一个公共jar。

下图正是整个项目的目录结构图。

公共部分

公共部分的存在是因为我将服务器端和客户端写在了一个项目中,为了不让代码重复警告,所以提出来的一个公共模块。主要是几个实体类和一些序列化工具类。

  • MsgPackDecoder

    • 该类是一个MsgPack编码器,主要作用将Object对象序列化成byte数组。
  • MsgPackEncoder

    • 该类是一个MsgPack解码器,主要作用将byte数组反序列化成Object对象。
  • ObjectCodec

    • 该类是继承了MessageToMessageCodec<ByteBuf, Object>。是自定义序列化类主要有两个方法,encode和decode

      @Override
      protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) {
      // 调用工具类的序列化方法
      byte[] data = ObjectSerializerUtils.serilizer(msg);
      ByteBuf buf = Unpooled.buffer();
      buf.writeBytes(data);
      out.add(buf);
      } @Override
      protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) {
      // 调用工具类的反序列化方法
      byte[] bytes = new byte[msg.readableBytes()];
      msg.readBytes(bytes);
      Object deSerilizer = ObjectSerializerUtils.deSerilizer(bytes);
      out.add(deSerilizer);
      }
  • ObjectSerializerUtils

    • 序列化工具类。序列化方法可以自己实现
  • MethodInvokeMeta

    • 重点类。这个类是一个实体类。用于在网络中传输的类。主要有5个字段分别记录了一个接口的类对象,调用接口的方法名,方法的参数列表(包含参数类型,和参数列表),方法的返回值类型。
    • 在客户端中,这个类将调用方所要调用的方法封装。
    • 在服务端中,这个类主要用于服务器反射调用方法。
    • 当然,也可以用String来记录这些元信息。
  • NullWritable

    • 这个类主要用于在网络中传输null。当返回值为null时,服务端会返回NullWritable对象。客户端接收到NullWritable时进行null处理。
  • User

    • 实体对象。测试用例

客户端

上面的目录结构图也有提到,客户端中只有UserService接口,所以客户端中如果不做处理是不能正常运行的。

客户端中核心类有以下7个,其中与Netty相关的核心类与服务端一样有3个

  • ClientChannelHandlerAdapter
  • CustomChannelInitializerClient
  • NettyClient
  • RpcProxyFactoryBean
  • NettyBeanScanner
  • PackageClassUtils
  • WrapMethodUtils

NettyClient

这个类是Netty客户端的启动类,这个类中与Netty服务端进行通信

CustomChannelInitializerClient

这个类是用于初始化管道事件的类。主要添加了TCP粘包问题解决方案和自定义编解码器工具

ClientChannelHandlerAdapter

这个类是Netty客户端的处理类,主要通过这个类将调用信息写给Netty服务端。

NettyBeanScanner

这个类实现了BeanFactoryPostProcessor接口。BeanFactoryPostProcessor是Spring初始化Bean时对外暴露的扩展点。

Spring IoC容器允许BeanFactoryPostProcessor在容器实例化任何bean之前读取bean的定义(配置元数据),并可以修改它。同时可以定义多个BeanFactoryPostProcessor,通过设置'order'属性来确定各个BeanFactoryPostProcessor执行顺序。

在postProcessBeanFactory方法中,调用PackageClassUtils.resolver方法,将UserService.class注册到SpringBean工厂。

/**
* 注册Bean到Spring的bean工厂
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
this.beanFactory = (DefaultListableBeanFactory) beanFactory;
// 加载远程服务的接口
List<String> resolverClass = PackageClassUtils.resolver(basePackage);
for (String clazz : resolverClass) {
String simpleName;
if (clazz.lastIndexOf('.') != -1) {
simpleName = clazz.substring(clazz.lastIndexOf('.') + 1);
} else {
simpleName = clazz;
}
BeanDefinitionBuilder gd = BeanDefinitionBuilder.genericBeanDefinition(RpcProxyFactoryBean.class);
gd.addPropertyValue("interfaceClass", clazz);
gd.addPropertyReference("nettyClient", clientName);
this.beanFactory.registerBeanDefinition(simpleName, gd.getRawBeanDefinition());
}
}

RpcProxyFactoryBean

重点类,这个类继承了AbstractFactoryBean实现了InvocationHandler。是一个代理类。

使用jdk动态代理的方式创建代理对象。

Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, this);

在invoke方法中,

调用WrapMethodUtils工具类中的方法,将代理对象方法封装成MethodInvokeMeta对象。

然后通过NettyClient传输给NettyServer端,进行RPC调用,并将结果返回。

至此,客户端的核心类介绍完毕。

服务端

服务端主要的核心类有三个

  • ServerChannelHandlerAdapter
  • RequestDispatcher
  • NettyServer
NettyServer

这个类主要有两个方法,一个是启动Netty服务的方法,一个是关闭服务器的方法。核心代码如下:

	/**
* 启动netty服务的方法
*/
public void start() {
// 服务器监听端口号
int port = serverConfig.getPort();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
// BACKLOG用于构造服务端套接字ServerSocket对象,
// 标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。
// 如果未设置或所设置的值小于1,Java将使用默认值50
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO));
try {
// 设置事件处理
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
// 自定义长度解码器解决TCP黏包问题
// maxFrameLength 最大包字节大小,超出报异常
// lengthFieldOffset 长度字段的偏差
// lengthFieldLength 长度字段占的字节数
// lengthAdjustment 添加到长度字段的补偿值
// initialBytesToStrip 从解码帧中第一次去除的字节数
pipeline.addLast(new LengthFieldBasedFrameDecoder(65535
, 0, 2, 0, 2));
// LengthFieldPrepender编码器,它可以计算当前待发送消息的二进制字节长度,将该长度添加到ByteBuf的缓冲区头中
pipeline.addLast(new LengthFieldPrepender(2));
// 序列化工具
pipeline.addLast(new ObjectCodec());
pipeline.addLast(channelHandlerAdapter);
}
});
ChannelFuture f = serverBootstrap.bind(port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
ServerChannelHandlerAdapter

这个类主要重写了ChannelRead方法,在ChannelRead方法中调用了RequestDispatcher类中的dispatcher方法来处理消息。

RequestDispatcher

这个类的作用为,将ChannelRead方法中读到的数据(也可以说命令),通过反射来调用,执行对应方法,将执行后的结果写回通道,供客户端使用。

	/**
* 分发命令
*
* @param channelHandlerContext channelHandlerContext
* @param invokeMeta invokeMeta
*/
public void dispatcher(final ChannelHandlerContext channelHandlerContext, final MethodInvokeMeta invokeMeta) {
ChannelFuture f = null;
try {
// 获取客户端准备调用的接口类
Class<?> interfaceClass = invokeMeta.getInterfaceClass();
// 获取准备调用的方法名称
String name = invokeMeta.getMethodName();
// 获取方法对应的参数列表
Object[] args = invokeMeta.getArgs();
// 获取参数类型
Class<?>[] parameterTypes = invokeMeta.getParameterTypes();
// 通过Spring获取对象
Object targetObject = app.getBean(interfaceClass);
// 获取待调用方法
Method method = targetObject.getClass().getMethod(name, parameterTypes);
// 执行方法
Object obj = method.invoke(targetObject, args);
if (obj == null) {
// 如果方法结果为空,将NULL结果写给客户端
f = channelHandlerContext.writeAndFlush(NullWritable.nullWritable());
} else {
// 写给客户端结果
f = channelHandlerContext.writeAndFlush(obj);
}
// 释放通道,不是关闭连接
f.addListener(ChannelFutureListener.CLOSE);
} catch (Exception e) {
// 出现异常后的处理
f = channelHandlerContext.writeAndFlush(e.getMessage());
} finally {
if (f != null) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
}

使用方法

  1. 启动ServerApplication
  2. 启动ClientApplication
  3. 打开Chrome浏览器,输入:http://localhost:8080/client/get/name 或者 http://localhost:8080/client/get/info 即可看到结果。

如果想整合到现有项目中,请直接留言或者联系作者,此次并没有提供集成版本,但如果此篇文章已理解,那么自己可以手动的去集成到自己的项目中。基于Netty的RPC简易实现

代码地址如下:
http://www.demodashi.com/demo/13448.html

注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

基于Netty的RPC简易实现的更多相关文章

  1. 基于Netty打造RPC服务器设计经验谈

    自从在园子里,发表了两篇如何基于Netty构建RPC服务器的文章:谈谈如何使用Netty开发实现高性能的RPC服务器.Netty实现高性能RPC服务器优化篇之消息序列化 之后,收到了很多同行.园友们热 ...

  2. 这样基于Netty重构RPC框架你不可能知道

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365天原创计划”第5天. 今天呢!灯塔君跟大家讲: 基于Netty重构RPC框架 一.CyclicBarrier方法说明 1. ...

  3. 《Java 编写基于 Netty 的 RPC 框架》

    一 简单概念 RPC: ( Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO :当阻塞 ...

  4. java编写基于netty的RPC框架

    一 简单概念 RPC:(Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO:当阻塞I/O ...

  5. 基于netty实现rpc框架-spring boot服务端

    demo地址 https://gitee.com/syher/grave-netty RPC介绍 首先了解一下RPC:远程过程调用.简单点说就是本地应用可以调用远程服务器的接口.那么通过什么方式调用远 ...

  6. 基于Netty重构RPC框架

    下面的这张图,大概很多小伙伴都见到过,这是Dubbo 官网中的一张图描述了项目架构的演进过程.随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在 ...

  7. 基于netty实现rpc框架-spring boot客户端

    上篇讲了RPC服务端的实现.原理就是解析netty通道数据拿到类.方法及入参等信息,然后通过java反射机制调用本地接口返回结果.没有用到很复杂的技术. 这篇我们将客户端的实现.说白了客户端的任务很简 ...

  8. 基于Netty的RPC架构学习笔记(六):netty5案例学习

    文章目录 netty5服务端入门案例 netty5客户端入门案例 单客户端多连接程序 知识普及 线程池原理图 对象池原理图 对象组原理图 结论 理论结合实际 开干开干 总结 netty5服务端入门案例 ...

  9. 基于Netty的RPC架构学习笔记(五):netty线程模型源码分析(二)

    文章目录 小技巧(如何看开源框架的源码) 源码解析 阅读源码技巧 打印查看 通过打断点调试 查看调用栈 小技巧(如何看开源框架的源码) 一断点 二打印 三看调用栈 四搜索 源码解析 //设置nioso ...

随机推荐

  1. 关于使用jqmobi前端框架在phonegap平台上开发时的日期时间选择控件

    jqmobi(appframework)作为Intel的一款html5移动前端框架,以其自身轻量级和容易上手获得了很多移动HTML5开发者的喜爱,相对于jquerymobile,它可以说将jQuery ...

  2. Web移动应用调试工具——Weinre

          如今人们也越来越习惯在手机上浏览网页,而在手机上这些针对桌面浏览器设计的网页经常惨不忍睹.Web应用开发者需要针对手机进行界面的重新设计,但是手机上并没有称心如意的调试工具(如Firebu ...

  3. [CODECHEF]RIN

    题意:一个人要在$m$个学期上$n$节课,在第$j$学期上$i$课有$X_{i,j}$的收益,有些课$B_i$有前置课程$A_i$,问最大得分 这个题我都做不出来还去看题解...我退役吧== 考虑每种 ...

  4. 【最短路】【Heap-dijkstra】hihocoder 1587 ACM-ICPC国际大学生程序设计竞赛北京赛区(2017)网络赛 J. Typist's Problem

    题意:给你一个串,仅含有a~g,且每个字母只出现最多一次.和一个光标初始位置,以及一个目标串,问你最少要多少的代价变化成目标串. 有五种操作:在光标前添加一个未出现过的字母,代价1. 删除光标前或者光 ...

  5. Problem E: 零起点学算法34——3n+1问题

    #include<stdio.h> #include<math.h> int main() { int n; n<=pow(,); ; scanf("%d&qu ...

  6. [转][Navicat for MySQL系列]Navicat如何使用(二)

    上一篇经验已经建立了数据连接,这篇我介绍一下Navicat for MySQL的“增删改查”功能是如何操作的. 工具/原料 Navicat for MySQL 10.1.7 数据库操作(基本) 1 远 ...

  7. 8VC Venture Cup 2016 - Final Round C. Package Delivery 优先队列

    C. Package Delivery 题目连接: http://www.codeforces.com/contest/627/problem/C Description Johnny drives ...

  8. Fatal error: Call to undefined function mb_detect_encoding()

    1.找到php扩展目录(我的php扩展目录的路径是:C:\Program Files\AppServ\php\extensions)     在extensions目录下面找到php_mbstring ...

  9. all objects of the same class share the same set of class methods

    #include <iostream> #include "First.h" void Test(); int main() { std::cerr<<&q ...

  10. SQL Server 获取某时间点后修改的函数Function 并以文本格式显示

    修改查询分析器如下选项 右键=>查询选项 =>结果=>文本=> 取消 在结果集中包括列标题 的勾选 右键=>将结果保存到=> 选择 以文本格式显示结果 执行如下SQ ...