表哥侃侃Netty(2)——用Netty手写一个RPC框架

在上篇初步介绍Netty的一些概念和执行流程后,接下来废话不多说,直接撸代码。先用Netty写一个RPC框架,实现远程过程调用,来推开Netty实用场景的大门。后续还会接上用Netty来实现聊天室、心跳机制以及用Netty手写一个雏形的Dubbo框架。
**

手写RPC框架

**
RPC是远程过程调用的简写,是一个协议,处于网络通信协议的第五层:会话层,其下就是TCP/IP协议,建立在其基础上的通信会话协议。RPC定义了交互的模式,而应用程序使用这些模式,来访问其他服务器的方法,并不需要关系具体的网络上的细节。 RPC采用C/S模式。请求程序即客户机,而服务提供程序就是一个服务器。首先,客户端调用进程发送一个带有请求参数的调用信息到服务端,然后等待应答信息。在服务端,进程一直保持睡眠状态直到接收到调用信息为止。当一个调用信息到达,服务器根据请求参数进行计算,并将结果发送给发出请求的客户端,然后等待下一个调用信息。客户端调用进程接收到答复信息,获得调用结果,然后继续发出下一次调用。

废话不多说,就是干。
RPC框架说明:
I.用户将业务接口通知到Server与Client,这里要注意的是***业务接口***也就是***服务名称***。
II. 用户只需将业务接口的实现类写入到Server端的指定包下,并且在server端对该包下的所有实现类进行发布。
III. Client端只需根据服务名(业务接口名)就可获取到Server端发布的服务提供者(动态代理),然后进行远程调用,获得server端实现类执行的结果。
需要的项目工程如下:
表哥侃侃Netty(2)——用Netty手写一个RPC框架

1.创建API工程,定义服务接口
(1)创建一个普通的maven工程,导入依赖,这里仅需lombok,不用lombok也行,随性。


```xml
<dependencies>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>

</dependencies>

(2) 定义业务接口和常量类

```java
/**
*业务接口,很随性地搞点事情
*/
public interface SomeService {
String doSome(String some);
}
/**
*客户端发送给服务端的服务调用信息
*/
@Data
public class InvokeMessage implements Serializable {
private static final long serialVersionUID = 1L;
//服务名称(接口名称)
private String serviceName;
//方法名称
private String methodName;
//方法参数类型列表
private Class<?>[] paramTypes;
//方法参数值列表
private Object[] paramValues;
}

2.创建server端
(1)创建一个maven工程,依赖:

<dependencies>
<!-- netty-all依赖 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.36.Final</version>
</dependency>
<!--API工程依赖 -->
<dependency>
<groupId>com.netty</groupId>
<artifactId>rpc-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
</dependencies>

(2)定义接口实现类,提供业务产出

/**
* 服务提供者
*
*/
public class SomeServiceImpl implements SomeService {

public String doSome(String anything) {
return "欢迎来搞事,就是干~ "+anything;
}

}

(3)定义服务器类即server,主要实现服务的注册、发布

public class RpcServer {
// 服务注册表:Map(指定包下的实现类的实例存放到这个集合中)
private Map<String, Object> registryMap = new HashMap<>();
// 创建List(指定包中的class加载到这个集合中)
private List<String> classCache = new ArrayList<>();

private static int port=8888;

// 将指定包下的所有业务接口实现类进行发布
public void publish(String providerPackage) throws Exception {

// 扫描指定包下的所有实现类
getProviderClass(providerPackage);

// 提供者注册
doRegister();

NioEventLoopGroup parentGroup = new NioEventLoopGroup();
NioEventLoopGroup childGroup = new NioEventLoopGroup();

try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(parentGroup, childGroup)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ObjectEncoder());
pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE,
ClassResolvers.cacheDisabled(null)));
pipeline.addLast(new RpcServerHandler(registryMap));
}
});
ChannelFuture future = bootstrap.bind(port).sync();
System.out.println("微服务已注册成功,port:"+port);
future.channel().closeFuture().sync();
} finally {
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}

}

// 扫描指定包下的所有实现类:要将指定包下的class添加到一个集合
// providerPackage的形式如:com.abc.service
public void getProviderClass(String providerPackage) {
// 获取指定包的资源对象
URL resource = this.getClass().getClassLoader() // 获取到类加载器
// 将包中的点(.)替换为路径中的/
.getResource(providerPackage.replaceAll("\\.", "/"));

// 获取路径文件
File dir = new File(resource.getFile());
for(File file : dir.listFiles()) {
// 若当前遍历的是个目录,则递归调用
if(file.isDirectory()) {
getProviderClass(providerPackage + "." + file.getName());

// 若当前遍历的是一个.class文件,则将文件名中的.class扩展名去掉
} else if(file.getName().endsWith(".class")){
// 获取到文件名,即类的简单类名
String fileName = file.getName().replace(".class", "").trim();
// 将实现类的全限定性类名添加到classCache中
classCache.add(providerPackage + "." + fileName);
}
}
System.out.println("classCache:"+classCache);
}

// 提供者注册:将指定包中的实现类实例化后存放到一个map中
// key为实现类的接口名,value为实现类实例
private void doRegister() throws Exception {
// 若指定包中没有类,则无需注册
if(classCache.size() == 0) return;

// 遍历缓存集合中的所有类,创建相应的实例,存放到map中
for (String className : classCache) {
// 加载当前遍历的类
Class<?> clazz = Class.forName(className);
// 获取当前遍历类所实现的接口名称
String interfaceName = clazz.getInterfaces()[0].getName();
System.out.println("interfaceName:"+interfaceName);
// 将接口名作为key,当前遍历类的实例作为value,写入到map中
registryMap.put(interfaceName, clazz.newInstance());
}

}
}

