小喵的嘮叨話:寫這篇博客的初衷是因為看到了室友電腦面試的時候,面試官要求在線寫代碼。然后就想到,如果兩個人能夠在同一個頁面進行編輯工作,不就能更方便的調試代碼了嗎?(PS.懂linux的screen或tmux的可以繞道了。)代碼十分簡單,在一個月前就寫完了,只是一直沒有時間寫博客說明一下。
!請不要用這個編輯特別大的文件!
!小喵的服務器很菜雞的!!!!/(ㄒoㄒ)/~~
原博客地址:http://www.miaoerduo.com/nodejs/小喵的在線共享編輯器.html
心急的同學可以在 http://www.editor.miaoerduo.com/?doc=demo 先預覽一下效果。打開頁面兩次,進行編輯工作時會發現兩邊的頁面做出了同樣的修改。
github:https://github.com/miaoerduo/shared-editor 歡迎fork和star。
那么,實現一個這樣的在線的共享編輯器需要哪些工作呢?我們下面一點一點的說明。
一、寫在前面
熟悉Linux的同學都知道screen和tmux這兩個工具。通常我們可以使用他們來執行一些長時間的任務,也可以使用他們的共享終端的功能。在結對編程中,這是很有效的一個工具。
本文要實現的,是和上述兩個工具類似的共享編輯器。要說優點的話,可能就是更親民一些,打開網頁就能使用。很適合遠程幫女票看個代碼啥的(好像很多公司里面會截斷websocket,這樣就沒法用了)。
實現一個這樣的編輯器,主要有兩個部分。編輯器和同步數據的服務端。下面我們依次介紹。
二、在線編輯器
首先,我們需要一個好看的編輯器。調研了一下,找到了ACE這個編輯器,網址是 https://ace.c9.io ,簡單的了解了一下這個編輯器,發現居然連Github用的都是這個編輯器!看來我們選擇這個編輯器是沒錯的啦。
使用起來也異常的簡單,官方的Demo如下:
<!DOCTYPE html> <html lang="en"> <head> <title>ACE in Action</title> <style type="text/css" media="screen"> #editor { position: absolute; top: 0; right: 0; bottom: 0; left: 0; } </style> </head> <body> <div id="editor">function foo(items) { var x = "All this is syntax highlighted"; return x; }</div> <script src="/ace-builds/src-noconflict/ace.js" type="text/javascript" charset="utf-8"></script> <script> var editor = ace.edit("editor"); editor.setTheme("ace/theme/monokai"); editor.getSession().setMode("ace/mode/javascript"); </script> </body> </html>
上面的script中的src可能需要換成可以訪問的鏈接。之后就能預覽到編輯器的效果。大致和前面小喵的效果類似。
具體的其他的用法可以在官網中查到,這里小喵就不着重介紹了。
三、消息同步機制
選擇好合適的編輯器之后,我們需要做的就是消息通信的功能了。
這里主要有四種情況:
1. 文檔同步。當用戶修改文檔的時候,其修改的部分必須同步到所有的閱讀改文檔的用戶。這里只同步修改的部分,因為每次都同步整個文檔,那么會很消耗帶寬(總不能輸入一個 "hello world"都同步十幾次文檔吧)。
下圖是一個示例(強勢安利一下:https://www.processon.com 這個畫圖的工具)。用戶1編輯了文檔,文檔更新的內容發送給了服務器,服務器將更新的內容組播到所有打開相同文檔的用戶(注意,這里不是廣播,廣播是向所有的用戶發送),同時更新自己的遠程備份。

圖1 文檔更新
2. 文檔副本。當用戶第一次訪問已經存在的文檔的時候。這個時候,該用戶需要加載頁面的所有的內容。因此我們的服務器端需要存放完整的文檔的副本。
如下圖,用戶3打開了這個文檔,這時候會請求服務器發送完整的文檔信息。

圖2 新增用戶
3. 文檔銷毀。小喵這里的文檔的內容是直接在內存中保存的。這樣的好處是很方便,不需要額外的控制數據庫啥的。但是弊端也很明顯,雖然每個文檔可能比較小,但如果文檔創建的比較多,就會一直消耗內存。所以當沒有用戶使用文檔的時候,需要刪除文檔,這樣服務器端就需要保存一個引用計數。計數為0,就刪除文檔。
4. 沖突解決。考慮到網絡可能會出現故障,用戶在編輯文檔之后,其他的用戶可能並沒有即使同步,這樣就出現文檔落后的情況。一個簡單的策略就是,每次文檔修改之后都返回一個時間戳,下一次修改文檔的時候要將這個時間戳作為參數發送到服務器,如果時間戳不是最新的,那么就刷新整個文檔。當然這個策略也有很多的不足之處,如果大家能有什么改進,煩請告訴小喵一下~
上面就是設計部分,具體實現的話,需要用到WebSocket技術,這是瀏覽器和服務器實時通信的一個很好的工具。WebSocket有很多語言的實現,小喵這里選擇的是比較容易上手的socket.io。
socket.io 不僅支持用戶和服務器的點對點通信,還支持組播、廣播的操作。簡單的學習一下,就可以完成上面的設計。
這里,小喵也不在代碼層面上解釋實現了。感興趣的同學可以看看小喵的github: https://github.com/miaoerduo/shared-editor 能夠給小喵提交一些PR的話就更好了。
四、寫在后面
終於寫完這篇博客了,拖了快兩個月了。之前遲遲沒有動手的一個原因是比較忙,更多的可能因為自己有點懶,一直不想畫解釋原理的示意圖。現在終於寫完了,還是挺開心的。
這次的博客相比之前的,更多的是介紹設計的思路。反正代碼都在github上了,大家可以隨意食用~
希望能和大家一起進步!
轉載請注明出處,謝謝~
