文章目錄
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方法,出現下面結果
就算是成功了。
總結
- api工程定義業務接口。
- server端實現業務接口,並提供netty服務器
- client端使用動態代理產生遠程調用的實例,訪問server端返回結果
通過這個RPC框架也爲了後續學習Dubbo打下基礎