【手写一个RPC框架】序章 simpleRPC-01

序章

RPC(RemoteProcedureCall)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。

在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

在本模块中,我们试图实现client通过指定id,通过RPC框架,从server拿到id对应的User对象信息。

本项目实现参考自MyRPCFromZero

环境

JDK8

IntelliJ IDEA 2021.2.3 (Community Edition)

实现

项目创建

先new一个project 在这里插入图片描述

创建一个maven工程

在这里插入图片描述 命名为SimpleRPC

在这里插入图片描述

创建

在这里插入图片描述

右键SimpleRPC文件,再new 一个 module,方便管理:

在这里插入图片描述

在这里插入图片描述 命名为simpleRPC-01

在这里插入图片描述 这个module simpleRPC-01就是我们这次要编写的位置。

simpleRPC-01将会初步实现一个极简单的RPC调用,之后还会有其他module:simpleRPC-02,simpleRPC-03,simpleRPC-04 ....

我们将会一步步改进我们的RPC。

在本模块中,我们试图实现一个简单地调用:client通过给定一个id,通过RPC框架,从server拿到id对应的User对象信息。

在project的java目录下,创建package

在这里插入图片描述

名为com.rpc

在这里插入图片描述

依赖配置

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SimpleRPC</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <artifactId>simpleRPC-01</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>
复制代码

common

创建一个package common,然后再common中创建User对象:

在这里插入图片描述

package com.rpc.common;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;


/**
 * @author zwy
 *
 * 定义简单User信息,要使用lombok,IDEA必须也安装lombok插件,否则用不了。
 *
 * '@Builder' 创建者模式又叫建造者模式。简单来说,就是一步步创建一个对象,它对用户屏蔽了里面构建的细节,但却可以精细地控制对象的构造过程。
 * '@NoArgsConstructor' 生成一个无参构造方法
 * '@AllArgsConstructor' 使用后添加一个构造函数,该构造函数含有所有已声明字段属性参数
 * '@Data' 相当于 @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode这5个注解的合集。
 */
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    // 客户端和服务端共有的
    private Integer id;
    private String userName;
    private Boolean sex;
}

复制代码

service

创建package service,创建UserService接口和UserSercviceImpl的实现类:

在这里插入图片描述

UserService接口

package com.rpc.service;


import com.rpc.common.User;

/**
 * @author zwy
 *
 * 服务器端提供服务的方法的接口
 */
public interface UserService {
    // 客户端通过这个接口调用服务器端的实现类
    User getUserByUserId(Integer id);
}

复制代码

UserServiceImpl.java

package com.rpc.service;


import com.rpc.common.User;

import java.util.Random;
import java.util.UUID;


/**
 * @author zwy
 *
 * 服务器端提供服务的方法
 * UserServiceImpl服务:接收一个id,返回一个User对象,
 * 提供属于这个ID(Integer)的User,User中包含他的ID(Integer),
 * 名字Name(String)和性别sex(Boolean)。
 *
 * lombok.Builder构造器构造方式详情可见:https://blog.csdn.net/weixin_41540822/article/details/86606562
 * 
 * 这里举个例子,构造格式为:目标类.builder()....build():比如
 * User.builder().id(id).build();
 * 则实际上是给User构造了:
 *         public User.UserBuilder id(int id) {
 *             this.id = id;
 *             return this;
 *         }
 */
public class UserServiceImpl implements UserService {

    @Override
    public User getUserByUserId(Integer id) {
        System.out.println("客户端查询了ID:" + id + "的用户");

        // 模拟数据库中取用户的行为
        Random random = new Random();
        User user = User.builder()
                .userName(UUID.randomUUID().toString())
                .id(id)
                .sex(random.nextBoolean()).build();  // 随机给User对象赋值
        return user;  // 返回User对象
    }
}

复制代码

server

创建package server,创建RPCServer.java

在这里插入图片描述

RPCServer.java:

package com.rpc.server;

import com.rpc.common.User;
import com.rpc.service.UserServiceImpl;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;


