简单实现RPC框架(二)客户端的实现

继上一篇文章实现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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值