netty学习之手写RPC框架

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框架服务端处理器,负责接收和处理客户端发来的调度 <br>
 * SimpleChannelInboundHandler<InvokeMessage> 泛型中定义的是服务端-客户端之间的消息通信类型
 * 
 * @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;

/**
 * 该类有以下两种功能 <br>
 * 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框架的核心<br>
 * 使用动态代理生成业务接口的实现类,远程调用服务端取得结果
 * 
 * @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方法,出现下面结果
在这里插入图片描述
就算是成功了。

总结

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

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

源码下载地址

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章