
本專案所有代碼可見:https://github.com/weiyu-zeng/SimpleRPC
前言
本次改進我們將引入zookeeper作為RPC框架的注冊中心,服務端在zookeeper上注冊自己的服務,而客戶端呼叫服務,回去zookeeper上根據服務名尋找呼叫的服務器地址,使得我們RPC支持集群調度通信的能力,
實作
zookeeper安裝與使用
zookeeper安裝請見:
【zookeeper】windows版zookeeper安裝與啟動 可能遇到的各種問題
安裝好之后,我們打開zookeeper的server:

server啟動如下:

開啟zookeeper的client:

如下,說明成功啟動了

按回車:

輸入ls /我們查看目錄:

到此為止,先放在這不要關,我們寫代碼去,
專案創建
創建一個名為simpleRPC-06的module:

創建com.rpc的package:

依賴配置
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">
<parent>
<artifactId>SimpleRPC</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>simpleRPC-06</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.51.Final</version>
</dependency>
<!-- 阿里的fastjson序列化框架 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.67</version>
</dependency>
<!--這個jar包應該依賴log4j,不引入log4j會有控制臺會有warn,但不影響正常使用-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.30</version>
</dependency>
</dependencies>
</project>
請注意一下,curator必須要和zookeeper版本適配,如果curator版本太高,專案將無法運行,
我們在resources目錄下配置一下 log4j的配置,檔案名為 log4j.properties:

log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
register
我們創建一個名為register的package:

