netty学习之手写RPC框架

文章目录

  • RPC简介
  • RPC框架需求分析
  • 实现步骤
    • 创建API工程
      • 创建工程并导入依赖
      • 定义业务接口
      • 定义常量类
    • 创建Server工程
      • 导入依赖
      • 实现业务接口
      • 定义服务端消息处理器
      • 定义服务器类
      • 定义启动类
    • 创建client工程
      • 客户端处理类
      • 客户端动态代理类
      • 定义消费者类
    • 执行测试
  • 总结
  • 源码下载地址

RPC简介

RPC,Remote Procedure Call,远程过程调用,是一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。在 OSI 网络通信模型中,RPC 跨越了传输层(第四层,传输协议 TCP/UDP,即通过 ip+port 进行通信)和应用层(第七层,传输协议有 HTTP、HTTPS、FTP 等)。RPC 使得开发分布式系统应用变得更加容易。
RPC 采用 C/S 模式。请求程序就是 Client,而服务提供程序就是 Server。首先,Client 发送一个带有请求参数的调用请求到 Server,然后等待响应。在 Server 端,进程一直处于睡眠状态直到接收到 Client 的调用请求。当一个调用请求到达,Server 会根据请求参数进行计算,并将计算结果发送给 Client,然后等待下一个调用请求。Client 接收到响应信息,即获取到调用结果,然后根据情况继续发出下一次调用。

RPC框架需求分析

我们这里要定义一个 RPC 框架,这个框架提供给用户后,用户只需要按照使用步骤就可以完成 RPC 远程调用。我们现在给出用户对于该 RPC 框架的使用步骤:

  • 用户需要将业务接口通知到 Server 与 Client,因为业务接口是服务名称。
  • 用户只需将业务接口的实现类写入到 Server 端的指定包下,那么这个包下的实现类就
    会被 Server 发布。
  • Client 端只需根据业务接口名就可获取到 Server 端发布的服务提供者,然后就可以调用
    到远程 Server 端的实现类方法的执行。

实现步骤

创建API工程

该 api 工程中用于存放业务接口、常量类、工具类等将来服务端与客户端均会使用到的一个接口与类。

创建工程并导入依赖

创建工程rpc-api,并导入

<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.lhl</groupId>
	<artifactId>rpc-api</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>rpc-api</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.6</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
</project>

定义业务接口

定义业务接口

package com.lhl.rpc_api.service;

import com.lhl.rpc_api.service.databean.User;

/**
 * 用户处理业务Service
 * 
 * @author LIUHL
 *
 */
public interface UserService {

	/**
	 * 新增用户接口
	 * 
	 * @param user
	 *            用户信息实体类
	 * @return 添加结果
	 */
	public String addUser(User user);

}

定义实体类信息

package com.lhl.rpc_api.service.databean;

import lombok.Data;

/**
 * 用户信息
 * 
 * @author LIUHL
 *
 */
@Data
public class User {

	// 注意一定要实现Serializable接口否则netty会报xception in thread "main" io.netty.handler.codec.EncoderException: java.io.NotSerializableException: com.lhl.rpc_api.service.databean.User
	/*
	 * 用户名称
	 */
	private String userName;

}

定义常量类

这是定义了客户端请求服务端的调用信息

package com.lhl.rpc_api.invoke;

import java.io.Serializable;

/**
 * 客户端调用信息
 * 
 * @author LIUHL
 *
 */
public class InvokeMessage implements Serializable {
	/**
	 * 接口名,即微服务名称
	 */
	private String className;
	/**
	 * 要远程调用的方法名
	 */
	private String methodName;
	/**
	 * 参数类型列表
	 */
	private Class<?>[] paramTypes;
	/**
	 * 参数值列表
	 */
	private Object[] paramValues;
}

创建Server工程

该工程实现了API中定义的业务接口,并且创建netty服务器。

导入依赖

