继上一篇文章实现Server端后,本篇实现Client端。上篇文章链接https://blog.csdn.net/qq_22200097/article/details/83048668
1. 添加依赖
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.10.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.38</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.8</version>
</dependency>
2. 要调用的接口
/*
* @author uv
* @date 2018/10/12 8:56
* 为了方便,这里就不把接口打成jar包调用了,从server端直接拿来用,保持和Server端一样的包路径即可
*/
public interface UserService {
String sayHello(String name);
}
3. 全局配置类,用于存储client连接server产生的channel和处理server返回信息的handler
/*
* @author uv
* @date 2018/10/14 15:34
* 全局配置
*/
import com.uv.rpc.netty.client.ClientHandler;
import io.netty.channel.Channel;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class GlobalConfig {
//key为ip:port,存储channel,方便其他类获取channel
private static Map<String, Channel> channelMap = new ConcurrentHashMap<>();
//key为ip:port,存储handler,方便其他类获取handler
private static Map<String, ClientHandler> handlerMap = new ConcurrentHashMap<>();
public static Map<String, Channel> getChannelMap() {
return channelMap;
}
public static Map<String, ClientHandler> getHandlerMap() {
return handlerMap;
}
}
4. zookeeper连接类,用于client连接zookeeper并根据接口名获取server端的地址
/*
* @author uv
* @date 2018/10/11 15:56
* zookeeper 工具类(服务发现)
* zookeeper存储结构为目录层级结构
*/
import java.io.IOException;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
public class ZookeeperUtil {
//zookeeper连接超时时间
private static final int SESSION_TIMEOUT = 5000;
//在zookeeper注册的根节点
private static final String ROOT = "/rpc";
private static ZooKeeper zooKeeper = null;
/**
*
* @param ip zookeeper服务的IP地址
* @param port 端口号
* 连接zookeeper服务
*/
public static void connect(String ip, int port) {
try {
zooKeeper = new ZooKeeper(ip + ":" + port, SESSION_TIMEOUT, null);
} catch (IOException e) {
System.out.println("zookeeper连接失败!");
e.printStackTrace();
}
}
/**
* @param api 调用的接口
* 从zookeeper获取接口所在服务的地址和端口号
*/
public static String getDataFromServer(String api) {
try {
String path = ROOT + "/" + api;
Stat exists = zooKeeper.exists(path, true);
//节点存在
if(exists != null) {
byte[] data = zooKeeper.getData(ROOT + "/" + api, true, new Stat());
String ipAndPost = new String(data);
System.out.println("取到节点:" + ipAndPost);
return ipAndPost;
}
} catch (Exception e) {
System.out.println("zookeeper获取节点失败");
e.printStackTrace();
}
return null;
}
}
5. NettyClient,根据从zookeeper获取的ip+port连接NettyServer
/*
* @author uv
* @date 2018/10/12 20:54
* NettyClient
*/
import com.uv.rpc.config.GlobalConfig;
import com.uv.rpc.netty.protocol.RpcDecoder;
import com.uv.rpc.netty.protocol.RpcEncoder;
import com.uv.rpc.netty.protocol.RpcRequest;
import com.uv.rpc.netty.protocol.RpcResponse;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
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.NioSocketChannel;
public class NettyClient {
private final String host;
private final int port;
//连接服务端的端口号地址和端口号
public NettyClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws Exception {
final EventLoopGroup group = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class) // 使用NioSocketChannel来作为连接用的channel类
.handler(new ChannelInitializer<SocketChannel>() { // 绑定连接初始化器
@Override
public void initChannel(SocketChannel ch) throws Exception {
System.out.println("正在连接中...");
ClientHandler handler = new ClientHandler();
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new RpcEncoder(RpcRequest.class)); //编码request
pipeline.addLast(new RpcDecoder(RpcResponse.class)); //解码response
pipeline.addLast(handler); //客户端处理类
GlobalConfig.getHandlerMap().put(host + ":" + port, handler);
}
});
//发起异步连接请求,绑定连接端口和host信息
final ChannelFuture future = b.connect(host, port).sync();
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture arg0) throws Exception {
if (future.isSuccess()) {
System.out.println("连接服务器成功");
} else {
System.out.println("连接服务器失败");
future.cause().printStackTrace();
group.shutdownGracefully(); //关闭线程组
}
}
});
//放入map中存储
GlobalConfig.getChannelMap().put(host + ":" + port, future.channel());
}
//开启客户端,连接服务端
public static synchronized ClientHandler startNettyClient(String host, int port) {
try {
//map中没有有对象,则客户端未启动
if(!GlobalConfig.getHandlerMap().containsKey(host + ":" + port) || !GlobalConfig.getChannelMap().containsKey(host + ":" + port)) {
NettyClient client = new NettyClient(host, port);
//启动客户端
client.start();
}
return GlobalConfig.getHandlerMap().get(host + ":" + port);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
6. NettyClient消息处理类,处理Server端返回的消息
package com.uv.rpc.netty.client;
/*
* @author uv
* @date 2018/10/12 20:56
* client消息处理类
*/
import com.uv.rpc.netty.protocol.RpcFuture;
import com.uv.rpc.netty.protocol.RpcRequest;
import com.uv.rpc.netty.protocol.RpcResponse;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ClientHandler extends SimpleChannelInboundHandler<RpcResponse>{
//存储future(类似java线程执行返回结果的future)
private Map<String, RpcFuture> futureMap = new ConcurrentHashMap<>();
//处理服务端返回的数据
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcResponse response) throws Exception {
//request和response的ID相同,根据ID存储future
RpcFuture rpcFuture = futureMap.get(response.getId());
//服务端发送回response,放到future中,供java动态代理类获取结果
rpcFuture.setResponse(response);
//移除掉future
futureMap.remove(response.getId());
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
//发送消息,并返回用来存储response的future
public RpcFuture sendRequest(RpcRequest request, Channel channel) {
RpcFuture rpcFuture = new RpcFuture();
futureMap.put(request.getId(), rpcFuture);
channel.writeAndFlush(request);
return rpcFuture;
}
}
7. RpcDecoder、RpcEncoder、RpcRequest、RpcRepose同Server端的相同,此处就不再贴出,详细查看Server篇文章
8. RpcFuture(类似JDK中的Future):Netty是异步请求的方式,发送请求后不会立即返回响应结果,为了调用时保持同步获取响应结果,由request的ID对应future(即一个客户端的requst请求对应一个响应结果获取类future),在get方法内,通过lock的condition.await()方法,阻塞当前线程,当Handler接受到Server的响应结果后执行future的setResponse方法添加response,同时condition.singal(),唤醒阻塞的线程,线程唤醒后继续执行await()后的方法,返回取到的响应结果。
package com.uv.rpc.netty.protocol;
/*
* @author uv
* @date 2018/10/14 13:30
* 获取server返回消息
*/
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class RpcFuture implements Future<Object> {
private RpcResponse response = null;
//等待响应超时时间10s
private long timeout = 10000;
private final Lock lock = new ReentrantLock();
private final Condition done = lock.newCondition();
//未实现
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
throw new UnsupportedOperationException();
}
//未实现
@Override
public boolean isCancelled() {
throw new UnsupportedOperationException();
}
//有响应结果则已经完成响应
@Override
public boolean isDone() {
return response != null;
}
//获取响应结果,等待响应的超时时间为5s
@Override
public Object get() throws InterruptedException, ExecutionException {
return get(timeout, TimeUnit.MILLISECONDS);
}
//获取响应结果
@Override
public Object get(long timeout, TimeUnit unit) {
//记录响应时间
long start = System.currentTimeMillis();
lock.lock();
try {
//循环判断服务端是否返回响应结果
while (!isDone()) {
//阻塞当前线程,当future接收到response时被唤醒;或达到超时时间唤醒
done.await(timeout, TimeUnit.MILLISECONDS);
//服务端返回结果 或者 等待响应超时
if (isDone() || System.currentTimeMillis() - start > timeout) {
break;
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
//返回响应结果
if (response != null && response.getStatus() == 1) {
return response.getData();
}
return null;
}
//handler获取到响应结果,添加到future
public void setResponse(RpcResponse response) {
this.response = response;
//获取到响应结果,唤醒被阻塞的线程
lock.lock();
try {
if (done != null) {
done.signal();
}
} finally {
lock.unlock();
}
}
}
9. 动态代理,当接口执行时,由代理类远程获取结果并返回
package com.uv.rpc.proxy;
/*
* @author uv
* @date 2018/10/14 12:10
* 动态代理类
*/
import com.uv.rpc.config.GlobalConfig;
import com.uv.rpc.discovery.ZookeeperUtil;
import com.uv.rpc.netty.client.ClientHandler;
import com.uv.rpc.netty.client.NettyClient;
import com.uv.rpc.netty.protocol.RpcFuture;
import com.uv.rpc.netty.protocol.RpcRequest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.UUID;
public class InterfaceProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//根据接口,从zookeeper获取到服务端的ip+port
String ipAndPort = ZookeeperUtil.getDataFromServer(method.getDeclaringClass().getName());
if (ipAndPort == null || ipAndPort.length() == 0) {
return null;
}
String[] content = ipAndPort.split(":");
//client启动,连接到server
ClientHandler handler = NettyClient.startNettyClient(content[0], Integer.valueOf(content[1]));
//组装request
RpcRequest request = new RpcRequest()
.setId(UUID.randomUUID().toString())
.setClassName(method.getDeclaringClass().getName())
.setMethodName(method.getName())
.setParameterTypes(method.getParameterTypes())
.setParameters(args);
//获取server端的响应结果
RpcFuture rpcFuture = handler.sendRequest(request, GlobalConfig.getChannelMap().get(content[0] + ":" + content[1]));
return rpcFuture.get();
}
//动态代理绑定
public static <T> T newInterfaceProxy(Class<T> intf) {
ClassLoader classLoader = intf.getClassLoader();
Class<?>[] interfaces = new Class[]{intf};
InterfaceProxy proxy = new InterfaceProxy();
return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
}
}
10. 客户端启动并发测试
import com.uv.api.UserService;
import com.uv.rpc.discovery.ZookeeperUtil;
import com.uv.rpc.proxy.InterfaceProxy;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* <uv> [2018/10/15 9:25]
*/
public class Main {
public static void main(String[] args) throws InterruptedException {
//连接到zookeeper
ZookeeperUtil.connect("127.0.0.1", 2181);
ExecutorService executorService = Executors.newCachedThreadPool();
//设置线程发令枪,计数器为51,启动50个线程惊醒等待
CountDownLatch latch = new CountDownLatch(51);
for (int i = 0; i < 50; i++) {
executorService.execute(new Task(latch));
}
//主线程睡眠10s,等待50个线程就绪
Thread.sleep(10000);
System.out.println("----并发开始-----");
//计数器减一到达零,线程开始执行
latch.countDown();
}
}
//并发测试
class Task implements Runnable {
private final CountDownLatch latch;
public Task(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
//计数器减一
latch.countDown();
try {
//线程阻塞,当计数器等于零时,唤醒该线程
latch.await();
//动态代理绑定
UserService userService = InterfaceProxy.newInterfaceProxy(UserService.class);
//执行结果
System.out.println(userService.sayHello("Tom"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
11. 依次启动zookeeper、server和client,测试结果如下,响应成功。
github源码地址:https://github.com/UVliuwei/UVRpc