BIO 应用-RPC 框架

什么是 RPC?

  • RPC(Remote Procedure Call ——远程过程调用),它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络的技术。

在这里插入图片描述

  • 一次完整的 RPC 同步调用流程:
    1)服务消费方(client)以本地调用方式调用客户端存根;
    2)什么叫客户端存根?就是远程方法在本地的模拟对象,一样的也有方法名,也有方法参数,client stub(客户端存根) 接收到调用后负责将方法名、方法的参数等包装,并将包装后的信息通过网络发送到服务端;
    3)服务端收到消息后,交给代理存根在服务器的部分后进行解码为实际的方法名和参数;
    4)server stub(服务端存根) 根据解码结果调用服务器上本地的实际服务;
    5)本地服务执行并将结果返回给 server stub;
    6)server stub 将返回结果打包成消息并发送至消费方;
    7)client stub 接收到消息,并进行解码;
    8)服务消费方得到最终结果。
  • RPC 框架的目标就是要中间步骤都封装起来,让我们进行远程方法调用的时候感觉到就 像在本地调用一样。

RPC 和 HTTP

  • rpc 字面意思就是远程过程调用,只是对不同应用间相互调用的一种描述,一种思想。 具体怎么调用?实现方式可以是最直接的 tcp 通信,也可以是 http 方式,在很多的消息中间件的技术书籍里,甚至还有使用消息中间件来实现 RPC 调用的,我们知道的 dubbo 是基于 tcp 通信的,gRPC 是 Google 公布的开源软件,基于最新的 HTTP2.0 协议,底层使用到了 Netty 框架的支持。所以总结来说,rpc 和 http 是完全两个不同层级的东西,他们之间并没有什么可比性。

实现 RPC 框架

  • 实现 RPC 框架需要解决的那些问题

代理问题

  • 代理本质上是要解决什么问题?要解决的是被调用的服务本质上是远程的服务,但是调用者不知道也不关心,调用者只要结果,具体的事情由代理的那个对象来负责这件事。既然是远程代理,当然是要用代理模式了。

序列化问题

  • 序列化问题在计算机里具体是什么?我们的方法调用,有方法名,方法参数,这些可能是字符串,可能是我们自己定义的 java 的类,但是在网络上传输或者保存在硬盘的时候, 网络或者硬盘并不认得什么字符串或者 javabean,它只认得二进制的 0、1 串,所以要进行序列化,网络传输后要进行实际调用,就要把二进制的 0、1 串变回我们实际的 java 的类, 这个叫反序列化。java 里已经为我们提供了相关的机制 Serializable。

通信问题

  • 我们在用序列化把东西变成了可以在网络上传输的二进制的 0、1 串,但具体如何通过网络传输?使用 JDK 为我们提供的 BIO。

登记的服务实例化

  • 登记的服务有可能在我们的系统中就是一个名字,怎么变成实际执行的对象实例,当然是使用反射机制。

  • 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对 于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。

  • 反射机制主要提供了以下功能:
    1、在运行时判断任意一个对象所属的类;
    2、在运行时构造任意一个类的对象;
    3、在运行时判断任意一个类所具有的成员变量和方法;
    4、在运行时调用任意一个对象的方法;
    5、生成动态代理。

  • 下面实现一个简单rpc框架,像负载均衡、容灾、集群、断线重连这些复杂功能就不实现了。

    扫描二维码关注公众号,回复: 13586967 查看本文章

创建注册中心

1. pom.xml

  • 使用一个最简单的web项目

在这里插入图片描述

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>rpc-reg</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rpc-reg</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2. properties

  • 设置服务的端口,因为后面还有服务端和客户端

在这里插入图片描述

server.port=8080

3. 启动类

在这里插入图片描述

package com.example.rpc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RpcRegApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(RpcRegApplication.class, args);
    }

}

4. 注册中心实体类

  • 要实现序列化接口

在这里插入图片描述

package com.example.rpc.remote.vo;

import java.io.Serializable;

//注册中心实体
public class RegisterVO implements Serializable {
    
    
    private String host;
    private int port;

