一. 前言
本文探討多人在線文件編輯器的實(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)用接口必須一致惶桐,包括包名和方法屬性等撮弧。
2.swing只支持單線程,在同步更新文本時(shí)發(fā)生了死鎖的現(xiàn)象姚糊,故在遠(yuǎn)程接收消息更新文本內(nèi)容時(shí)應(yīng)再開一個(gè)線程贿衍。
3.安全機(jī)制比較麻煩,需慎重考慮
4.由于java只支持單繼承救恨,所以這里將圖形界面的類放在了客戶端主類里面(客戶端主類繼承UnicastRemoteObject贸辈,圖形界面繼承JFrame)