网络编程——分布式远程调用

远程调用方案

1. RMI

远程方法调用(Remote Method Invocation) 只适用于java语言
在本地调用,操作的是服务器端口在本地的副本,通过stub来重放服务器的端口;服务端也设置了一个扶助对象skeleton。实质上是stub和skeleton在直接交互
因此,RMI必须将服务端的接口在本地复制一份

2. RPC

远程过程调用(Remote Procedure Call Protocol) 跨平台、跨语言,客户端和服务器端之间通过网络服务协议来进行请求和响应

案例:实现自定义的RMI

实现思路:

(1) 客户端和服务器端通过socket交互信息
1. 客户端以字符串的形式定义好要请求的接口名字,然后通过反射,解析出请求的接口名、方法名、参数类型、参数值等,并将这些信息通过对象流ObjectOutputStream发送给服务端
2. 服务端逐个解析这些信息,并通过反射中的invoke()方法调用服务端上被请求的方法
(2) 服务端可能存在多个提供方法的接口,简单实现一个服务注册中心
(3)服务注册中心
(4)服务完毕后,将返回值通过对象流返回给客户端
(5)客户端需要通过动态代理来获取服务端的返回值

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 服务端:提供服务的接口
package rmi.server;

/**
 * @ClassName: RMIService
 * @Description: 服务端的服务
 * @Author sunsl
 * @Date 2022/9/11 21:06
 * @Version 1.0
 */
public interface RMIService {
    public String hello(String name);
}

  1. 服务端:实现类
package rmi.server;

/**
 * @ClassName: RMIServiceImpl
 * @Description: TODO
 * @Author sunsl
 * @Date 2022/9/11 21:46
 * @Version 1.0
 */
public class RMIServiceImpl implements RMIService {
    @Override
    public String hello(String name) {
        return "服务端已收到请求,你好," + name;
    }
}

  1. 服务端:注册中心接口
package rmi.server;

/**
 * @ClassName: RMIEureka
 * @Description: 简单的服务注册中心
 * @Author sunsl
 * @Date 2022/9/11 21:48
 * @Version 1.0
 */
public interface RMIEureka {
    //启动服务
    public void start() ;
    //关闭服务
    public void stop();
    //注册服务
    public void register(Class service, Class serviceImpl);
}

  1. 服务端:实现类
package rmi.server;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @ClassName: RMIEurekaImpl
 * @Description: 服务注册中心具体实现
 * @Author sunsl
 * @Date 2022/9/11 21:48
 * @Version 1.0
 */
public class RMIEurekaImpl implements RMIEureka {
    // map:服务端的所有可供客户端访问的接口,都注册到该map中
    // key:接口的名字(如"RMIService"),
    // value:真正的提供服务的类(如RMIServiceImpl类)
    private static HashMap<String, Class> register = new HashMap<>();

    //服务端的端口号
    private static int port;

    //线程池,获取当前环境CPU核数
    private static ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    //是否开启服务
    private static volatile boolean isRunning = false;

    // 构造函数,传入端口号
    public RMIEurekaImpl(int port) {
        this.port = port;
    }