<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.lhl</groupId>
	<artifactId>rpc-server</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>rpc-server</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>

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

实现业务接口

package com.lhl.rpc_server.provider;

import com.lhl.rpc_api.service.UserService;
import com.lhl.rpc_api.service.databean.User;

/**
 * 用户服务实现
 * 
 * @author LIUHL
 *
 */
public class UserServiceImpl implements UserService {

	/**
	 * 添加用户并返回结果
	 */
	public String addUser(User user) {
		return user.getUserName() + "注册成功";
	}

}

定义服务端消息处理器

package com.lhl.rpc_server.server;

import java.util.Map;

import com.lhl.rpc_api.invoke.InvokeMessage;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * RPC框架服务端处理器,负责接收和处理客户端发来的调度 
* SimpleChannelInboundHandler 泛型中定义的是服务端-客户端之间的消息通信类型 * * @author LIUHL * */
public class RpcServerHandler extends SimpleChannelInboundHandler<InvokeMessage> { // 扫描过后的的服务名与对应的处理类 private Map<String, Object> registerMap; public RpcServerHandler(Map<String, Object> registerMap) { this.registerMap = registerMap; } /** * 接受客户端发送过来的消息,并通过服务名称找到对应的实现类,通过反射机制执行对应业务,并将结果返回到客户端 */ @Override protected void channelRead0(ChannelHandlerContext ctx, InvokeMessage msg) throws Exception { Object result = "没有该提供者,或没有该方法"; if (registerMap.containsKey(msg.getClassName())) { // 从注册表中获取接口对应的实现类实例 Object invoker = registerMap.get(msg.getClassName()); result = invoker.getClass().getMethod(msg.getMethodName(), msg.getParamTypes()).invoke(invoker, msg.getParamValues()); } // 将运算结果返回给client ctx.writeAndFlush(result); ctx.close(); } }

定义服务器类

package com.lhl.rpc_server.server;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;

/**
 * 该类有以下两种功能 
* 1.业务实现类的解析器 2.定义netty的服务端信息并提供启动方法 * * @author LIUHL * */
public class RpcServer { // 注册表 private Map<String, Object> registerMap = new HashMap<String, Object>(); // 用于存放指定包中的业务接口的实现类名 private List<String> classCache = new ArrayList<String>(); // 发布服务:将指定包中的业务接口实现类实例写入到注册表 public void publish(String basePackage) throws Exception { // 将指定包中的业务接口实现类名写入到classCache中 cacheClassCache(basePackage); // 将指定包中的业务接口实现类实例写入到注册表 doRegister(); } // 将指定包中的业务接口实现类名写入到classCache中 private void cacheClassCache(String basePackage) { // 获取指定包目录中的资源 URL resource = this.getClass().getClassLoader() // com.lhl.rpc_server.provider => com/lhl/rpc_server/provider .getResource(basePackage.replaceAll("\\.", "/")); // 若指定的目录中没有资源,则直接返回 if (resource == null) { return; } File dir = new File(resource.getFile()); // 遍历指定目录中的所有文件 for (File file : dir.listFiles()) { if (file.isDirectory()) { // 若当前file为目录,则递归 cacheClassCache(basePackage + "." + file.getName()); } else if (file.getName().endsWith(".class")) { // 去掉文件名后的.class后辍 String fileName = file.getName().replace(".class", "").trim(); // 将类的全限定性类名写入到classCache classCache.add(basePackage + "." + fileName); } } // System.out.println(classCache); } // 将指定包中的业务接口实现类实例写入到注册表 // 注册表是一个map // key为业务接口名,即微服务名称 // value为该业务接口对应的实现类实例 private void doRegister() throws Exception { if (classCache.size() == 0) { return; } for (String className : classCache) { // 将当前遍历的类加载到内存 Class<?> clazz = Class.forName(className); registerMap.put(clazz.getInterfaces()[0].getName(), clazz.newInstance()); } } // 启动服务器 public void start() throws InterruptedException { EventLoopGroup parentGroup = new NioEventLoopGroup(); EventLoopGroup childGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(parentGroup, childGroup) // 用于指定当Server的连接请求处理线程全被占用时, // 临时存放已经完成了三次握手的请求的队列的长度。 // 默认是50 .option(ChannelOption.SO_BACKLOG, 1024) // 指定使用心跳机制来保证TCP长连接的存活性 .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(registerMap)); } }); // 不使用同步的话 这一点不会阻塞会执行下面的关闭方法 ChannelFuture future = bootstrap.bind(8888).sync(); System.out.println("服务端已启动,监听的端口为:8888"); // 不使用同步的话 这一点不会阻塞会执行下面的关闭方法 future.channel().closeFuture().sync(); } finally { parentGroup.shutdownGracefully(); childGroup.shutdownGracefully(); } } }

定义启动类

package com.lhl.rpc_server.server;

/**
 * 服务器启动类
 * 
 * @author LIUHL
 *
 */
public class RpcStarter {
	public static void main(String[] args) throws Exception {
		RpcServer server = new RpcServer();
		// 指定业务实现类包路径
		server.publish("com.lhl.rpc_server.provider");
		server.start();
	}
}

创建client工程

<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.lhl</groupId>
	<artifactId>rpc-client</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>rpc-client</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>

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

客户端处理类

package com.lhl.rpc_client.client;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * 客户端处理类
 * 
 * @author LIUHL
 *
 */
public class RpcClientHandler extends SimpleChannelInboundHandler<Object> {
	private Object result;

