使用Java/CORBA实现分布应用编程

EXPT 3:使用Java/CORBA实现分布应用编程

实验要求

Build a distributed to-do list using Java and CORBA. Various clients should be able to connect to a server managing various to-do list objects to query list items, add list items, delete list items, and clear to-do lists. The server should also have management functionality that allows member registration. Each to-do list item has a description, and a start time and an end time in which users must complete their items. Users should be able to add any number of items to their to-do lists.

使用Java与CORBA完成一个TodoList应用,应用需要涵盖的功能有:

  • 用户管理:包括用户注册、登录
  • 用户操作:包括添加事项、删除事项、展示清单、清除清单等功能

To complete this assignment, you will need to manage multiple CORBA objects using the CORBA naming service. For each user that successfully registers, you must create a new to-do list and add it to the CORBA naming service. To that end, you will need to write a to-do list creator that creates to-do lists and adds them to the CORBA naming system. The to-do list creator must be also be accessible using the CORBA naming service. Therefore, if there are n to-do list system users, CORBA must manage n+1 CORBA objects — one to-do list object for each user plus the creator object. You will need to perform the following tasks to complete this assignment.

具体的实现细节要求为:若系统中共有n个用户,则建立的CORBA对象应有n+1个。其中,1个为Creator服务,剩余的n个服务分别对应着n个用户。

方案设计

在CORBA应用中,主要涉及到的模块有三个:客户端、服务器、NameService服务器。

客户端-服务器

Client与Servant进行通信时,都是通过ORB对象来实现的。方法的调用与返回,通过IIOP协议(应用层协议,其底层协议支持为TCP/IP)进行。

服务器-NameService服务器

服务器的多个CORBA服务对象,都需要在NameService服务器注册后才能进一步被调用。

客户端-服务器-NameService

三个模块的交互如下图所示:

  1. 服务器通过NameService服务器进行服务注册;
  2. 客户端查询NameService服务器中已注册的服务,并得到相应的服务对象引用;
  3. 客户端根据服务对象引用,在IIOP协议上与服务器进行交互。

时序图

1

整个CORBA应用的时序图如上所示。

具体实现

项目结构如上图所示,TodoListUserCreator模块通过.idl接口文件自动生成。

IDL接口及实现

UserCreator.idl
module UserCreator{
    interface Creator{
        boolean login(in string username, in string password);
        boolean register(in string username, in string password);
    };
};

UserCreator的模块结构如下:

CreatorServantImpl
package UserCreator;

import app.AppServant;

import java.util.Hashtable;

/**
 * Created by snow on 2018/5/17.
 */
public class CreatorServantImpl extends CreatorPOA {
    private Hashtable<String, String> users = new Hashtable<String, String>();

    public boolean login(String username, String password) {
       if (!users.containsKey(username)){
           System.err.println("该用户名不存在!");
           return false;
       }else if (!users.get(username).equals(password)){
           System.err.println("密码错误!");
           return false;
       }
       return true;
    }

    public boolean register(String username, String password) {
        if (users.containsKey(username)) {
            System.err.println("该用户名已注册!");
            return false;
        }

        //create a CORBA object
        users.put(username, password);
        AppServant.createNameServceOfUser(username);

        System.out.println("注册成功,已成功登入该账户!");
        return true;
    }
}
TodoList.idl
module TodoList{
    interface UserOperation{
        boolean add(in string startTime, in string endTime, in string description);
        string query(in string startTime, in string endTime);
        boolean delete(in string key);
        boolean clear();
        string show();
    };
};

TodoList的模块结构如下:

UserOperationServantImpl
package TodoList;

import bean.Item;

import java.util.ArrayList;
import java.util.Date;

/**
 * Created by snow on 2018/5/17.
 */
public class UserOperationServantImpl extends UserOperationPOA {
    private ArrayList<Item> todoList = new ArrayList<Item>();

    public boolean add(String startTime, String endTime, String description) {
        Item item = new Item(startTime, endTime, description);
        todoList.add(item);
        return true;
    }

    public String query(String startTime, String endTime) {
        String result = "";

        String boarder = "************************************************************";
        System.out.println(boarder);
        String head = "任务序号\t起始时间\t截止时间\t任务描述";
        System.out.println(head);
        result += boarder + "\n" + head + "\n";

        for (int i = 0; i < todoList.size(); i++) {
            Item item = todoList.get(i);
            if (item.getStartTime().after(new Date(startTime))
                    && item.getEndTime().before(new Date(endTime))) {
                String task = i + "\t" + item.getStartTime() + "\t" + item.getEndTime() + "\t" + item.getDescription();
                System.out.println(task);
                result += task + "\n";
            }

        }
        System.out.println(boarder);
        result += boarder;
        return result;
    }