    public RegisterVO(String host, int port) {
    
    
        this.host = host;
        this.port = port;
    }

    public String getHost() {
    
    
        return host;
    }

    public void setHost(String host) {
    
    
        this.host = host;
    }

    public int getPort() {
    
    
        return port;
    }

    public void setPort(int port) {
    
    
        this.port = port;
    }
}

5. 注册中心实现类

  • 服务端启动时在注册中心注册自己的信息,客户端在启动时查询服务端信息

在这里插入图片描述

package com.example.rpc.service;

import com.example.rpc.remote.vo.RegisterVO;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//注册中心,服务端启动时在注册中心注册自己的信息,客户端在启动时查询服务端信息
@Service
public class RegisterCenter {
    
    

    private static ExecutorService executorService =
            Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    //key表示服务名,value表示服务地址的集合
    private static final Map<String, Set<RegisterVO>> serviceHoler = new HashMap<>();

    //注册服务的端口号
    private int port;

    //服务注册
    private static synchronized void registerService(String serviceName, String host, int port) {
    
    
        //获得当前服务的已有地址集合
        Set<RegisterVO> voSet = serviceHoler.get(serviceName);
        if (voSet == null) {
    
    
            //已有地址集合为空,则新增集合
            voSet = new HashSet<>();
            serviceHoler.put(serviceName, voSet);
            System.out.println("服务注册成功," + serviceName + "," + host + ":" + port);
        }
        //把新加入的服务地址加入集合
        voSet.add(new RegisterVO(host, port));
        System.out.println("服务地址添加成功," + host + ":" + port);
    }

    //取出服务提供者
    private static Set<RegisterVO> getService(String serviceName) {
    
    
        return serviceHoler.get(serviceName);
    }

    //处理服务请求的任务,1、注册服务,2、查询服务
    private static class ServerTask implements Runnable {
    
    
        private Socket socket = null;

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

        @Override
        public void run() {
    
    
            try (ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                 ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())) {
    
    
                //检查当前请求是注册还是查询
                boolean isGetService = inputStream.readBoolean();
                //查询服务
                if (isGetService) {
    
    
                    String serviceName = inputStream.readUTF();
                    //取出服务提供者的地址集合
                    Set<RegisterVO> voSet = getService(serviceName);
                    //返回给客户端
                    outputStream.writeObject(voSet);
                    outputStream.flush();
                    System.out.println("将查询的服务-" + serviceName + "发送给客户端");
                }
                //注册服务
                else {
    
    
                    //获取注册的服务名,ip,端口
                    String serviceName = inputStream.readUTF();
                    String host = inputStream.readUTF();
                    int port = inputStream.readInt();
                    //注册中心保存
                    registerService(serviceName, host, port);
                    outputStream.writeBoolean(true);
                    outputStream.flush();
                    System.out.println("服务注册成功:" + serviceName);
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

    public void startService() throws IOException {
    
    
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(port));
        System.out.println("注册中心 on:" + port);
        try {
    
    
            while (true) {
    
    
                executorService.execute(new ServerTask(serverSocket.accept()));
            }
        } finally {
    
    
            serverSocket.close();
        }
    }

    //启动注册中心服务
    @PostConstruct
    public void init() {
    
    
        this.port = 9001;
        new Thread(() -> {
    
    
            try {
    
    
                startService();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }).start();
    }

}

创建服务端

1. 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>rpc-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rpc-server</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2. properties

在这里插入图片描述

server.port=8081

3. 启动类

在这里插入图片描述

package com.example.rpc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RpcServerApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(RpcServerApplication.class, args);
        System.out.println("hello world");
    }

}

4. 服务端发送消息的实体类

在这里插入图片描述

package com.example.rpc.remote.vo;

import java.io.Serializable;

public class UserInfo implements Serializable {
    
    
    private String name;
    private String phone;

    public UserInfo(String name, String phone) {
    
    
        this.name = name;
        this.phone = phone;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public String getPhone() {
    
    
        return phone;
    }

    public void setPhone(String phone) {
    
    
        this.phone = phone;
    }
}

5. 对外提供的业务方法

在这里插入图片描述

package com.example.rpc.remote;

import com.example.rpc.remote.vo.UserInfo;

public interface SendSms {
    
    