    // 开启服务端服务
    @Override
    public void start() {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket();
            // 绑定IP地址
            serverSocket.bind(new InetSocketAddress(port));
        } catch (IOException e) {
            e.printStackTrace();
        }
        isRunning = true;
        //客户端每次请求一次请求,服务端从线程池中启动一个线程对象去处理
        while (true){
            //具体的服务内容:接收客户端请求,处理请求,并返回结果
            System.out.println("服务已启动...");
            Socket socket = null;
            try {
                //The method blocks until a connection is made.
                socket = serverSocket.accept();// 等待客户端连接
            } catch (IOException e) {
                e.printStackTrace();
            }
            //启动一个线程 去处理客户请求
            threadPool.execute(new ServiceTask(socket));
        }
    }

    @Override
    public void stop() {
        isRunning = false;
        threadPool.shutdown();
    }

    //将接口名和接口实现类一一对应,以便于接收到请求时,能及时获取到对应的服务实现类
    @Override
    public void register(Class service, Class serviceImpl) {
        register.put(service.getName(), serviceImpl);
    }

    //处理请求的线程
    private static class ServiceTask implements Runnable {
        private Socket socket;

        public ServiceTask() {
        }

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

        // 处理逻辑
        @Override
        public void run() {
            ObjectInputStream inputStream = null;
            ObjectOutputStream outputStream = null;
            try {
                //接收客户端请求的参数
                inputStream = new ObjectInputStream(socket.getInputStream());

                // 因为ObjectInputStream对发送数据的顺序有严格要求,因此必须按照发送的顺序逐个接收
                // 请求的接口名
                String serviceName = inputStream.readUTF();
                String methodName = inputStream.readUTF();
                Class<?>[] parameterTypes = ( Class<?>[]) inputStream.readObject();
                Object[] args = (Object []) inputStream.readObject();

                // 根据客户请求,到服务注册中心map中找到与之对应的具体接口(即RMIService)
                Class ServiceClass = register.get(serviceName);
                // 构造请求的方法
                Method method = ServiceClass.getMethod(methodName, parameterTypes);
                // 执行该服务端方法
                Object result = method.invoke(ServiceClass.newInstance(), args);

                // 执行完成后返回给客户端
                outputStream = new ObjectOutputStream(socket.getOutputStream());
                outputStream.writeObject(result);

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

  1. 客户端:
package rmi.client;

/**
 * @ClassName: RMIClient
 * @Description: RMI客户端,发送请求,接收服务端返回的数据(代理模式
 * @Author sunsl
 * @Date 2022/9/11 21:05
 * @Version 1.0
 */

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;

@SuppressWarnings("all")
public class RMIClient {
    /**
     * 获取代表服务端接口的动态代理对象(RMIService)
     * @param serviceInterface 请求的接口名
     * @param addr 待请求服务端的ip:端口
     * @param <T>
     * @return
     */
    public static <T> T getRemoteProxyObj(Class serviceInterface, InetSocketAddress addr) {
        /**
		   newProxyInstance(a,b,c)中,前两个参数的含义如下:
		   	a:类加载器 :需要代理那个对象的类加载器
		    b:用于表示需要代理的对象提供了哪些方法。Java是单继承、多实现,
		    	因此,如果某一个对象实现了多个接口,那么该对象就拥有其全部接口的所有方法,因此是一个接口数组。
		        例如,如果有A implements B接口,c接口,并且B接口中有3个方法,C接口中有2个方法,那么A的对象就拥有5个方法(暂不考虑A自身提供的方法)
		 */
        return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(),
                new Class<?>[]{serviceInterface},
                new InvocationHandler() {
                    //proxy:代理的对象, method:哪个方法(sayHi(参数列表)), args:参数列表
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 客户端向服务端发送请求,请求某一个具体的接口
                        Socket socket = new Socket();
                        ObjectOutputStream output = null;
                        ObjectInputStream input = null;
                        try {
                            //请求地址
                            socket.connect(addr);

                            //通过序列化流向服务端发送请求
                            output = new ObjectOutputStream(socket.getOutputStream());
                            //发送请求的接口名、方法名、参数类型、参数值
                            output.writeUTF(serviceInterface.getName());
                            output.writeUTF(method.getName());
                            output.writeObject(method.getParameterTypes());
                            output.writeObject(args);

                            //等待处理...
                            //接收处理后返回的返回值
                            input = new ObjectInputStream(socket.getInputStream());
                            return input.readObject();
                        } catch (Exception e) {
                            e.printStackTrace();
                            return null;
                        } finally {
                            // 关闭打开的流
                            try {
                                if (output != null) output.close();
                                if (input != null) input.close();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                });
    }


}

  1. 服务端启动类:
package rmi.test;

import rmi.server.RMIEurekaImpl;
import rmi.server.RMIServiceImpl;
import rmi.server.RMIServiceImpl2;
import rmi.server.RMIService;

/**
 * @ClassName: TestServer
 * @Description: 开启服务端
 * @Author sunsl
 * @Date 2022/9/11 22:35
 * @Version 1.0
 */
public class TestServer {
    public static void main(String[] args) {
        //用线程的形式启动服务
        new Thread(new Runnable() {
            @Override
            public void run() {
                //服务中心
                RMIEurekaImpl server = new RMIEurekaImpl(9999);
                //将RMIService接口及实现类,注册到服务中心
                server.register(RMIService.class, RMIServiceImpl.class);
                server.start();
            }
        }).start();
    }
}

  1. 客户端启动类:
package rmi.test;

import rmi.client.RMIClient;
import rmi.server.RMIService;

import java.net.InetSocketAddress;

/**
 * @ClassName: TestClient
 * @Description: 开启客户端
 * @Author sunsl
 * @Date 2022/9/11 22:27
 * @Version 1.0
 */
public class TestClient {
    public static void main(String[] args) throws ClassNotFoundException {
        //调用远程的rmi.server.RMIService接口,并执行接口中的sayHi()方法
        RMIService remoteProxyObj = RMIClient.getRemoteProxyObj(
                Class.forName("rmi.server.RMIService"),
                new InetSocketAddress("127.0.0.1", 9999)
        );
        System.out.println(remoteProxyObj.hello("sunsl"));
    }
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值