多人/終端 文件共同編輯器的實(shí)現(xiàn)-javaRMI版

一. 前言

本文探討多人在線文件編輯器的實(shí)現(xiàn)室谚,主要借助swing來(lái)進(jìn)行圖形化界面實(shí)現(xiàn)忧设。
基于java RMI進(jìn)行RPC調(diào)用和回調(diào)控制。
服務(wù)器其實(shí)是一個(gè)消息中轉(zhuǎn)站井厌。

二. 開發(fā)環(huán)境

Windows10
jdk1.8
idea2020.3

三. 具體過(guò)程

1.接口設(shè)置

1.1定義服務(wù)端接口(用于客戶端調(diào)用)

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

    /**
     * 客戶端文本改變時(shí)上載服務(wù)端進(jìn)行廣播
     * @param words
     * @param client
     * @return
     * @throws RemoteException
     */
    Boolean onTextChange(Words words,Client client) throws RemoteException;

    /**
     * 注冊(cè)客戶端
     * @param client
     * @return
     * @throws RemoteException
     */
    Boolean registerClient(Client client) throws RemoteException;

    void unregisterClient(Client client) throws RemoteException;
}

1.2定義客戶端接口(用于服務(wù)端回調(diào)蚓庭,根據(jù)注冊(cè)集合)

public interface Client extends Remote {
    /**
     * 當(dāng)文本內(nèi)容改變時(shí),服務(wù)端向客戶端廣播時(shí)調(diào)用
     * @param words
     * @throws RemoteException
     */
    public void OtherChanged(Words words) throws RemoteException;

    public void setMainWindow(MainWindow mainWindow)throws RemoteException;
}

2.定義傳輸內(nèi)容Words類

定義words類用于文本內(nèi)容傳輸旗笔,注意要implements Serializable 用于序列化

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

    //插入項(xiàng)
    private String item;

    //插入/刪除開始位置
    private int Startposition;

    //插入/刪除結(jié)束位置
    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);//設(shè)置單詞在一行不足容納時(shí)換行
        textArea.setLineWrap(true);//設(shè)置文本編輯區(qū)自動(dòng)換行默認(rèn)為true,即會(huì)"自動(dòng)換行"
        id = UUID.randomUUID().toString();

        //關(guān)鍵是下面這兩行代碼
        Document document = textArea.getDocument();
        document.addDocumentListener(this);

        contentPanel.add(jScrollPane);

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

插入事件處理蝇恶,構(gòu)造words類向服務(wù)端傳輸

    @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);
    }

調(diào)用接口實(shí)現(xiàn):

    @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.初始化服務(wù)端

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();
        }
    }

服務(wù)端接口實(shí)現(xiàn):

    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("移出無(wú)效對(duì)象");
                    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);
    }

最終效果展示:


最終界面

四. 總結(jié)與討論

遇到的坑還是挺多的,主要有以下幾點(diǎn):

1.RMI要求客戶端和服務(wù)端的遠(yuǎn)程調(diào)用接口必須一致惶桐,包括包名和方法屬性等撮弧。


客戶端

服務(wù)端

2.swing只支持單線程,在同步更新文本時(shí)發(fā)生了死鎖的現(xiàn)象姚糊,故在遠(yuǎn)程接收消息更新文本內(nèi)容時(shí)應(yīng)再開一個(gè)線程贿衍。
3.安全機(jī)制比較麻煩,需慎重考慮
4.由于java只支持單繼承救恨,所以這里將圖形界面的類放在了客戶端主類里面(客戶端主類繼承UnicastRemoteObject贸辈,圖形界面繼承JFrame)


繼承
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市肠槽,隨后出現(xiàn)的幾起案子擎淤,更是在濱河造成了極大的恐慌,老刑警劉巖秸仙,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘴拢,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡寂纪,警方通過(guò)查閱死者的電腦和手機(jī)席吴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)捞蛋,“玉大人孝冒,你說(shuō)我怎么就攤上這事∧馍迹” “怎么了庄涡?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)捣域。 經(jīng)常有香客問我啼染,道長(zhǎng)宴合,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任迹鹅,我火速辦了婚禮卦洽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘斜棚。我一直安慰自己阀蒂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布弟蚀。 她就那樣靜靜地躺著蚤霞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪义钉。 梳的紋絲不亂的頭發(fā)上昧绣,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音捶闸,去河邊找鬼夜畴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛删壮,可吹牛的內(nèi)容都是我干的贪绘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼央碟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼税灌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起亿虽,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤菱涤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后经柴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狸窘,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年坯认,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了翻擒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡牛哺,死狀恐怖陋气,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情引润,我是刑警寧澤巩趁,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響议慰,放射性物質(zhì)發(fā)生泄漏蠢古。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一别凹、第九天 我趴在偏房一處隱蔽的房頂上張望草讶。 院中可真熱鬧,春花似錦炉菲、人聲如沸堕战。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嘱丢。三九已至,卻和暖如春祠饺,著一層夾襖步出監(jiān)牢的瞬間越驻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工吠裆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伐谈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓试疙,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親抠蚣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子祝旷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容