    public boolean delete(String key) {
        try {
            int num = Integer.getInteger(key);
            todoList.remove(num);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean clear() {
        todoList = new ArrayList<Item>();
        return true;
    }

    public String show() {
        String result = "";

        String boarder = "************************************************************";
        System.out.println(boarder);
        String head = "任务序号\t起始时间\t截止时间\t任务描述";
        System.out.println(head);
        result += boarder + "\n" + head + "\n";

        for (int i = 0; i < todoList.size(); i++) {
            Item item = todoList.get(i);
            String task = i + "\t" + item.getStartTime() + "\t" + item.getEndTime() + "\t" + item.getDescription();
            System.out.println(task);
            result += task + "\n";
        }

        System.out.println(boarder);
        result += boarder;
        return result;
    }
}

服务器

package app;

import TodoList.UserOperation;
import TodoList.UserOperationHelper;
import TodoList.UserOperationServantImpl;
import UserCreator.Creator;
import UserCreator.CreatorHelper;
import UserCreator.CreatorServantImpl;
import org.omg.CORBA.ORB;
import org.omg.CosNaming.NameComponent;
import org.omg.CosNaming.NamingContextExt;
import org.omg.CosNaming.NamingContextExtHelper;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAHelper;

import java.util.Properties;

/**
 * Created by snow on 2018/5/8.
 */
public class AppServant {
    public static final String NAME_SERVICE_CREATOR = "Creator";

    static ORB orb;
    static org.omg.CORBA.Object obj;
    static org.omg.CORBA.Object ref;
    static POA rootPOA;
    static org.omg.CORBA.Object objRef;
    static NamingContextExt ncRef;

    public static void main(String[] args) {
        try {
            Properties properties = new Properties();

            /* 配置NameService服务器地址 */

            properties.put("org.omg.CORBA.ORBInitialHost", "127.0.0.1");
            properties.put("org.omg.CORBA.ORBInitialPort", "8080");
            orb = ORB.init(args, properties);

            /* 启动Servant */

            obj = orb.resolve_initial_references("RootPOA");
            rootPOA = POAHelper.narrow(obj);
            rootPOA.the_POAManager().activate();

            /* 服务注册与绑定 */

            CreatorServantImpl creatorImpl = new CreatorServantImpl();
            ref = rootPOA.servant_to_reference(creatorImpl);
            Creator creatorRef = CreatorHelper.narrow(ref);

            objRef = orb.resolve_initial_references("NameService");
            ncRef = NamingContextExtHelper.narrow(objRef);

            NameComponent path[] = ncRef.to_name(NAME_SERVICE_CREATOR);
            ncRef.rebind(path, creatorRef);

            System.out.println("server.ToDoListServer is ready and waiting....");

            /* 服务启动 */

            orb.run();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据用户名新建一个服务对象
     *
     * @param username
     */
    public static void createNameServceOfUser(String username) {
        try {
            /* 服务注册与绑定 */

            UserOperationServantImpl userImpl = new UserOperationServantImpl();
            ref = rootPOA.servant_to_reference(userImpl);
            UserOperation userRef = UserOperationHelper.narrow(ref);

            NameComponent path[] = ncRef.to_name(username);
            ncRef.rebind(path, userRef);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

//orbd -ORBInitialPort 8080 -ORBInitialHost 127.0.0.1

客户端

package app;

import TodoList.UserOperation;
import TodoList.UserOperationHelper;
import UserCreator.Creator;
import UserCreator.CreatorHelper;
import org.omg.CORBA.ORB;
import org.omg.CORBA.ORBPackage.InvalidName;
import org.omg.CosNaming.NamingContextExt;
import org.omg.CosNaming.NamingContextExtHelper;
import org.omg.CosNaming.NamingContextPackage.CannotProceed;
import org.omg.CosNaming.NamingContextPackage.NotFound;

import java.util.Properties;
import java.util.Scanner;

/**
 * Created by snow on 2018/5/8.
 */
public class AppClient {
    private static Creator creatorOfServant;
    private static UserOperation user;
    private static Scanner scanner = new Scanner(System.in);

    static NamingContextExt ncRef;

    public static void main(String[] args) {
        connectToServant(args);
        if (creatorOfServant == null) {
            System.err.println("Something wrong in connect to servant.");
            return;
        }

        try {
            while (true) {
                System.out.println();
                System.out.println("-----------请选择下列操作-----------");
                System.out.println("1. 注册账户");
                System.out.println("2. 登录账户");
                System.out.println("3. 退出");
                System.out.println("----------------------------------");
                String choice = scanner.next();

                if (Integer.parseInt(choice) == 1)
                    register();
                else if (Integer.parseInt(choice) == 2)
                    login();
                else if (Integer.parseInt(choice) == 3)
                    return;
                else
                    System.out.println("请输入1-3中的选项");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端与服务器连接
     *
     * @param args
     */
    public static void connectToServant(String[] args) {

        /* 配置NameService服务器地址 */

        Properties properties = new Properties();
        properties.put("org.omg.CORBA.ORBInitialHost", "127.0.0.1");
        properties.put("org.omg.CORBA.ORBInitialPort", "8080");
        ORB orb = ORB.init(args, properties);

        /* 获取Servant的引用 */

        try {
            org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
            ncRef = NamingContextExtHelper.narrow(objRef);

            try {
                creatorOfServant = CreatorHelper.narrow(ncRef.resolve_str(AppServant.NAME_SERVICE_CREATOR));
            } catch (NotFound e) {
                e.printStackTrace();
            } catch (CannotProceed e) {
                e.printStackTrace();
            } catch (org.omg.CosNaming.NamingContextPackage.InvalidName e) {
                e.printStackTrace();
            }
        } catch (InvalidName e) {
            e.printStackTrace();
        }
    }

    private static void register() {
        System.out.printf("请输入用户名:");
        String username = scanner.next();
        System.out.printf("请输入密码:");
        String password = scanner.next();

        /* servant */
        if (creatorOfServant.register(username, password)) {
            userOperation(username);
            return;
        }
        System.err.println("该用户名已注册!");
    }

    private static void login() {
        System.out.printf("请输入用户名:");
        String username = scanner.next();
        System.out.printf("请输入密码:");
        String password = scanner.next();

        /* servant */
        if (creatorOfServant.login(username, password)) {
            userOperation(username);
            return;
        }
        System.err.println("该用户名不存在,或密码错误!");

    }

    private static void userOperation(String username) {
        try {
            /* 获取Servant的引用 */
            user = UserOperationHelper.narrow(ncRef.resolve_str(username));
        } catch (NotFound e) {
            e.printStackTrace();
        } catch (CannotProceed e) {
            e.printStackTrace();
        } catch (org.omg.CosNaming.NamingContextPackage.InvalidName e) {
            e.printStackTrace();
        }

        System.out.println();
        System.out.println("欢迎您," + username);
        System.out.println();

        while (true) {
            System.out.println();
            System.out.println("-----------请选择下列操作-----------");
            System.out.println("1. 添加事项");
            System.out.println("2. 查询事项");
            System.out.println("3. 删除事项");
            System.out.println("4. 展示清单");
            System.out.println("5. 清除清单");
            System.out.println("6. 返回登录主页");
            System.out.println("----------------------------------");

            try {
                int choice = scanner.nextInt();
                String startTime, endTime, description;
                switch (choice) {
                    case 1:
                        System.out.printf("起始时间:");
                        startTime = scanner.next();
                        System.out.printf("截止时间:");
                        endTime = scanner.next();
                        System.out.printf("任务说明:");
                        description = scanner.next();
                        user.add(startTime, endTime, description);
                        break;
                    case 2:
                        System.out.println("请输入查询的时间范围:");
                        System.out.printf("\t起始时间:");
                        startTime = scanner.next();
                        System.out.printf("\t截止时间:");
                        endTime = scanner.next();
                        user.query(startTime, endTime);
                        break;
                    case 3:
                        System.out.println("当前的任务清单如下:");
                        user.show();
                        System.out.printf("请输入所要删除的任务序号:");
                        String key = scanner.next();
                        user.delete(key);
                        break;
                    case 4:
                        System.out.println("当前的任务清单如下:");
                        System.out.println(user.show());
                        break;
                    case 5:
                        System.out.println("您确定要清空所有任务吗?(请输入Y/N)");
                        String s = scanner.next();
                        if (s.equals("Y")) user.clear();
                        break;
                    case 6:
                        return;
                    default:
                        System.out.println("请输入1-6中的选项");
                        break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

实验结果

首先运行orbd -ORBInitialPort 8080 -ORBInitialHost 127.0.0.1命令,启动NameService服务器:

之后再分别运行AppServantAppClient,可以看到AppClient的界面如预期所示:

项目分析

  • CORBA应用在运行过程中,需要客户端、服务器、NameService服务器这三者的交互。在此项目中,三者都由同一台物理机充当。
  • .idl.java文件的自动映射中,生成的接口文件[NAME]Operations.java定义了服务器对象的操作。实现了该接口的类有:
    • 通过idl -fall [FILENAME].idl命令自动生成的_[NAME]Stub.java,它定义了客户端与服务器进行通信时的操作。
    • 我们自定义的实现类[NAME]ServantImpl.java,它定义了服务器的业务逻辑。
  • 项目运行时,客户端和服务器应分别运行,且它们均对应各自的控制台输出。