    //发送短信
    boolean sendMsg(UserInfo userInfo);

}

6. 业务方法具体实现

在这里插入图片描述

package com.example.rpc.sms;

import com.example.rpc.remote.SendSms;
import com.example.rpc.remote.vo.UserInfo;
import org.springframework.stereotype.Service;

//短信息发送服务的实现
@Service
public class SendSmsImpl implements SendSms {
    
    
    @Override
    public boolean sendMsg(UserInfo userInfo) {
    
    
        try {
    
    
            Thread.sleep(2000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("已发送短信到:" + userInfo.getName());
        return true;
    }
}

7. 完成服务注册

在这里插入图片描述

package com.example.rpc.rpc.base;

import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

//注册服务
@Service
public class RegisterServiceWithRegCenter {
    
    

    //本地可提供服务的一个名单,用缓存实现
    private static final Map<String, Class> serviceCache = new ConcurrentHashMap<>();

    //往远程服务器注册本服务,同时在本地注册本服务
    public void regRemote(String serverName, String host, int port, Class impl) throws IOException {
    
    
        //登记到注册中心
        Socket socket = new Socket();
        //服务端的通信地址
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 9001);
        //连接服务器
        socket.connect(inetSocketAddress);
        //实例化与客户端通信的输入输出流,先输出再输入
        try (ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
             ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
    
    
            //用false表示注册服务,true表示查询服务
            outputStream.writeBoolean(false);
            //提供服务名
            outputStream.writeUTF(serverName);
            //提供ip
            outputStream.writeUTF(host);
            //提供端口
            outputStream.writeInt(port);
            outputStream.flush();
            //接收服务端的输出
            if (inputStream.readBoolean()) {
    
    
                System.out.println(serverName + "服务注册成功");
            }
            //可提供的服务放入本地缓存
            serviceCache.put(serverName, impl);
        } finally {
    
    
            if (socket != null) {
    
    
                socket.close();
            }
        }
    }

    //获取服务
    public Class getLocalService(String serviceName) {
    
    
        return serviceCache.get(serviceName);
    }

}

8. 服务端实现

  • 支持远程过程调用,提供业务方法

在这里插入图片描述

package com.example.rpc.rpc.base;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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;

//服务端
@Service
public class RpcServerFrame {
    
    

    private static ExecutorService executorService =
            Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    @Autowired
    private RegisterServiceWithRegCenter registerServiceWithRegCenter;

    //处理服务请求任务
    private static class ServerTask implements Runnable {
    
    

        private Socket socket;

        private RegisterServiceWithRegCenter registerServiceWithRegCenter;

        public ServerTask(Socket socket, RegisterServiceWithRegCenter registerServiceWithRegCenter) {
    
    
            this.socket = socket;
            this.registerServiceWithRegCenter = registerServiceWithRegCenter;
        }

