多人/终端 文件共同编辑器的实现-javaRMI版

一. 前言

本文探讨多人在线文件编辑器的实现,主要借助swing来进行图形化界面实现。
基于java RMI进行RPC调用和回调控制。
服务器其实是一个消息中转站。

二. 开发环境

Windows10
jdk1.8
idea2020.3

三. 具体过程

1.接口设置

1.1定义服务端接口(用于客户端调用)

public interface Server extends Remote{
    String sayHello(String name) throws RemoteException;

    /**
     * 客户端文本改变时上载服务端进行广播
     * @param words
     * @param client
     * @return
     * @throws RemoteException
     */
    Boolean onTextChange(Words words,Client client) throws RemoteException;

    /**
     * 注册客户端
     * @param client
     * @return
     * @throws RemoteException
     */
    Boolean registerClient(Client client) throws RemoteException;

    void unregisterClient(Client client) throws RemoteException;
}

1.2定义客户端接口(用于服务端回调,根据注册集合)

public interface Client extends Remote {
    /**
     * 当文本内容改变时,服务端向客户端广播时调用
     * @param words
     * @throws RemoteException
     */
    public void OtherChanged(Words words) throws RemoteException;

    public void setMainWindow(MainWindow mainWindow)throws RemoteException;
}

2.定义传输内容Words类

定义words类用于文本内容传输,注意要implements Serializable 用于序列化

public class Words implements Serializable {
    
    public Words(){}
    //操作,0代表删除,1代表插入
    private int Operation;

    //插入项
    private String item;

    //插入/删除开始位置
    private int Startposition;

    //插入/删除结束位置
    private int Endposition;

    public String getItem() {
        return item;
    }

    public void setItem(String item) {
        this.item = item;
    }

    public int getStartposition() {
        return Startposition;
    }

    public void setStartposition(int startposition) {
        Startposition = startposition;
    }

    public int getEndposition() {
        return Endposition;
    }

    public void setEndposition(int endposition) {
        Endposition = endposition;
    }

    public int getOperation() {
        return Operation;
    }

    public void setOperation(int operation) {
        Operation = operation;
    }
}

3.定义MainWindow类显示主界面

public class MainWindow extends JFrame implements DocumentListener {

    private JPanel contentPanel;
    private JScrollPane jScrollPane;
    public JTextArea textArea;
    private Server stub;
    private String id;

    private Client client;

    public void init(Server stub,Client client) {
        this.client=client;
        this.stub=stub;
        textArea = new JTextArea(20, 50);
        jScrollPane = new JScrollPane(textArea);
        contentPanel = new JPanel();
        setContentPane(contentPanel);
        contentPanel.setBounds(400, 200, 500, 500);
        textArea.setWrapStyleWord(true);//设置单词在一行不足容纳时换行
        textArea.setLineWrap(true);//设置文本编辑区自动换行默认为true,即会"自动换行"
        id = UUID.randomUUID().toString();

        //关键是下面这两行代码
        Document document = textArea.getDocument();
        document.addDocumentListener(this);

        contentPanel.add(jScrollPane);

        this.setVisible(true);
        this.setSize(600, 400);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

插入事件处理,构造words类向服务端传输

    @Override
    public void insertUpdate(DocumentEvent e) {
        try {
            System.out.println(e.getDocument().getText(e.getOffset(), e.getLength()));
            Words words = new Words();
            words.setItem(e.getDocument().getText(e.getOffset(), e.getLength()).toString());
            words.setOperation(1);
            words.setStartposition(e.getOffset());
            words.setEndposition(e.getOffset());
            stub.onTextChange(words,client);
        } catch (BadLocationException | RemoteException badLocationException) {
            badLocationException.printStackTrace();
        }
    }

删除事件处理

    @Override
    public void removeUpdate(DocumentEvent e) {
        try {
            System.out.println(e.getDocument());
            Words words = new Words();
            words.setOperation(0);
            words.setStartposition(e.getOffset());
            words.setEndposition(e.getOffset() + e.getLength());
            stub.onTextChange(words, client);
        } catch (RemoteException badLocationException) {
            badLocationException.printStackTrace();
        }
    }

4.初始化客户端

public class ClientImpl extends UnicastRemoteObject implements Client, Serializable {

    public static Server stub;

    public static MainWindow mainWindow;

    private String Clientid;

    protected ClientImpl() throws RemoteException {
        super();
    }

    static private ExecutorService service = Executors.newCachedThreadPool(new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "output");
        }
    });

    public static void main(String[] args) throws RemoteException, MalformedURLException, NotBoundException, InterruptedException {
//        if(System.getSecurityManager()==null){
//            System.setSecurityManager(new SecurityManager());}
        Client clientImpl =new ClientImpl();

        String host = (args.length < 1) ? "localhost" : args[0];
        String name = (args.length == 2) ? args[1] : "World";

        String urlo = "rmi://" + host + ":3333/Server";
         stub = (Server) Naming.lookup(urlo);
        MainWindow mainWindow1=new MainWindow();
        mainWindow1.init(stub,clientImpl);
        clientImpl.setMainWindow(mainWindow1);
        System.out.println("link to the server: \n" + urlo);
        //Registry registry = LocateRegistry.getRegistry(host);
        //rmi_test.Hello stub = (rmi_test.Hello)registry.lookup("rmi_test.Hello");
        String response = stub.sayHello(name);
        System.out.println("Response: " + response);
        Boolean isok=stub.registerClient(clientImpl);
    }

