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

a 夏天 提交于 2020-08-19 23:28:27

在上篇初步介绍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端实现类执行的结果。
需要的项目工程如下:
在这里插入图片描述





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();
    }
}

运行启动:
在这里插入图片描述
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);
    }
}

在这里插入图片描述
以上,就算是一个简单RPC框架了,实现了远程过程调用。欢迎一起来交流和搞事!

最后请欣赏表哥手绘山水搞事图-
在这里插入图片描述
大表哥的剑 2020.8.16

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!