RPC系列-初体会

RPC(Remote Process Call) 远程过程调用,是分布式服务调用的流行的解决方案,现在比较流行的RPC框架有Google的grpc,脸谱的Thrift,Hadoop的子项目Avro-RPC等。
作为RPC的菜鸟,该怎么建立起对RPC的初步认识呢,那就亲自手动的自己敲一个简单的RPC实现,对RPC的理解有很大的帮助。下面是自己模拟简单实现RPC的实现,代码简陋,适合初学(最好自己使用过RPC技术)。
建立两个项目RpcService,RpcClient
一、Rpc服务端(RpcService)
服务器端建立对外提供服务的service,rpc的发布,开启服务,对应的包结构:
com.hhh.rpc.service –服务提供
com.hhh.rpc.publish –服务发布
com.hhh.app –服务开启

1、 对外提供的服务
1.1 服务接口

package com.hhh.rpc.service;

/**
 * Created by 泡泡 on 2017/3/23.
 * 服务提供
 */
public interface GreetService {
    String greet(String msg);
}

1.2 服务实现

package com.hhh.rpc.service.impl;

import com.hhh.rpc.service.GreetService;

/**
 * Created by 泡泡 on 2017/3/23.
 * 服务实现
 */
public class GreetServiceImpl implements GreetService {

    @Override
    public String greet(String msg) {
        return "servive greeting:" + msg != null ? msg :"Hello RPC!";
    }
}

2、服务发布
服务发布利用传统的阻塞Socket通信

package com.hhh.rpc.publish;

import java.io.*;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by 泡泡 on 2017/3/23.
 * 服务发布
 */
public class RpcPublish {
    private static final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    /**
     * host:对外暴露的服务地址
     * port:服务端口
     **/
    public static void publish(String host,int port){

        ServerSocket server = null;
        try{
            server = new ServerSocket();
            server.bind(new InetSocketAddress(host,port));
            while (true){
                executor.execute(new PublishTask(server.accept()));
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally {
             try {
                 if(server != null){
                     server.close();
                 }
            } catch (IOException e) {
            }
        }


    }

    /**
     * 利用线程池方式还处理客户端的服务调用
     *
     * 服务调用处理任务
     */
   private static final class PublishTask implements  Runnable{

        private Socket socket;
        @Override
        public void run() {

            ObjectInputStream is = null;
            ObjectOutputStream os = null;
            try{

                is = new ObjectInputStream(socket.getInputStream());
                //读取类名并获取对应的类的类
                Class<?> service = Class.forName(is.readUTF());
                //读取调用的方法名
                String methodName = is.readUTF();
                //读取参数类型
                Class<?>[] paramTypes = (Class<?>[])is.readObject();
                Method method = service.getMethod(methodName,paramTypes);
                //读取参数列表
                Object[] args = (Object[]) is.readObject();
                //方法调用返回的结果
                Object result = method.invoke(service.newInstance(),args);
                System.out.println(service.getName()+"."+methodName+"("+paramTypes[0].toString()+" "+args[0].toString()+")");
                os = new ObjectOutputStream(socket.getOutputStream());
                //结果写回
                os.writeObject(result);

            }catch (Exception e){
                e.printStackTrace();
            }finally {
                 try {
                     if(is !=null){
                         is.close();
                     }
                     if(os != null){
                         os.close();
                     }

                     if(socket != null){
                         socket.close();
                     }

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        public PublishTask(Socket socket){
            this.socket = socket;
        }
    }
}

3、服务开启

package com.hhh.app;

import com.hhh.rpc.publish.RpcPublish;

/**
 * Created by 泡泡 on 2017/3/23.
 */
public class ServiceApp {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                RpcPublish.publish("127.0.0.1",8888);
            }
        },"service").start();
        System.out.println("server start...");
        while (true){
        }
    }

二、RPC消费端(RpcClient)

模拟调用RpcService提供的服务,包结构如下:
com.hhh.rpc.service –相当于远程服务对外暴露的API
com.hhh.rpc.client–本地调用远程服务发现
com.hhh.app –消费远程服务
1、远程服务的API

package com.hhh.rpc.service;

/**
 * Created by 泡泡 on 2017/3/23.
 * 远程服务的API
 */
public interface GreetService {

    String greet(String msg);
}

2、本地调用远程服务发现

package com.hhh.rpc.client;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
 * Created by 泡泡 on 2017/3/23.
 * 远程服务发现
 * T 远程服务代理
 */
public class RpcClient<T> {

    /**
     *
     * @param className 远程服务的实现类
     * @param service 远程调用的API
     * @param address 远程服务地址
     * @return
     */
    public T subscribe(final String className,final Class<?> service, final InetSocketAddress address){
        System.out.println(service.getInterfaces().length);
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{
                        service},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Socket socket = null;
                        ObjectInputStream is = null;
                        ObjectOutputStream os =null;
                        try {
                            socket = new Socket();
                            socket.connect(address);
                            os = new ObjectOutputStream(socket.getOutputStream());
                            //远程调用的实现类
                            os.writeUTF(className);
                            //调用的方法
                            os.writeUTF(method.getName());
                            //方法的参数类型列表
                            os.writeObject(method.getParameterTypes());
                            //方法参数列表
                            os.writeObject(args);
                            //返回远程调用结果
                            is = new ObjectInputStream(socket.getInputStream());
                            return is.readObject();
                        }catch(Exception e){
                            e.printStackTrace();
                        }finally {
                            if(os != null){
                                os.close();
                            }
                            if(is != null){
                                is.close();
                            }

                            if(socket != null){
                                socket.close();
                            }
                        }
                        return null;
                    }
                });
    }
}

3、远程服务调用

package com.hhh.app;

import com.hhh.rpc.client.RpcClient;
import com.hhh.rpc.service.GreetService;

import java.net.InetSocketAddress;

/**
 * Created by 泡泡 on 2017/3/23.
 * 远程服务调用
 */
public class ClientApp {

    /**
     * 服务的发现有很多种,服务的拉取在这里这里写死了
     */
    private static final String SERVER_NAME = "com.hhh.rpc.service.impl.GreetServiceImpl";
    public static void main(String[] args) {

        RpcClient<GreetService> client = new RpcClient<>();
        GreetService greet = client.subscribe(SERVER_NAME,GreetService.class,new InetSocketAddress("127.0.0.1",8888));
        System.out.println(greet.greet("I'm coming! RPC"));
    }
}

三、运行
先启动服务(运行ServiceApp),运行结果:

server start...

运行客户端(ClientApp),运行结果:

0
servive greeting:I'm coming! RPC

这时你会发现服务端还会打印一句话,说明远程服务确实调用了

com.hhh.rpc.service.impl.GreetServiceImpl.greet(class java.lang.String I'm coming! RPC)

好了,简单的RPC框架的实现就结束了。
总结如下:
1. 服务端的发布,通过不同的协议来处理消费服务的请求,通过反射来来实现服务的调用,本实例的使用socket通信。
2. 消费服务端需要知道服务提供的那些服务(也就是对外的API)以及服务发现

这里就不多细说RPC框架的细节,本篇只作为RPC新手的引导和认识一下RPC,更多RPC框架内容请期待下期。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值