调用接口实现:

    @Override
    public void OtherChanged(Words words) throws RemoteException {
        service.submit(new Runnable() {

            @Override
            public void run() {
                System.out.println("received change"+words.getItem());
                mainWindow.textArea.getDocument().removeDocumentListener(mainWindow);

                if(words.getOperation()==1) {
                    mainWindow.textArea.insert(words.getItem(), words.getStartposition());
                }
                else {
                    mainWindow.textArea.replaceRange("", words.getStartposition(), words.getEndposition());
                }
                mainWindow.textArea.getDocument().addDocumentListener(mainWindow);
            }
        });

    }

5.初始化服务端

public class ServerImpl extends UnicastRemoteObject implements Server, Serializable {
    public ServerImpl() throws RemoteException {
        super();
        client=new HashSet();
//        clientToId=new HashMap();
    }

    private HashSet client;
    
    public static void main(String args[]) throws RemoteException {
        //System.setSecurityManager(new RMISecurityManager());
        //System.setSecurityManager(new SecurityManager());
        final ServerImpl obj = new ServerImpl();
        try {                               // 0 - anonymous TCP port ↓
            //Server stub = (Server)UnicastRemoteObject.exportObject(obj, 0);
             //Bind the remote object's stub in the registry
            Registry registry = LocateRegistry.createRegistry(3333);
            registry.rebind("Server", obj);
            for(int i = 0; i < registry.list().length; i++)
                System.out.println(registry.list()[i]);
//            Naming.bind("localhost/Server",obj);
            System.err.println("Server ready....");
            System.err.println("Listinging on port 3333 ....");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

服务端接口实现:

    public Boolean onTextChange(Words words,Client client1) {
        System.out.println("text changed");
        //Words words1=new Words();
//        wordsQueue.add(words1);
        Iterator iterator=client.iterator();

        while(iterator.hasNext())
            {
                Client client =(Client) iterator.next();
                if(client.equals(client1))
                    continue;
                try{
                    client.OtherChanged(words);
                }catch (Exception e)
                {
                    System.out.println("移出无效对象");
                    e.printStackTrace();
                    iterator.remove();
                }
            }
        return true;
    }

    public Boolean registerClient(Client client) throws RemoteException {
        System.out.println("new client connected");
        this.client.add(client);
        return true;
    }

    public void unregisterClient(Client client) throws RemoteException {
        System.out.println("client disconnected");
        this.client.remove(client);
    }

最终效果展示:


最终界面

四. 总结与讨论

遇到的坑还是挺多的,主要有以下几点:

1.RMI要求客户端和服务端的远程调用接口必须一致,包括包名和方法属性等。


客户端

服务端

2.swing只支持单线程,在同步更新文本时发生了死锁的现象,故在远程接收消息更新文本内容时应再开一个线程。
3.安全机制比较麻烦,需慎重考虑
4.由于java只支持单继承,所以这里将图形界面的类放在了客户端主类里面(客户端主类继承UnicastRemoteObject,图形界面继承JFrame)


继承
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容