/**
 * @author zwy
 *
 * RPC服务器端
 * 服务器端:创建ServerSocket对象监听客户端的连接(BIO),监听到连接之后,开启一个线程来处理,socket对象的获取输入
 * 输出流作为targat,初始化输入输出流,读取从客户端传过来的id,用输入流的readInt()读取出来,
 * 调用getUserByUserId,给User赋值之后返回User对象,把Client想要的User对象通过输出流返回给客户端Client,
 * 输出流刷新(flush)。
 *
 * ServerSocket:用于服务器端,监听客户端连接
 * ServerSocket.accept():是一个阻塞方法,方法从连接请求队列中取出一个客户的连接请求,然后创建与客户连接的Socket对象,
 *                       并将它返回。如果队列中没有连接请求,accept()方法就会一直等待,直到接收到了连接请求才返回。
 * java.io.ObjectInputStream.readInt():方法读取一个32位的int
 * java.io.ObjectOutputStream.writeObject(Object obj): 此方法将指定的对象写入ObjectOutputStream。该对象的类,类的签名,
 *                                                     以及类及其所有超类型的非瞬态和非静态字段的值被写入。
 */
public class RPCServer {

    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        
        try {
            // 创建ServerSocket对象,端口号要和Client一致
            ServerSocket serverSocket = new ServerSocket(8899);
            System.out.println("服务器启动!");

            // BIO的方式监听Socket,监听到之后返回Socket对象
            while (true) {
                Socket socket = serverSocket.accept();

                // 监听到连接之后,开启一个线程来处理
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // socket对象的获取输入输出流作为targat,初始化输入输出流
                            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());

                            // 读取从客户端传过来的id,用readInt读取出来,调用getUserByUserId,给User赋值之后返回User对象
                            Integer id = ois.readInt();
                            User userByUserId = userService.getUserByUserId(id);  // 这!就是Client想要Server调用的方法!
                            // 把Client想要的User对象返回给客户端Client
                            oos.writeObject(userByUserId);
                            oos.flush();
                        } catch (IOException e) {
                            e.printStackTrace();
                            System.out.println("从IO中读取数据错误");
                        }
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("服务器启动失败");
        }
    }
}

复制代码

client

创建package client,创建RPCClient.java

在这里插入图片描述

RPCClient.java:

package com.rpc.client;

import com.rpc.common.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Random;

/**
 * @author zwy
 *
 * RPC客户端:调用服务器端的方法
 * 客户端建立socket连接,标定主机ip地址,指定程序使用的端口号,
 * 将socket作为输入输出流的target来创建输入输出流对象,
 * 客户端通过输出流传id给服务器,刷新流。
 * 客户端通过输入流获取服务器的的返回对象,打印。
 *
 * host:主机名,用于回送地址。主机名对应的IP地址,可以和别人通信(一个主机是一栋楼,这栋楼的名字是这个主机的ip地址)
 * port:端口号(一栋楼有很多个房间可以使用,这就是端口。一个程序就是一个人,如果要跟另外一个主机通信,
 *              需要开一个房间给他的程序使用)
 * Socket.getInputStream():方法得到一个输入流,客户端的Socket对象上的getInputStream()方法
 *                          得到的输入流其实就是从服务器端发回的数据流。
 * Socket.GetOutputStream():方法得到一个输出流,客户端Socket对象上的getOutputStream()方法
 *                           返回的输出流,就是将要发送到服务器端的数据流(其实是一个缓冲区,暂时存储将要发送过去的数据)。
 * java.io.ObjectOutputStream.flush():此方法刷新流。这将写入所有缓冲的输出字节并刷新到基础流。
 * java.io.ObjectInputStream.readObject():方法从ObjectInputStream中读取对象。读取该对象的类,类签名,类及其所有超类型的
 *                                         非瞬态和非静态字段的值。默认的反序列化的类可以使用writeObject和readObject方法被重写。
 *                                          由此对象引用的对象被传递地读,这样对象的完全等价的图形是由readObject重建。
 */
public class RPCClient {

    public static void main(String[] args) {
        try {
            // 建立socket连接,标定主机ip地址,指定程序使用的端口号
            Socket socket = new Socket("127.0.0.1", 8899);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            // 传id给服务器
            objectOutputStream.writeInt(new Random().nextInt());
            objectOutputStream.flush();
            // 服务器查询数据,返回对应的对象,输入流读取对象,打印返回的user
            User user = (User) objectInputStream.readObject();
            System.out.println("返回的User: " +  user);
        }
        catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            System.out.println("客户端启动失败");
        }
    }
}

复制代码

文件结构

到此我们simpleRPC-01的所有文件结构如下:

在这里插入图片描述

运行

先启动RPCServer.java:

在这里插入图片描述

然后启动RPCClient.java

在这里插入图片描述 成功~

猜你喜欢

转载自juejin.im/post/7042963860130791437