        @Override
        public void run() {
    
    
            try (ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                 ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
    
    
                //方法所在的类名
                String serviceName = inputStream.readUTF();
                //方法名
                String methodName = inputStream.readUTF();
                //方法的入参类型
                Class<?>[] paramTypes = (Class<?>[]) inputStream.readObject();
                //方法的入参值
                Object[] args = (Object[]) inputStream.readObject();

                //从容器中拿到服务的class对象
                Class serviceClass = registerServiceWithRegCenter.getLocalService(serviceName);
                if (serviceClass == null) {
    
    
                    throw new ClassNotFoundException(serviceName + "not found");
                }
                //通过反射,执行实际的方法
                Method method = serviceClass.getMethod(methodName, paramTypes);
                Object result = method.invoke(serviceClass.newInstance(), args);
                //将服务的执行结果返回给调用者
                outputStream.writeObject(result);
                outputStream.flush();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                if (socket != null) {
    
    
                    try {
    
    
                        socket.close();
                    } catch (IOException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public void startService(String serverName, String host, int port, Class impl) throws Throwable {
    
    
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(port));
        System.out.println("server is on :" + port);
        registerServiceWithRegCenter.regRemote(serverName, host, port, impl);
        try {
    
    
            while (true) {
    
    
                executorService.execute(new ServerTask(serverSocket.accept(), registerServiceWithRegCenter));
            }
        } finally {
    
    
            serverSocket.close();
        }
    }

}

9. 开启服务

在这里插入图片描述

package com.example.rpc.sms;

import com.example.rpc.remote.SendSms;
import com.example.rpc.rpc.base.RpcServerFrame;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.Random;

//rpc的服务端,提供服务
@Service
public class SmsRpcServer {
    
    
    @Autowired
    private RpcServerFrame rpcServerFrame;

    @PostConstruct
    public void server() {
    
    
        Random random = new Random();
        int port = 8888 + random.nextInt(100);
        try {
    
    
            //往远程服务器注册本服务,并提供业务类
            rpcServerFrame.startService(SendSms.class.getName(), "127.0.0.1", port, SendSmsImpl.class);
        } catch (Throwable throwable) {
    
    
            throwable.printStackTrace();
        }
    }

}

创建客户端

1. 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>rpc-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rpc-client</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2. properties

在这里插入图片描述

server.port=8082

3. 启动类

在这里插入图片描述

package com.example.rpc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RpcClientApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(RpcClientApplication.class, args);
    }

}

4. 存根远端服务的类

  • 如果要想像使用自己项目中的方法一样,使用远端服务上的方法,就必须本地存根

在这里插入图片描述

  • 这里需要存根包括注册中心的实体类RegisterVO,和注册中心项目的代码一样

在这里插入图片描述

package com.example.rpc.remote.vo;

import java.io.Serializable;

//注册中心实体
public class RegisterVO implements Serializable {
    
    
    private String host;
    private int port;

    public RegisterVO(String host, int port) {
    
    
        this.host = host;
        this.port = port;
    }

    public String getHost() {
    
    
        return host;
    }

    public void setHost(String host) {
    
    
        this.host = host;
    }

    public int getPort() {
    
    
        return port;
    }

    public void setPort(int port) {
    
    
        this.port = port;
    }
}

  • 服务端的实体类UserInfo

在这里插入图片描述

package com.example.rpc.remote.vo;

import java.io.Serializable;

public class UserInfo implements Serializable {
    
    
    private String name;
    private String phone;

    public UserInfo(String name, String phone) {
    
    
        this.name = name;
        this.phone = phone;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public String getPhone() {
    
    
        return phone;
    }

    public void setPhone(String phone) {
    
    
        this.phone = phone;
    }
}

  • 服务端的发送信息的接口

在这里插入图片描述

package com.example.rpc.remote;


import com.example.rpc.remote.vo.UserInfo;

public interface SendSms {
    
    

    //发送短信
    boolean sendMsg(UserInfo userInfo);

}

  • 这里需要注意的是,本地存根的类的全限定名要和远端类的全限定名一致,不然当你通过反射在服务端去获取类的时候会找不到,比如下面这个类的全限定名在客户端和服务端都是com.example.rpc.remote.SendSms

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

5. 客户端实现

