前段时间手写了一个NIO的RPC 简易通信,今天用netty 重写了个RPC 通信,目前 已经可以工作,不过对于netty的有些参数还不是很清楚,文章中写了???的地方就是不清楚的,高手知道的可以在评论中说明 一下,谢谢。
为了方便,本文中的client和server在同一工程中,不过都是通过netty来访问的。
另外文章中的可用服务为了方便也是在本地的class path下搜索并保存的。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
现在我记录一下重写RPC的思路:
1.RPC 调用是在client只知道接口和方法名的情况要,通过发送给server 由server调用后返回结果的。所以我们要创建一个服务接口。
==本文中有两个接口:IRpcHelloService, 和IRpcService, 放在包api中。
2. 有了服务接口,那就必须要有实现类,这个在实际中一定是放到server上的。
==本文中实现类RpcHelloServiceImpl, 和RpcServiceImpl, 放在包provider中。
3. 服务器要提供服务,那就要把所有的可用服务显露出来。
==本文中由类RpcRegistry 类中的start() 通过Port 绑定在netty上来向网络发布服务。
==netty中必然要用到childHandler的配置,这里面就要用到我们自定义的handler (RegistryHandler类), 重写channelRead()方法
4.client端要请求服务,我们定义一个consumer类.在这个类中因为client只知道 接口名,我们需要为client端创建动态代理对象,通过代理对象来执行对应的方法.代理对象中invoke方法通过netty 向server端发送自定义的协议. 里边也会有自己定义的handler, 里面也要重写channelRead() 来处理收到的报文.
5. 在上一步骤中会有到自定义的协议,所以我们也要定义一个InvokerProtocol, 里面肯定会接口名,方法名,方法参数.
服务器上会根据收到的接口名,找到可用的服务实现类的实例.
----------------------------------------------------------以下是代码结构------------------------------------------------------------------------------------------
pom.xml和所有的代码就在这里,要练习的同学可以按以下结构构成工程,直接运行即可.
-------------
----------------------------------------------------------以下是pom.xml-------------------------------------------------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.netty.rpc</groupId>
<artifactId>RPC-NETTY</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
</dependencies>
</project>
接口1:
package com.yubo.study.netty.rpc.api;
/**
* Created by Administrator on 2019/7/15 0015.
*/
public interface IRpcHelloService {
public String hello(String name);
}
接口2:
package com.yubo.study.netty.rpc.api;
/**
* Created by Administrator on 2019/7/15 0015.
*/
public interface IRpcService {
public int add(int a, int b);
public int sub(int a, int b);
}
接口实现:
package com.yubo.study.netty.rpc.provider;
import com.yubo.study.netty.rpc.api.IRpcHelloService;
/**
* Created by Administrator on 2019/7/15 0015.
*/
public class RpcHelloServiceImpl implements IRpcHelloService {
public String hello(String name) {
return "hello: " + name;
}
}
接口实现:
package com.yubo.study.netty.rpc.provider;
import com.yubo.study.netty.rpc.api.IRpcService;
/**
* Created by Administrator on 2019/7/15 0015.
*/
public class RpcServiceImpl implements IRpcService {
public int add(int a, int b) {
return a + b;
}
public int sub(int a, int b) {
return a -b;
}
}
RPC 自定义协议:
package com.yubo.study.netty.rpc.protocol;
import lombok.Data;
import java.io.Serializable;
import java.util.Arrays;
/**
* Created by Administrator on 2019/7/15 0015.
*/
@Data
public class InvokerProtocol implements Serializable {
private String className;
private String methodName;
private Class<?> [] paraTypes;
private Object [] values;
@Override
public String toString() {
return "InvokerProtocol{" +
"className='" + className + '\'' +
", methodName='" + methodName + '\'' +
", paraTypes=" + Arrays.toString(paraTypes) +
", values=" + Arrays.toString(values) +
'}';
}
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 Class<?>[] getParaTypes() {
return paraTypes;
}
public void setParaTypes(Class<?>[] paraTypes) {
this.paraTypes = paraTypes;
}
public Object[] getValues() {
return values;
}
public void setValues(Object[] values) {
this.values = values;
}
}
服务器端发面程序:
package com.yubo.study.netty.rpc.registry;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
/**
* Created by Administrator on 2019/7/15 0015.
*/
public class RpcRegistry {
private int port;
public RpcRegistry(int port) {
this.port = port;
}
public void start()
{
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
ChannelFuture future = null;
try{
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {// 注意这是childHandler, client用的是handler, 都要重写initChannel方法
@Override
protected void initChannel(SocketChannel sc) throws Exception { // 这里是写了一个ChannelInitializer 类的匿名子类,实现了initChannel 方法,当有连接过来时,才会调用这个函数
System.out.println("sc:" + sc);
ChannelPipeline cp = sc.pipeline();
System.out.println("111111:");
cp.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0, 4, 0, 4));// ???
System.out.println("222:");
cp.addLast(new LengthFieldPrepender(4)); // ???
System.out.println("333");
cp.addLast("encoder", new ObjectEncoder()); // ???
System.out.println("44444:");
cp.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));// ???
System.out.println("555555");
// 这个才是最重要的, 我们处理从channel 中读取出来的数据, 有连接过来时,就会new RegistryHandler,
// 这个对象中会从本地搜索所有可用的服务并存到map中。k: interface , v : 实现类的实例
// 当server中的netty 的channelRead()函数中收到自定义协议对象后,就会用msg中的interface Name找到 服务器上对应的实现类的实例,然后用这个实例通过反射调用对应的方法,并把结果写到client的ctx中。
cp.addLast(new RegistryHandler());
System.out.println("6666666");
}
})
.option(ChannelOption.SO_BACKLOG, 128) // 来不及处理的连接放在缓存中的数目最大数
.childOption(ChannelOption.SO_KEEPALIVE, true); // 保持连接
future = b.bind(port).sync();
System.out.println("GP registry start listen on port:" + port);
future.channel().closeFuture().sync();// ???
}catch (Exception ex){ // 异常了就关闭所有的工作组
ex.printStackTrace();
if(null != future){
try {
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
new RpcRegistry(8083).start(); // 发布注册
}
}
服务器端用的handler (netty中pipline中加入的)
package com.yubo.study.netty.rpc.registry;
import com.yubo.study.netty.rpc.protocol.InvokerProtocol;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by Administrator on 2019/7/15 0015.
*/
public class RegistryHandler extends ChannelInboundHandlerAdapter { // handler 都是继承这个类ChannelInboundHandlerAdapter, 并重写channelRead 和exceptionCaught()两个方法
public static ConcurrentHashMap<String, Object> registryMap = new ConcurrentHashMap<String, Object>();
private List<String> classNames = new ArrayList<String>();
public RegistryHandler() { // RegistryHandler是用来处理client发来的请求的,所以他必须要知道哪些服务可用,所在在构造函数里,这把所有要用的服务初始化好。
doScan("com.yubo.study.netty.rpc.provider"); // 这里为了方便,是直接从本地的class 路径下搜索可用的服务,实际使用中可能是从另外的地方获取
doRegistry();
}
public void doScan(String packagePath) {
URL url = this.getClass().getClassLoader().getResource(packagePath.replaceAll("\\.", "/")); // 通过把 xx.xx.xx 替换成/xx/xx/xx/xx
System.out.println("url:" + url);
File dir = new File(url.getFile());// /xx/xx/xx 变成 e:\xx\xx\xx
System.out.println("dir:" + dir);
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
doScan(packagePath + "." + f.getName()); // 如果是目录 ,就递归调用
} else {
classNames.add(packagePath + "." + f.getName().replace(".class", "").trim()); // 如果是.class文件,就把class的全名放到list中
}
}
}
public void doRegistry() {
if (classNames.size() == 0)
return;
try {
for (String className : classNames) { // 把上边可用的所有服务(服务的实现类),全部映射成 k: 未实现的接口,v: 实现类的实例 的map 存起来
System.out.println("className:" + className);
Class<?> clazz = Class.forName(className);
Class<?> i = clazz.getInterfaces()[0];
registryMap.put(i.getName(), clazz.newInstance());
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
@Override
public void channelRead (ChannelHandlerContext ctx, Object msg)throws Exception { // channel.pipeline.addLast(handler中), 加了这个handler, 那就会自动调用 channelRead()方法,这里就是最核心的调用地方
//super.channelRead(ctx, msg);
Object result = new Object();
InvokerProtocol request = (InvokerProtocol) msg;
System.out.println("request:" + request);
if(registryMap.containsKey(request.getClassName())) // 从收到的msg中提取出接口名,方法名,方法形参,方法实参,然后找到对应的实现类的实例,用反射调用方法
{
Object clazzInstance = registryMap.get(request.getClassName());
Method method = clazzInstance.getClass().getMethod(request.getMethodName(), request.getParaTypes());
result = method.invoke(clazzInstance, request.getValues());
System.out.println("result:" + result);
ctx.writeAndFlush(result); // 将反射执行后的结果写回给client
ctx.flush();
ctx.close();
}
}
@Override
public void exceptionCaught (ChannelHandlerContext ctx, Throwable cause)throws Exception {
// super.exceptionCaught(ctx, cause);
ctx.close();
}
}
client端的调用 程序
package com.yubo.study.netty.rpc.consumer;
import com.yubo.study.netty.rpc.api.IRpcHelloService;
import com.yubo.study.netty.rpc.api.IRpcService;
import com.yubo.study.netty.rpc.consumer.proxy.RpcProxy;
/**
* Created by Administrator on 2019/7/16 0016.
*/
public class RpcConsumer {
public static void main(String[] args) {
// client 要调用远程的服务,他本身只知道接口名,所以他本身如何让server知道 自己的需要呢
// 就要把接口名和方法名传给server。
// 但是在本地呢,他还是调用原来的方法名。
// 还有就是在本地只有接口,不能实例化,那我们就只能给他创建一个代理对象
// 根据动态代理的原理,我们先要创建一个代理类,实现InvocationHandler, 并重写invoke方法
// 在invoke方法里,把我们自定义的协议对象发给server, 然后再读取server的返回,然后再把这个返回值返回给本地调用的方法
IRpcHelloService rpcHello = RpcProxy.createProxy(IRpcHelloService.class);
System.out.println("out:" + rpcHello.hello("yubo"));
IRpcService service = RpcProxy.createProxy(IRpcService.class);
int r = service.add(1,1);
System.out.println("result 1 + 1 =" + r);
}
}
----------------------------------------------------------以下是本在代理类的代码-------------------------------------------------------------------------------------------------------
package com.yubo.study.netty.rpc.consumer.proxy;
import com.yubo.study.netty.rpc.protocol.InvokerProtocol;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Created by Administrator on 2019/7/15 0015.
*/
public class RpcProxy {
public static <T> T createProxy(Class<T> clazz){
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new MethodProxy(clazz));
}
private static class MethodProxy implements InvocationHandler{
private Class<?> clazz;
public MethodProxy(Class<?> clazz) {
this.clazz = clazz;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(Object.class.equals(method.getDeclaringClass())){
return method.invoke(this, args);
}
else{
// invoke rpc server to execute method
return rpcInvoke(proxy, method, args);
}
}
public Object rpcInvoke(Object proxy, Method method, Object[] args){ // 远程调用 部分用netty来实现
// Package the msg and send to server
InvokerProtocol myMsg = new InvokerProtocol(); // 构造自定义协议报文
myMsg.setClassName(this.clazz.getName());
myMsg.setMethodName(method.getName());
myMsg.setParaTypes(method.getParameterTypes());
myMsg.setValues(args);
final RpcProxyHandler consumerHandler = new RpcProxyHandler();// netty 中不可少的handler, 继承自ChannelInboundHandlerAdaptor, 重写channelRead() exceptionCaught()
EventLoopGroup workGroup = new NioEventLoopGroup(); // 创建工作组
try{
Bootstrap b = new Bootstrap(); // bootStrap 不可少,server端用的是ServerBootStrap
b.group(workGroup)
.channel(NioSocketChannel.class) // server用的是NioServerSocketChannel
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() { // handler 设置不可少, pipeline 加handler. server用的是childHandler()
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0,4, 0,4));
pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
pipeline.addLast("encoder", new ObjectEncoder());
pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
pipeline.addLast("handler", consumerHandler); // 最后添加我们自己的handler
}
});
try {
ChannelFuture future = b.connect("localhost", 8083).sync(); //连接
System.out.println("write:" + myMsg);
future.channel().writeAndFlush(myMsg);// 写报文
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
catch (Exception ex){
ex.printStackTrace();
}finally {
workGroup.shutdownGracefully();
}
return consumerHandler.getResponse(); // 把server 返回的结果,返回给client调用的地方
}
}
}
client中netty 用的handler
package com.yubo.study.netty.rpc.consumer.proxy;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* Created by Administrator on 2019/7/16 0016.
*/
public class RpcProxyHandler extends ChannelInboundHandlerAdapter {
private Object response;
public Object getResponse() {
return response;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
this.response = msg;// 服务器写回来的东西,直接保存起来
// super.channelRead(ctx, msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
----------------------------------先执行server 端, 结果如下--------------------------------------------------
---------------------------------------再执行client 结果如下------------------------------------