rmi demo&调错日志


写在前面://20220526
尝试搭建rmi远程访问,记录出现的错误

java_RMI

  • 全称:Remote Method Invocation
  • RMI是一种分布式对象应用,使用它可以使一个JVM中的对象,调用另一个JVM中的对象方法并获取调用结果(这里的另一个JVM可以在同一台计算机也可以是远程计算机),因此RMI以为只需要一个Server端和一个Client端
  • Server端通常会创建一个对象,并使之可以被远程访问

工作原理:

  • 从图中可以看到,Client 端有一个被称 Stub 的东西,有时也会被成为存根,它是 RMI Client 的代理对象,Stub 的主要功能是请求远程方法时构造一个信息块,RMI 协议会把这个信息块发送给 Server 端。

  • 从图中可以看到,Client 端有一个被称 Stub 的东西,有时也会被成为存根,它是 RMI Client 的代理对象,Stub 的主要功能是请求远程方法时构造一个信息块,RMI 协议会把这个信息块发送给 Server 端。

  • 这个信息块由几个部分组成:

    • 远程对象标识符。
    • 调用的方法描述。
    • 编组后的参数值(RMI协议中使用的是对象序列化)。
  • 既然 Client 端有一个 Stub 可以构造信息块发送给 Server 端,那么 Server 端必定会有一个接收这个信息快的对象,称为 Skeleton 。

  • 它主要的工作是:

    • 解析信息快中的调用对象标识符和方法描述,在 Server 端调用具体的对象方法。
    • 取得调用的返回值或者异常值。
    • 把返回值进行编组,返回给客户端 Stub.
  • 到这里,一次从 Client 端对 Server 端的调用结果就可以获取到了。

RMI开发

  • 通过上面的介绍,知道了 RMI 的概念以及 RMI 的工作原理,下面介绍 RMI 的开发流程。

  • 这里会通过一个场景进行演示,假设 Client 端需要查询用户信息,而用户信息存在于 Server 端,所以在 Server 端开放了 RMI 协议接口供客户端调用查询。

RMI Server

Server 端主要是构建一个可以被传输的类 User,一个可以被远程访问的类 UserService,同时这个对象要注册到 RMI 开放给客户端使用。

1.定义服务器接口(需要继承 Remote 类,方法需要抛出 RemoteException)。

package com.wdbyte.rmi.server;

import java.rmi.Remote;
import java.rmi.RemoteException;


/**
 * RMI Server
 *
 * @author www.wdbyte.com
 * @date 2021/05/08
 */
public interface UserService extends Remote {

    /**
     * 查找用户
     * 
     * @param userId
     * @return
     * @throws RemoteException
     */
    User findUser(String userId) throws RemoteException;
}

User 对象在步骤 3 中定义。

2.实现服务器接口(需要继承 UnicastRemoteObject 类,实现定义的接口)。

package com.wdbyte.rmi.server;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

/**
 * @author www.wdbyte.com
 * @date 2021/05/08
 */
public class UserServiceImpl extends UnicastRemoteObject implements UserService {

    protected UserServiceImpl() throws RemoteException {
    }

    @Override
    public User findUser(String userId) throws RemoteException {
        // 加载在查询
         if ("00001".equals(userId)) {
            User user = new User();
            user.setName("金庸");
            user.setAge(100);
            user.setSkill("写作");
            return user;
        }
        throw new RemoteException("查无此人");
    }
}

3.定义传输的对象,传输的对象需要实现序列化(Serializable)接口。

需要传输的类一定要实现序列化接口,不然传输时会报错。IDEA 中如何生成 serialVersionUID,在文章末尾也附上了简单教程。

package com.wdbyte.rmi.server;

import java.io.Serializable;

/**
 *
 * @author www.wdbyte.com
 * @date 2021/05/08
 */
public class User implements Serializable {

    private static final long serialVersionUID = 6490921832856589236L;

    private String name;
    private Integer age;
    private String skill;

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getSkill() {
        return skill;
    }

    public void setSkill(String skill) {
        this.skill = skill;
    }
    
    @Override
    public String toString() {
        return "User{" +
            "name='" + name + ''' +
            ", age=" + age +
            ", skill='" + skill + ''' +
            '}';
    }
}

4.注册( rmiregistry)远程对象,并启动服务端程序。

服务端绑定了 UserService 对象作为远程访问的对象,启动时端口设置为 1900。

package com.wdbyte.rmi.server;

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

/**
 * RMI Server 端
 *
 * @author https://www.wdbyte.com
 * @date 2021/05/08
 */
public class RmiServer {

    public static void main(String[] args) {
        try {
            UserService userService = new UserServiceImpl();
            LocateRegistry.createRegistry(1900);
            Naming.rebind("rmi://localhost:1900/user", userService);
            System.out.println("start server,port is 1900");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

RMI Client

相比 Server 端,Client 端就简单的多。直接引入可远程访问和需要传输的类,通过端口和 Server 端绑定的地址,就可以发起一次调用。

package com.wdbyte.rmi.client;

import java.rmi.Naming;

import com.wdbyte.rmi.server.User;
import com.wdbyte.rmi.server.UserService;

/**
 * @author https://www.wdbyte.com
 * @date 2021/05/08
 */
public class RmiClient {
    public static void main(String args[]) {
        User answer;
        String userId = "00001";
        try {
            // lookup method to find reference of remote object
            UserService access = (UserService)Naming.lookup("rmi://localhost:1900/user");
            answer = access.findUser(userId);
            System.out.println("query:" + userId);
            System.out.println("result:" + answer);
        } catch (Exception ae) {
            System.out.println(ae);
        }
    }
}

RMI 测试

启动 Server 端。

start server,port is 1900

启动 Client 端。

query:00001
result:User{name='金庸', age=100, skill='写作'}

如果 Client 端传入不存在的 userId。

java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: 
	java.rmi.RemoteException: 查无此人

调错日志:

  • 放置服务端和客户端的主机互相都可ping通(否则会无法访问,哪怕client可以访问到server而server不能访问到client都不行——因该是二者之间需要通信,所以需要互相可访问)
  • server放置远端的时候需要增加配置——本地主机地址的配置System.setProperty("java.server.hostname","服务端ip地址"),否则服务端只有本地能访问到
  • server所在主机需要开放相应端口,否则访问不到
  • javac可以一次性编译多个文件(使用通配符即可实现),如果文件之间有互相引用,则必须同时编译,单个编译会报错
  • 如果java文件内有使用package关键字指定包,则在使用java执行的时候需要在包对应文件夹那个目录下,否则会报出“无法加载主类异常”
  • vim撤回操作是u

参考博客

[1]https://blog.csdn.net/JianguoChow/article/details/80425323

[2]https://blog.csdn.net/qq_41602468/article/details/117932520

[3]https://blog.csdn.net/hong201/article/details/3954603

[4]https://blog.csdn.net/qq_36938617/article/details/95234909

[5]https://blog.csdn.net/qq_41729780/article/details/105712527

[6]https://www.w3cschool.cn/article/30445887.html