創建注冊中心的注冊服務介面ServiceRegister.java:
package com.rpc.register;
import java.net.InetSocketAddress;
/**
* @author weiyu_zeng
*
* 服務注冊介面,兩大基本功能,注冊:保存服務與地址, 查詢:根據服務名查找地址
*/
public interface ServiceRegister {
void register(String serviceName, InetSocketAddress serverAddress);
InetSocketAddress serviceDiscovery(String serviceName);
}
然后創建服務注冊實作類 ZkServiceRegister.java:
package com.rpc.register;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import java.net.InetSocketAddress;
import java.util.List;
/**
* @author weiyu_zeng
*
* Curator:是Zookeeper開源的客戶端框架,封裝了很多API,使用起來非常的方便
* CuratorFramework:連接zookeeper服務的框架,客戶端創建使用靜態工廠方式CuratorFrameworkFactory進行創建
* tickTime:zk的心跳間隔(heartbeat interval),也是session timeout基本單位.單位為毫秒.
* minSessionTimeout:最小超時時間,zk設定的默認值為2*tickTime.
* maxSessionTimeout:最大超時時間,zk設定的默認值為20*tickTime.
* retryPolicy()重連策略:
* Curator 四種重連策略:
* 1.RetryUntilElapsed(int maxElapsedTimeMs, int sleepMsBetweenRetries)
* 以sleepMsBetweenRetries的間隔重連,直到超過maxElapsedTimeMs的時間設定
*
* 2.RetryNTimes(int n, int sleepMsBetweenRetries)
* 指定重連次數
*
* 3.RetryOneTime(int sleepMsBetweenRetry)
* 重連一次,簡單粗暴
*
* 4.ExponentialBackoffRetry
* ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries)
* ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs)
* 時間間隔 = baseSleepTimeMs * Math.max(1, random.nextInt(1 << (retryCount + 1)))
*
* namespace(): 為了避免各個應用的zk patch沖突, Curator Framework內部會給每一個Curator Framework實體分配一個namespace(可選).
* 這樣你在create ZNode的時候都會自動加上這個namespace作為這個node path的root.
* CuratorFramework.create():開始創建操作,可以呼叫額外的方法(比如方式mode 或者后臺執行background) 并在最后呼叫forPath()
* 指定要操作的ZNode
* CuratorFramework.checkExists(): 開始檢查ZNode是否存在的操作. 可以呼叫額外的方法(監控或者后臺處理)并在最后呼叫forPath()
* 指定要操作的ZNode
* CuratorFramework.start() / close():啟動和關閉客戶端
* CuratorFramework(client).create().withMode(CreateMode.EPHEMERAL):這將使用給定的資料創建臨時結點 EPHEMERAL ZNode
* CuratorFramework.getChildren():開始獲得ZNode的子節點串列, 以呼叫額外的方法(監控、后臺處理或者獲取狀態watch,
* background or get stat)并在最后呼叫forPath()指定要操作的ZNode
*
* InetSocketAddress:該類實作了可序列化介面,直接繼承自java.net.SocketAddress類,實作 IP 套接字地址(IP 地址 + 埠號),
* 它還可以是一個對(主機名 + 埠號),在此情況下,將嘗試決議主機名,如果決議失敗,則該地址將被視為未決議
* 地址,但是其在某些情形下仍然可以使用,比如通過代理連接,
* 構造方法:InetSocketAddress(InetAddress addr, int port) 根據 IP 地址和埠號創建套接字地址,
* InetSocketAddress(String hostname, int port) 根據主機名(IP地址指代)和埠號創建套接字地址,
* InetSocketAddress.getHostName():獲取 hostname,即地址的主機名部分,
* InetSocketAddress.getPort() 獲取埠號,
*/
public class ZkServiceRegister implements ServiceRegister {
// curator 提供的zookeeper客戶端
private CuratorFramework client;
// zookeeper根路徑結點
private static final String ROOT_PATH = "MyRPC";
// 構造方法
// 這里負責zookeeper客戶端的初始化,并與zookeeper服務端建立連接,
// 初始化包括指定重連策略,指定連接zookeeper的埠,指定超時時間,指定命名空間
// 初始化完成之后start()開啟zookeeper客戶端,
public ZkServiceRegister() {
// 重連策略:指數時間重試
RetryPolicy policy = new ExponentialBackoffRetry(1000, 3);
// zookeeper的地址固定,不管是服務提供者還是消費者,都要與之建立連接
// sessionTimeoutMs 與 zoo.cfg中的tickTime 有關系,
// zk還會根據minSessionTimeout與maxSessionTimeout兩個引數重新調整最后的超時值,默認分別為tickTime 的2倍和20倍
// 使用心跳監聽狀態
this.client = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181")
.sessionTimeoutMs(40000)
.retryPolicy(policy)
.namespace(ROOT_PATH)
.build();
this.client.start();
System.out.println("zookeeper 連接成功");
}
// 注冊:傳入服務方法名(String),傳入主機名和埠號的套接字地址(InetSocketAddress)
@Override
public void register(String serviceName, InetSocketAddress serverAddress) {
try {
// serviceName創建成永久節點,服務提供者下線時,不刪服務名,只刪地址
Stat stat = client.checkExists().forPath("/" + serviceName);
if (stat == null) {
client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT)
.forPath("/" + serviceName);
}
// 路徑地址,一個/代表一個節點
String path = "/" + serviceName + "/" + getServiceAddress(serverAddress);
// 臨時節點,服務器下線就洗掉節點
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path);
} catch (Exception e) {
System.out.println("此服務已存在");
}
}
// 根據服務名回傳地址
@Override
public InetSocketAddress serviceDiscovery(String serviceName) {
try {
List<String> strings = client.getChildren().forPath("/" + serviceName);
// 這里默認用的第一個,后面加負載均衡
String string = strings.get(0);
return parseAddress(string);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 地址 -> XXX.XXX.XXX.XXX:port 字串
private String getServiceAddress(InetSocketAddress serverAddress) {
return serverAddress.getHostName() + ":" + serverAddress.getPort();
}
// 字串決議為地址:按照":"切分開,前半是host(String),后半是port(int)
private InetSocketAddress parseAddress(String address) {
String[] result = address.split(":");
return new InetSocketAddress(result[0], Integer.parseInt(result[1]));
}
}
接下來可以對service,client和server進行修改,
client
NettyRPCClient.java 做一點修改:
package com.rpc.client;
import com.rpc.common.RPCRequest;
import com.rpc.common.RPCResponse;
import com.rpc.register.ServiceRegister;
import com.rpc.register.ZkServiceRegister;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;
import java.net.InetSocketAddress;
/**
* @author zwy
*
* 實作RPCClient介面
*/
public class NettyRPCClient implements RPCClient {
private static final Bootstrap bootstrap;
private static final EventLoopGroup eventLoopGroup;
private String host;
private int port;
private ServiceRegister serviceRegister; // ServiceRegister介面類class
// 建構式:初始化zookeeper
public NettyRPCClient() {
this.serviceRegister = new ZkServiceRegister();
}
// netty客戶端初始化,重復使用
static {
eventLoopGroup = new NioEventLoopGroup();
bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new NettyClientInitializer());
}
/**
* 這里需要操作一下,因為netty的傳輸都是異步的,你發送request,會立刻回傳一個值, 而不是想要的相應的response
*/
@Override
public RPCResponse sendRequest(RPCRequest request) {
InetSocketAddress address = serviceRegister.serviceDiscovery(request.getInterfaceName());
host = address.getHostName();
port = address.getPort();
try {
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
Channel channel = channelFuture.channel();
// 發送資料
channel.writeAndFlush(request);
channel.closeFuture().sync();
// 阻塞的獲得結果,通過給channel設計別名,獲取特定名字下的channel中的內容(這個在hanlder中設定)
// AttributeKey是,執行緒隔離的,不會由執行緒安全問題,
// 實際上不應通過阻塞,可通過回呼函式,后面可以再進行優化
AttributeKey<RPCResponse> key = AttributeKey.valueOf("RPCResponse");
RPCResponse response = channel.attr(key).get();
System.out.println(response);
return response;
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
TestClient.java 也做相應的修改:
package com.rpc.client;
import com.rpc.common.Blog;
import com.rpc.common.User;
import com.rpc.service.BlogService;
import com.rpc.service.UserService;
/**
* @author zwy
*/
public class TestClient {
public static void main(String[] args) {
// 不需傳host,port
RPCClient rpcClient = new NettyRPCClient();
// 把這個客戶端傳入代理客戶端
RPCClientProxy rpcClientProxy = new RPCClientProxy(rpcClient);
// 代理客戶端根據不同的服務,獲得一個代理類, 并且這個代理類的方法以或者增強(封裝資料,發送請求)
UserService userService = rpcClientProxy.getProxy(UserService.class);
// 服務的方法1
User userByUserId = userService.getUserByUserId(10);
System.out.println("從服務器端得到的user為:" + userByUserId);
// 服務的方法2
User user = User.builder().userName("張三").id(100).sex(true).build();
Integer integer = userService.insertUserId(user);
System.out.println("向服務器端插入資料" + integer);
// 服務的方法3
BlogService blogService = rpcClientProxy.getProxy(BlogService.class);
Blog blogById = blogService.getBlogById(10000);
System.out.println("從服務端得到的blog為:" + blogById);
}
}
client中的其他代碼和simpleRPC-05一樣,可以直接從simpleRPC-05復制粘貼過來,為了完整,我還是把代碼放下面:
RPCClient.java
package com.rpc.client;
import com.rpc.common.RPCRequest;
import com.rpc.common.RPCResponse;
/**
* @author zwy
*
* RPC客戶端:發送請求,獲得response
*/
public interface RPCClient {
RPCResponse sendRequest(RPCRequest request);
}
RPCClientProxy.java
package com.rpc.client;
import com.rpc.common.RPCRequest;
import com.rpc.common.RPCResponse;
import lombok.AllArgsConstructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author zwy
*
* 客戶端代理:把動態代理封裝request物件(這里和simpleRPC-02的ClientProxy函式一樣,保留了動態代理的設計)
*/
@AllArgsConstructor
public class RPCClientProxy implements InvocationHandler {
private RPCClient client;
// jdk動態代理,每一次代理物件呼叫方法,會經過此方法增強(反射獲取request物件,socket發送至客戶端)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// request的構建,使用了lombok中的builder,更加簡潔
RPCRequest request = RPCRequest.builder().interfaceName(method.getDeclaringClass().getName())
.methodName(method.getName())
.params(args)
.paramsTypes(method.getParameterTypes())
.build();
// 資料傳輸
RPCResponse response = client.sendRequest(request);
// System.out.println(response);
return response.getData();
}
<T> T getProxy(Class<T> clazz) {
Object o = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, this);
return (T)o;
}
}
NettyClientInitializer.java
package com.rpc.client;
import com.rpc.codec.JsonSerializer;
import com.rpc.codec.MyDecode;
import com.rpc.codec.MyEncode;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
/**
* @author zwy
*
* 同樣的與服務端解碼和編碼格式
*/
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 使用自定義的編解碼器
pipeline.addLast(new MyDecode());
// 編碼需要傳入序列化器,這里是json,還支持ObjectSerializer,也可以自己實作其他的
pipeline.addLast(new MyEncode(new JsonSerializer()));
pipeline.addLast(new NettyClientHandler());
}
}
NettyClientHandler.java
package com.rpc.client;
import com.rpc.common.RPCResponse;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.AttributeKey;
/**
* @author zwy
*/
public class NettyClientHandler extends SimpleChannelInboundHandler<RPCResponse> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, RPCResponse msg) throws Exception {
// 接收到response, 給channel設計別名,讓sendRequest里讀取response
AttributeKey<RPCResponse> key = AttributeKey.valueOf("RPCResponse");
ctx.channel().attr(key).set(msg);
ctx.channel().close();
}
// 跟NettyRPCServerHandler一樣
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
service
服務暴露類加入注冊的功能,ServiceProvider.java 做相應的修改:
package com.rpc.service;
import com.rpc.register.ServiceRegister;
import com.rpc.register.ZkServiceRegister;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
/**
* @author zwy
*/
public class ServiceProvider {
/**
* 一個實作類可能實作多個服務介面,
*/
private Map<String, Object> interfaceProvider;
private ServiceRegister serviceRegister;
private String host;
private int port;
public ServiceProvider(String host, int port){
// 需要傳入服務端自身的服務的網路地址
this.host = host;
this.port = port;
this.interfaceProvider = new HashMap<>();
this.serviceRegister = new ZkServiceRegister();
}
public void provideServiceInterface(Object service) throws Exception {
Class<?>[] interfaces = service.getClass().getInterfaces();
for(Class clazz : interfaces){
// 本機的映射表
interfaceProvider.put(clazz.getName(),service);
// 在注冊中心注冊服務
serviceRegister.register(clazz.getName(), new InetSocketAddress(host, port));
}
}
public Object getService(String interfaceName){
return interfaceProvider.get(interfaceName);
}
}
service中的其他代碼和simpleRPC-05一樣,可以直接從simpleRPC-05復制粘貼過來,為了完整,我還是把代碼放下面:
BlogService.java
package com.rpc.service;
import com.rpc.common.Blog;
public interface BlogService {
Blog getBlogById(Integer id);
}
BlogServiceImpl.java
package com.rpc.service;
import com.rpc.common.Blog;
public class BlogServiceImpl implements BlogService {
@Override
public Blog getBlogById(Integer id) {
Blog blog = Blog.builder()
.id(id)
.title("我的博客")
.useId(22).build();
System.out.println("客戶端查詢了" + id + "博客");
return blog;
}
}
UserService.java
package com.rpc.service;
import com.rpc.common.User;
/**
* @author zwy
*/
public interface UserService {
// 客戶端通過這個介面呼叫服務端的實作類
User getUserByUserId(Integer id);
// 給這個服務增加一個功能
Integer insertUserId(User user);
}
UserServiceImpl.java
package com.rpc.service;
import com.rpc.common.User;
/**
* @author zwy
*/
public class UserServiceImpl implements UserService {
@Override
public User getUserByUserId(Integer id) {
// 模擬從資料庫中取用戶的行為
User user = User.builder()
.id(id)
.userName("he2121")
.sex(true).build();
System.out.println("客戶端查詢了" + id + "的用戶");
return user;
}
@Override
public Integer insertUserId(User user) {
System.out.println("插入資料成功: " + user);
return 1;
}
}
server
TestServer.java 做相應的修改:
package com.rpc.server;
import com.rpc.service.*;
public class TestServer {
public static void main(String[] args) throws Exception {
UserService userService = new UserServiceImpl();
BlogService blogService = new BlogServiceImpl();
// 這里重用了服務暴露類,順便在注冊中心注冊,實際上應分開,每個類做各自獨立的事
ServiceProvider serviceProvider = new ServiceProvider("127.0.0.1", 8899); // 8899
serviceProvider.provideServiceInterface(userService);
serviceProvider.provideServiceInterface(blogService);
RPCServer RPCServer = new NettyRPCServer(serviceProvider);
RPCServer.start(8899);
}
}
server中的其他代碼和simpleRPC-05一樣,可以直接從simpleRPC-05復制粘貼過來,為了完整,我還是把代碼放下面:
RPCServer.java
package com.rpc.server;
/**
* @author zwy
*/
public interface RPCServer {
void start(int port);
void stop();
}
NettyRPCServer.java
package com.rpc.server;
import com.rpc.service.ServiceProvider;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.AllArgsConstructor;
/**
* @author zwy
*/
@AllArgsConstructor
public class NettyRPCServer implements RPCServer {
private ServiceProvider serviceProvider;
@Override
public void start(int port) {
// netty服務執行緒組負責建立連接(TCP/IP連接),work負責具體的請求
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
System.out.println("Netty服務端啟動了");
try {
// 啟動Netty服務器
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 初始化
serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
.childHandler(new NettyServerInitializer(serviceProvider));
// 同步阻塞
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
// 死回圈監聽
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
@Override
public void stop() {
}
}
NettyServerInitializer.java
package com.rpc.server;
import com.rpc.codec.JsonSerializer;
import com.rpc.codec.MyDecode;
import com.rpc.codec.MyEncode;
import com.rpc.service.ServiceProvider;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import lombok.AllArgsConstructor;
/**
* @author zwy
*/
@AllArgsConstructor
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {
private ServiceProvider serviceProvider;
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 使用自定義的解碼器
pipeline.addLast(new MyDecode());
// 使用自定義的編碼器,而且解碼器需要傳入序列化器,這里是json,還支持ObjectSerializer,也可以自己實作其他的
pipeline.addLast(new MyEncode(new JsonSerializer()));
pipeline.addLast(new NettyRPCServerHandler(serviceProvider));
}
}
NettyRPCServerHandler.java
package com.rpc.server;
import com.rpc.common.RPCRequest;
import com.rpc.common.RPCResponse;
import com.rpc.service.ServiceProvider;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.AllArgsConstructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author zwy
*/
@AllArgsConstructor
public class NettyRPCServerHandler extends SimpleChannelInboundHandler<RPCRequest> {
private ServiceProvider serviceProvider;
@Override
protected void channelRead0(ChannelHandlerContext ctx, RPCRequest msg) throws Exception {
// System.out.println(msg);
RPCResponse response = getResponse(msg);
ctx.writeAndFlush(response);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
// 這里和WorkThread里的getResponse差不多
RPCResponse getResponse(RPCRequest request) {
// 得到服務名
String interfaceName = request.getInterfaceName();
// 得到服務器相應類
Object service = serviceProvider.getService(interfaceName);
// 反射呼叫方法
Method method = null;
try {
method = service.getClass().getMethod(request.getMethodName(), request.getParamsTypes());
Object invoke = method.invoke(service, request.getParams());
return RPCResponse.success(invoke);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
System.out.println("方法執行錯誤");
return RPCResponse.fail();
}
}
}
common
和simpleRPC-05一樣,可以直接復制過來,

codec
和simpleRPC-05一樣,可以直接復制過來,

檔案結構
simpleRPC-06的檔案結構如下:


運行
啟動TestServer.java :

然后啟動TestClient.java:


我們來看看我們最開始開的zookeeper客戶端:
現在輸入ls /

發現我們多了一個結點 MyRPC:
輸入ls /MyRPC:

可以看到我們注冊的服務都在這里,成功!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/402694.html
標籤:其他
上一篇:【無標題】一定要走,走到燈火通明
