Netty 笔记——手把手教你如何写一个 RPC 程序

2、Provider 服务端

==================================================================================

Provider 服务端主要由三个类构成

  • Provider: 服务端的入口类,启动 Netty 服务

  • ProviderHandler: 根据请求调用在 Provider 注册的具体服务,并将结果写入 ChannelHandlerContext 返回

  • ProviderRegister: 使用 ConcurrentHashMap 保存在 Provider 注册的服务和其对象实例

ProviderRegister,主要提供两个方法,addService 和 getService,其作用是在 Provider 注册一个服务和从 HashMap 获取某个服务。

public class ProviderRegister {

/**

  • 服务名称和其对象

*/

private static final Map<String, Object> SERVICE_MAP = new ConcurrentHashMap<>();

/**

  • 添加 RPC Provider 端的服务

*/

public void addService(T service, Class clazz) {

// getCanonicalName() 是获取所传类从java语言规范定义的格式输出

String serviceName = clazz.getCanonicalName();

log.info(“添加服务,名称是 {}”, serviceName);

if (!SERVICE_MAP.containsKey(serviceName)) {

// 将服务名和服务对应的对象添加到 SERVICE_MAP

SERVICE_MAP.put(serviceName, service);

}

}

/**

  • 获取 RPC Provider 端的服务

*/

public Object getService(String serviceName) {

Object service = SERVICE_MAP.get(serviceName);

if (service == null) {

log.debug(“没有找到该 PRC 服务”);

return null;

}

log.info(“找到服务 {}”, serviceName);

return service;

}

}

ProviderHandler,根据 RpcProtocol 请求数据中的接口名称,获取其对应的服务,并将结果写入返回。

@Slf4j

public class ProviderHandler extends ChannelInboundHandlerAdapter {

private final ProviderRegister register = new ProviderRegister();

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

Object result; RpcProtocol rpcProtocol = (RpcProtocol) msg; try {

// 从 provider 查找有没有这个服务

Object service = register.getService(rpcProtocol.getInterfaceName());

// 从 service 根据方法名称和传入参数类型获取具体的方法

Method method = service.getClass().getMethod(rpcProtocol.getMethodName(),

rpcProtocol.getParamTypes());

// 执行这个方法

result = method.invoke(service, rpcProtocol.getParamValues());

// 将结果返回

ctx.writeAndFlush(result);

log.info(“服务名称:{},调用的方法是 {}”, rpcProtocol.getInterfaceName(), rpcProtocol.getMethodName());

} catch (NoSuchMethodException | IllegalArgumentException |

InvocationTargetException | IllegalAccessException e) {

log.error(“服务未找到或者服务发生错误”);

} finally {

ctx.flush();

ctx.close();

}

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

cause.printStackTrace();

ctx.close();

}

}

Provider,服务端的入口类,启动 Netty 服务,其跟前面两个 demo 中几乎没有区别,主要是更换了编码和解码器,注册 ProviderHandler 处理具体的事件。

@Slf4j

public class Provider {

private final int port;

private final String host;

private final ProviderRegister register = new ProviderRegister();

public Provider(String host, int port) {

this.port = port;

this.host = host;

} /**

  • 启动 Netty 服务,跟前面的 demo 差不多,不同点在于编码器和解码器

*/

public void start() {

log.info(“开始启动 RPC 服务,地址是 {} 端口号是:{}”, host, port);

EventLoopGroup bossGroup = new NioEventLoopGroup();

EventLoopGroup workerGroup = new NioEventLoopGroup();

try {

ServerBootstrap bootstrap = new ServerBootstrap();

bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class)

// 连接的超时时间,超过这个时间还是建立不上的话则代表连接失败

.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)

// TCP默认开启了 Nagle 算法,该算法的作用是尽可能的发送大数据快,减少网络传输。

// TCP_NODELAY 参数的作用就是控制是否启用 Nagle 算法。

.childOption(ChannelOption.TCP_NODELAY, true)

// 是否开启 TCP 底层心跳机制

.childOption(ChannelOption.SO_KEEPALIVE, true)

//表示系统用于临时存放已完成三次握手的请求的队列的最大长度,如果连接建立频繁,服务器处理创建新连接较慢,

// 可以适当调大这个参数

.option(ChannelOption.SO_BACKLOG, 128)

// Channel 通道的绑定 ChannelPipeline

.childHandler(new ChannelInitializer() {

@Override

protected void initChannel(SocketChannel ch) throws Exception {

ChannelPipeline pipeline = ch.pipeline(); // 使用 JDK 自带的序列化机制 TODO 使用 protobuf 或者 kryo 进行序列化

// 对象的解码器

pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE,

ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())));

// 对象的编码器

pipeline.addLast(new ObjectEncoder());

pipeline.addLast(new ProviderHandler());

}

});

// 使用 bind 监听 host 和 port

ChannelFuture future = bootstrap.bind(host, port).sync();

future.channel().closeFuture().sync();

} catch (InterruptedException e) {

log.error(“开启服务错误:”, e);

} finally {

log.info(“关闭 bossGroup 和 workerGroup”);

bossGroup.shutdownGracefully();

workerGroup.shutdownGracefully();

}

}

/**

  • PRC Provider 服务端注册服务

  • @param service 服务

  • @param clazz 服务接口定义的类

  • @param 服务具体的实现类

*/