  • 通过动态代理调用远端服务的方法

在这里插入图片描述

package com.example.rpc.client.rpc;

import com.example.rpc.remote.vo.RegisterVO;
import org.springframework.stereotype.Service;

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;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;

//客户端代理部分
@Service
public class RpcClientFrame {
    
    
    //获得服务提供方的地址列表
    private static List<InetSocketAddress> getAddressList(String serviceName) throws Exception {
    
    
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress("127.0.0.1", 9001));
        try (ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
             ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
    
    
            //查询服务列表
            outputStream.writeBoolean(true);
            //发送服务名称
            outputStream.writeUTF(serviceName);
            outputStream.flush();
            Set<RegisterVO> voSet = (Set<RegisterVO>) inputStream.readObject();
            List<InetSocketAddress> services = new ArrayList<>();
            for (RegisterVO registerVO : voSet) {
    
    
                String host = registerVO.getHost();
                int port = registerVO.getPort();
                InetSocketAddress address = new InetSocketAddress(host, port);
                services.add(address);
            }
            System.out.println("查询服务列表成功:" + serviceName);
            return services;

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

    //获取的远程服务的地址
    private static InetSocketAddress getService(String serviceName) throws Exception {
    
    
        Random random = new Random();
        //获得服务提供方的地址列表
        List<InetSocketAddress> addressList = getAddressList(serviceName);
        //随机选一台
        InetSocketAddress address = addressList.get(random.nextInt(addressList.size()));
        System.out.println("本次调用选择的服务器是:" + address);
        return address;
    }

    //动态代理,实现对远程服务的访问
    private static class DynProxy implements InvocationHandler {
    
    
        private Class<?> serviceInterface;
        private InetSocketAddress socketAddress;

        public DynProxy(Class<?> serviceInterface, InetSocketAddress socketAddress) {
    
    
            this.serviceInterface = serviceInterface;
            this.socketAddress = socketAddress;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
            Socket socket = new Socket();
            socket.connect(socketAddress);
            try (ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                 ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
    
    
                //方法所在类的名字
                outputStream.writeUTF(serviceInterface.getName());
                //方法名
                outputStream.writeUTF(method.getName());
                //方法入参类型
                outputStream.writeObject(method.getParameterTypes());
                //方法入参
                outputStream.writeObject(args);
                outputStream.flush();
                //接收服务器的输出
                System.out.println(serviceInterface + "remote exec success !");
                return inputStream.readObject();
            } finally {
    
    
                if (socket != null) {
    
    
                    socket.close();
                }
            }
        }
    }

    //远程服务的代理对象,参数为客户端要调用的服务
    public static Object getRemoteProxyObject(Class serviceInterface) throws Exception {
    
    
        //获得远程服务的一个网络地址
        InetSocketAddress address = getService(serviceInterface.getName());
        //拿到一个代理对象,由代理对象通过网络进行实际的服务调用
        return Proxy.newProxyInstance(serviceInterface.getClassLoader(),
                new Class[]{
    
    serviceInterface}, new DynProxy(serviceInterface, address));
    }

}

6. 注入远端服务的bean

  • 通过@Bean把SendSms加到spring容器中,SendSms实际是一个代理对象

在这里插入图片描述

package com.example.rpc.client.config;

import com.example.rpc.client.rpc.RpcClientFrame;
import com.example.rpc.remote.SendSms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ClientConfig {
    
    

    @Autowired
    private RpcClientFrame rpcClientFrame;

    //注入远端服务的SendSms类,可以在controller中使用其中的方法
    @Bean
    public SendSms getSendSms() throws Exception {
    
    
        return (SendSms) rpcClientFrame.getRemoteProxyObject(SendSms.class);
    }
}

7. 创建Controller

  • 用于调用服务端SendSms类中发送信息的方法

在这里插入图片描述

package com.example.rpc.Controller;

import com.example.rpc.remote.SendSms;
import com.example.rpc.remote.vo.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ClientController {
    
    

    @Autowired
    private SendSms sendSms;

    @RequestMapping(value = "sendMsg")
    public void sendMsg() {
    
    
        long start = System.currentTimeMillis();
        UserInfo userInfo = new UserInfo("fisher", "123456");
        System.out.println("send message: " + sendSms.sendMsg(userInfo));
        System.out.println("共耗时:" + (System.currentTimeMillis() - start) + "ms");
    }
}

8. 启动服务,查看测试结果

  • 先启动注册中心,再启动服务端,最后启动客户端

在这里插入图片描述

  • 注册功能使用9001端口,启动服务端后,服务端的地址127.0.0.1:8940成功注册到注册中心,最后启动客户端,客户端会到注册中心查询服务列表
  • 请求http://localhost:8082/sendMsg

在这里插入图片描述

  • 客户端打印

在这里插入图片描述

  • 服务端打印

在这里插入图片描述

代码下载地址

https://gitee.com/fisher3652/netWork.git

猜你喜欢

转载自blog.csdn.net/qq_40977118/article/details/109380474
BIO