	public Object getResult() {
		return result;
	}

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
		this.result = msg;
	}

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

客户端动态代理类

package com.lhl.rpc_client.client;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.lhl.rpc_api.invoke.InvokeMessage;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;

/**
 * RPC框架的核心
* 使用动态代理生成业务接口的实现类,远程调用服务端取得结果 * * @author LIUHL * */
public class RpcProxy { @SuppressWarnings("unchecked") 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); } }); } /** * 服务端远程调用 * * @param clazz * @param method * @param args * @return * @throws InterruptedException */ private static Object rpcInvoke(Class<?> clazz, Method method, Object[] args) throws InterruptedException { final RpcClientHandler handler = new RpcClientHandler(); NioEventLoopGroup loopGroup = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(loopGroup).channel(NioSocketChannel.class) // Nagle算法开关 .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 invocation = new InvokeMessage(); invocation.setClassName(clazz.getName()); invocation.setMethodName(method.getName()); invocation.setParamTypes(method.getParameterTypes()); invocation.setParamValues(args); // 将参数实例发送给Server future.channel().writeAndFlush(invocation).sync(); future.channel().closeFuture().sync(); } finally { loopGroup.shutdownGracefully(); } return handler.getResult(); } }

定义消费者类

package com.lhl.rpc_client.consumer;

import com.lhl.rpc_api.service.UserService;
import com.lhl.rpc_api.service.databean.User;
import com.lhl.rpc_client.client.RpcProxy;

/**
 * 用户Service的消费者
 * @author LIUHL
 *
 */
public class UserConsumer {
    public static void main(String[] args) {
        UserService service = RpcProxy.create(UserService.class);
        User user = new User();
        user.setUserName("张三");
        System.out.println(service.addUser(user));
    }
}

执行测试

运行client工程的UserConsumer的main方法,出现下面结果
netty学习之手写RPC框架_第1张图片
就算是成功了。

总结

  1. api工程定义业务接口。
  2. server端实现业务接口,并提供netty服务器
  3. client端使用动态代理产生远程调用的实例,访问server端返回结果

通过这个RPC框架也为了后续学习Dubbo打下基础

源码下载地址

https://download.csdn.net/download/baidu_29609961/12323282

你可能感兴趣的:(netty)