public void addService(T service, Class clazz) {

register.addService(service, clazz);

}

}

3、Consumer 客户端

==================================================================================

  • Consumer:连接 Provider 服务端,发送请求

  • ConsumerHandler :将服务端返回的数据进行处理

  • ConsumerProxy :使用 InvocationHandler 处理动态代理对象的方法调用

// 将返回的结果提取出来即可

public class ConsumerHandler extends ChannelInboundHandlerAdapter { private Object result; public Object getResult() { return result;

} @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { result = msg; }}

Consumer ,使用 Netty 和 Provider 端通信,基本使用方法和 Netty 客户端方法一样,

public class Consumer {

private final int port;

private final String host;

private final RpcProtocol protocol;

public Consumer(String host, int port, RpcProtocol protocol) {

this.port = port;

this.host = host;

this.protocol = protocol;

} public Object start() throws InterruptedException {

// TODO Netty 连接复用,将这些业务抽取出来

EventLoopGroup group = new NioEventLoopGroup();

ConsumerHandler consumerHandler = new ConsumerHandler();

try {

Bootstrap bootstrap = new Bootstrap();

bootstrap.group(group)

.channel(NioSocketChannel.class)

// 连接的超时时间,超过这个时间还是建立不上的话则代表连接失败

.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)

// 是否开启 TCP 底层心跳机制

.option(ChannelOption.SO_KEEPALIVE, true)

// TCP默认开启了 Nagle 算法,该算法的作用是尽可能的发送大数据快,减少网络传输。TCP_NODELAY 参数的作用就是控制是否启用 Nagle 算法。

.option(ChannelOption.TCP_NODELAY, true)

// Channel 通道的绑定 ChannelPipeline

.handler(new ChannelInitializer() {

@Override

protected void initChannel(SocketChannel ch) {

ChannelPipeline pipeline = ch.pipeline();

// 对象参数类型解码器

pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE,

ClassResolvers.cacheDisabled(this.getClass().getClassLoader())));

// 对象参数类型编码器

pipeline.addLast(new ObjectEncoder());

pipeline.addLast(consumerHandler);

}

});

// 链接到服务端和使用 ChannelFuture 接收返回的数据

ChannelFuture future = bootstrap.connect(host, port).sync();

// 发送请求

future.channel().writeAndFlush(protocol).sync();

future.channel().closeFuture().sync();

} finally {

group.shutdownGracefully().sync();

}

return consumerHandler.getResult();

}

}

ConsumerProxy ,实现 InvocationHandler 接口,在调用动态代理的方法时候,实际是调用其中的 invoke() 方法。

public class ConsumerProxy implements InvocationHandler {

private final String host;

private final int port;

public ConsumerProxy(String host, int port) {

this.host = host;

this.port = port;

} @SuppressWarnings(“unchecked”)

public T getProxy(Class clazz) {

// this 表示将 ConsumerProxy 的实例传入,调用动态代理对象的时候实际调用的是 ConsumerProxy.invoke() 的方法。

return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, this);

}

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 封装成 RPC 的请求

RpcProtocol protocol = new RpcProtocol();

// 获取方法对应的类名

protocol.setInterfaceName(method.getDeclaringClass().getName());

// 方法名

protocol.setMethodName(method.getName());

// 方法传入的参数类型

protocol.setParamTypes(method.getParameterTypes());

// 方法传入参数的实际值

protocol.setParamValues(args);

// 启动 PRC Consumer

Consumer consumer = new Consumer(host, port, protocol);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

谈到面试,其实说白了就是刷题刷题刷题,天天作死的刷。。。。。

为了准备这个“金三银四”的春招,狂刷一个月的题,狂补超多的漏洞知识,像这次美团面试问的算法、数据库、Redis、设计模式等这些题目都是我刷到过的

并且我也将自己刷的题全部整理成了PDF或者Word文档(含详细答案解析)

我的美团offer凉凉了?开发工程师(Java岗)三面结束等通知...

66个Java面试知识点

架构专题(MySQL,Java,Redis,线程,并发,设计模式,Nginx,Linux,框架,微服务等)+大厂面试题详解(百度,阿里,腾讯,华为,迅雷,网易,中兴,北京中软等)

我的美团offer凉凉了?开发工程师(Java岗)三面结束等通知...

算法刷题(PDF)

我的美团offer凉凉了?开发工程师(Java岗)三面结束等通知...

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
白了就是刷题刷题刷题,天天作死的刷。。。。。

为了准备这个“金三银四”的春招,狂刷一个月的题,狂补超多的漏洞知识,像这次美团面试问的算法、数据库、Redis、设计模式等这些题目都是我刷到过的

并且我也将自己刷的题全部整理成了PDF或者Word文档(含详细答案解析)

[外链图片转存中…(img-JSmoO5Cv-1713340849750)]

66个Java面试知识点

架构专题(MySQL,Java,Redis,线程,并发,设计模式,Nginx,Linux,框架,微服务等)+大厂面试题详解(百度,阿里,腾讯,华为,迅雷,网易,中兴,北京中软等)

[外链图片转存中…(img-P55CR0DS-1713340849751)]

算法刷题(PDF)

[外链图片转存中…(img-UieQAAFg-1713340849751)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值