(4)定义处理器handler

// 服务端处理器:
// 1 接收客户端提交的调用消息,并根据调用消息调用本地的服务
// 2 将本地服务运行结果返回给客户端
public class RpcServerHandler extends ChannelInboundHandlerAdapter {
private Map<String, Object> registryMap;
// 接收服务注册表
public RpcServerHandler(Map<String, Object> registryMap) {
this.registryMap = registryMap;
}

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

if(msg instanceof InvokeMessage) {
Object result = "没有指定的提供者";
InvokeMessage message = (InvokeMessage) msg;
System.out.println("收到执行请求信息:"+message);
// 判断注册表中是否存在客户端要调用的服务
if(registryMap.containsKey(message.getServiceName())) {
// 获取到指定名称的服务提供者实例
Object provider = registryMap.get(message.getServiceName());
result = provider.getClass()
.getMethod(message.getMethodName(), message.getParamTypes())
.invoke(provider, message.getParamValues());
System.out.println("执行结果:"+result);
}
ctx.writeAndFlush(result);
ctx.close();
}
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}

(5)服务启动类

public class RpCServerStarter {
public static void main(String[] args) throws Exception {
new RpcServer().publish("com.netty.rpc.service");
System.in.read();
}
}

运行启动:
表哥侃侃Netty(2)——用Netty手写一个RPC框架
3.创建客户端client
(1)创建maven工程rpc-client,并导入依赖:

 <dependencies>
<!-- netty-all依赖 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.36.Final</version>
</dependency>
<!--API工程依赖-->
<dependency>
<groupId>com.netty</groupId>
<artifactId>rpc-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
</dependencies>

(2)在客户端创建一个动态代理类,用于动态代理服务端的提供者对象,连接上server将调用信息发送给server端,进行远程调用:

public class RpcProxy {

// 泛型方法
public static <T> T create(final Class<?> clazz) {
return (T)Proxy.newProxyInstance(
clazz.getClassLoader(), // 类加载器
new Class[]{clazz}, // 代理的接口
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 若代理对象调用的是Object的方法,则直接进行本地调用
if(Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}

// 若代理对象调用的是业务接口中的方法,则进行远程调用
return rpcInvoke(clazz, method, args);
}
});
}

// 进行远程调用
private static Object rpcInvoke(Class<?> clazz, Method method, Object[] args) throws InterruptedException {
NioEventLoopGroup loopGroup = new NioEventLoopGroup();
final RpcClientHandler handler = new RpcClientHandler();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(loopGroup)
.channel(NioSocketChannel.class)
// TCP默认使用了一个称为Nagle的算法,该算法可以通过发送尽量大的数据块来充分利用网络带宽,
// 若该属性设置为true,则TCP不进行发送延迟,有数据马上就发送,不进行大数据块的积攒
// 若属性设置为false,则当要发送的数据积攒到一定大小后才会发送
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ObjectEncoder());
pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE,
ClassResolvers.cacheDisabled(null)));
pipeline.addLast(handler);
}
});
ChannelFuture future = bootstrap.connect("localhost", 8888).sync();

// 创建并初始化调用信息对象
InvokeMessage message = new InvokeMessage();
message.setServiceName(clazz.getName());
message.setMethodName(method.getName());
message.setParamTypes(method.getParameterTypes());
message.setParamValues(args);

// 一旦连接上Server,马上就将调用信息发送给Server端
future.channel().writeAndFlush(message);
future.channel().closeFuture().sync();
} finally {
loopGroup.shutdownGracefully();
}
Object reObj=handler.getResult();
System.out.println("reObj="+reObj);
return reObj;
}
}

(3)定义客户端处理器,接受调用server端后server端传过来的结果:

// 客户端处理器
// 接收Server传来的远程调用结果
public class RpcClientHandler extends SimpleChannelInboundHandler {
private Object result;
public Object getResult() {
return this.result;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
// msg即为服务端传递来的远程调用结果
this.result = msg;
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}

(4)编写客户端测试类,远程调用:

public class SomeConsumer {
public static void main(String[] args) {
SomeService service = RpcProxy.create(SomeService.class);
String result = service.doSome("拒绝PUA");
System.out.println(result);
}
}

表哥侃侃Netty(2)——用Netty手写一个RPC框架
以上,就算是一个简单RPC框架了,实现了远程过程调用。欢迎一起来交流和搞事!

最后请欣赏表哥手绘山水搞事图-
表哥侃侃Netty(2)——用Netty手写一个RPC框架
大表哥的剑 2020.8.16

原创:https://www.panoramacn.com
源码网提供WordPress源码,帝国CMS源码discuz源码,微信小程序,小说源码,杰奇源码,thinkphp源码,ecshop模板源码,微擎模板源码,dede源码,织梦源码等。

专业搭建小说网站,小说程序,杰奇系列,微信小说系列,app系列小说

表哥侃侃Netty(2)——用Netty手写一个RPC框架

免责声明,若由于商用引起版权纠纷,一切责任均由使用者承担。

您必须遵守我们的协议,如果您下载了该资源行为将被视为对《免责声明》全部内容的认可-> 联系客服 投诉资源
www.panoramacn.com资源全部来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。 敬请谅解! 侵权删帖/违法举报/投稿等事物联系邮箱:2640602276@qq.com
未经允许不得转载:书荒源码源码网每日更新网站源码模板! » 表哥侃侃Netty(2)——用Netty手写一个RPC框架
关注我们小说电影免费看
关注我们,获取更多的全网素材资源,有趣有料!
120000+人已关注
分享到:
赞(0) 打赏

评论抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

您的打赏就是我分享的动力!

支付宝扫一扫打赏

微信扫一扫打赏