Java学习笔记 2:RMI建立一个分布式消息系统

69 阅读4分钟

使用RMI建立一个分布式消息系统。该系统需实现用户注册、查看所有已注册用户信息、给其他用户留言,以及查看其它用户给自己的留言。包括 register()showusers()checkmessages()leavemessage() 方法。

这个思路其实和 RMI 分布式议程服务一样,只不过换了个要实现的功能而已。在这里我们仍然是定义一个接口用来定义操作对象的方法,实现一个客户端获取远程对象,然后再在服务器端实现远程对象并注册。

HelloInterface 接口

这里的 HelloInterface 添加了一个 echo() 方法,用来在客户端输出当前可以执行的命令作为提示。

public interface HelloInterface extends Remote {

    /** 所有方法必须抛出RemoteException */
    public String echo(String msg) throws RemoteException;

    /**
     * 注册用户
     *
     * @param username 用户名
     * @param password 密码
     * @return 注册是否成功
     * @throws RemoteException RemoteException
     */
    public String register(String username, String password) throws RemoteException;

    /**
     * 显示所有注册用户
     *
     * @return 所有注册用户名列表
     * @throws RemoteException RemoteException
     */
    public String showusers() throws RemoteException;

    /**
     * 显示用户所有留言
     *
     * @param username 用户名
     * @param password 密码
     * @return 用户所有留言列表
     * @throws RemoteException RemoteException
     */
    public String checkmessages(String username, String password) throws RemoteException;

    /**
     * 留言
     *
     * @param username     用户名
     * @param password     密码
     * @param receiverName 接收者
     * @param messageText  留言信息
     * @return 留言返回消息
     * @throws RemoteException RemoteException
     */
    public String leavemessage(String username, String password, String receiverName, String messageText) 
    throws RemoteException;
}

Bean 数据封装

这里用 Message 类和 User 类分别表示消息系统里面的两种数据对象。要注意的是和之前一样,对象要实现序列化的接口。

public class Message implements Serializable {
    /** 评论者的名字 */
    private String commenter;
    /** 评论的日期 */
    private Date commentDate;
    /** 评论的内容 */
    private String contents;

    /**
     * 构造函数初始化Message对象
     */
    public Message(String commenter, Date commentDate, String contents) {
        super();
        this.commenter = commenter;
        this.commentDate = commentDate;
        this.contents = contents;
    }

    ... 各种方法

}
public class User implements Serializable {
    /** 用户名 */
    private String name;
    /** 密码 */
    private String password;
    /** 用户的消息列表 */
    private List<Message> messageList = new ArrayList<Message>();

    /**
     * 构造函数初始化User对象,有用户名、密码、消息列表三个属性
     *
     * @param name        用户名
     * @param password    密码
     * @param messageList 消息列表
     */
    public User(String name, String password, List<Message> messageList) {
        super();
        this.name = name;
        this.password = password;
        this.messageList = messageList;
    }

    /**
     * 构造函数初始化User对象,有用户名、密码两个属性
     *
     * @param name     用户名
     * @param password 密码
     */
    public User(String name, String password) {
        super();
        this.name = name;
        this.password = password;
    }

    /**
     * 构造函数初始化User对象,有用户名一个属性
     *
     * @param name 用户名
     */
    public User(String name) {
        super();
        this.name = name;
    }

    ... 各种方法

}

HelloClient RMI 客户端

RMI 客户端也和之前实现的 RMI 客户端类似,不过在获取远程对象之前添加了对于客户端、服务器端以及端口号是否错误的判断。只有在输入正确的情况下才会去请求远程对象。

if (!clientName.equals("HelloClient")) {
    System.out.println("客户端错误");
} else if (!serverName.equals("localhost")) {
    System.out.println("服务器错误");
} else if (portNumber != 1099) {
    System.out.println("端口号错误");
} else {

    // 通过查找获得远程对象
    HelloInterface hello = (HelloInterface) Naming.lookup("Hello");

    // 调用远程方法
    System.out.println(hello.echo("good morning"));

    // 存储用户输入信息
    String info = null;
    while ((info = in.readLine()) != null) {

        ...远程对象的具体操作

    }
}

Hello 远程对象实现

public class Hello extends UnicastRemoteObject implements HelloInterface {

    /**
     * 服务器和客户端这个字段必须保持一致才能进行反序列化
     * JAVA序列化的机制是通过判断类的serialVersionUID来验证的版本一致的
     * 在进行反序列化时JVM会把传来的字节流中的serialVersionUID于本地相应实体类的serialVersionUID进行比较
     * 如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常
     */
    private static final long serialVersionUID = 1L;

    /** 定义状态 */
    private String status = "0000";

    /** 定义用户列表 */
    private List<User> userList = new ArrayList<User>();

    /**
     * 必须定义构造方法,即使是默认构造方法,也必须把它明确地写出来,因为它必须抛出出RemoteException异常
     */
    public Hello() throws RemoteException {

    }

    /** 远程接口方法的实现 */

    ...

}

HelloServer RMI 服务器端

public class HelloServer {
    /**
     * 启动 RMI 注册服务并进行对象注册
     */
    public static void main(String[] args) {
        try {

            // 启动RMI注册服务,指定端口为1099 (1099为默认端口)
            // 注册远程对象,向客户端提供远程对象服务。
            // 远程对象是在远程服务上创建的,无法确切地知道远程服务器上的对象的名称,
            // 但是,将远程对象注册到RMI Registry之后,
            // 客户端就可以通过RMI Registry请求到该远程服务对象的stub,
            // 利用stub代理就可以访问远程服务对象了。
            LocateRegistry.createRegistry(1099);

            // 创建远程对象的一个或多个实例,下面是hello对象
            // 可以用不同名字注册不同的实例
            HelloInterface hello = new Hello();

            // 把hello注册到RMI注册服务器上,命名为Hello
            // 如果要把hello实例注册到另一台启动了RMI注册服务的机器上,Naming.rebind("//192.168.1.105:1099/Hello",hello)
            Naming.rebind("Hello", hello);

            // 打印服务器就绪信息
            System.out.println("Hello Server is ready.");

        } catch (Exception e) {
            System.out.println("Hello Server failed: " + e);
        }
    }
}

基本上结束啦,用 RMI 实现分布式消息系统,这里写得很简略,具体更多的细节可以参考 RMI 分布式议程服务。