遠程桌面控制的產品已經有不少不少,我作此項目的初衷並非要開發出一個商用的產品,只是出於興趣愛好,作一個開源的項目,以前也沒有閱讀過任何遠程桌面控制的項目源碼,只是根據本身已有的經驗設計開發,確定有許多不足,有興趣的朋友能夠留言討論與支持。html
通常須要遠程控制的場景發生在公司和家之間,因爲公司和家裏的電腦通常都在局域網內,因此不能直接相連,須要第三方中轉,因此至少有三方,以下圖。java
負責中轉的第三方是服務器,控制端和傀儡端(被控制端)相對於服務器來講都是客戶端,都和服務器直接相連,也就是說控制端不和傀儡端相連。git
約定:github
- 控制端M(Master)
- 服務器S(Server)
- 傀儡端P(Puppet)
爲了敘述方便,如下如不作特別說明,M表示控制端,S表示服務端,P表示傀儡端。spring
若是要達到控制傀儡的目的,應該怎麼作呢?三方之間至少要發生什麼交互呢?數組
控制端、傀儡端的接收器和服務器中的轉發器都是一個,爲便於流程的清晰,分開畫了。
能夠看出三者交互主要經過命令形式(命令能夠帶數據也能夠不帶數據),發送、轉發、接收命令,而後作出相應的動做。
從上圖中看到,服務端不只須要轉數據,還須要記錄存活的傀儡以及維護控制端和傀儡之間的關係,其實還得處理一些異常狀況,好比遠程過程當中,傀儡斷開,過一會又鏈接上,傀儡是否須要繼續給控制端發送屏幕截圖。服務器
粗粒度分一下,能夠分爲三層:Desktop層負責UI處理,CommandHandler層負責命令處理,Netty網絡層負責數據的網絡傳輸。網絡
具體來看一下commandHandler層:mvc
CommandHandlerLoader工具類會根據Netty或Desktop層傳入的Command到配置文件commandhandlers中查找對應的處理類,動態加載,而後進行邏輯處理,這樣對於後期命令添加是很是方便的,命令與命令之間,以及命令與Netty/Deskto之間解耦。異步
這個項目一共有四個子模塊:
各個子模塊的包結構相似,咱們看其中的一個子模塊puppet便可。
包名 | 描述 |
---|---|
commandhandler | 命令處理器 |
constants | 常量類,包括配置參數常量、異常消息常量、和消息常量 |
exception | 自定義的一些業務異常類 |
netty | Netty網絡通訊的相關類 |
ui | 界面操做的相關類 |
PuppetStarter | 啓動器類 |
Resources/commandhandlers | 命令對應的處理器配置文件 |
下面來看一下關鍵幾個類的設計:
public class Invocation implements Serializable { /** * ID(客戶端標識(控制端爲'M',傀儡端爲'P')+MAC地址+序列號) */ private String id; /** * 傀儡名 */ private String puppetName; /** * 命令 */ private Enum<Commands> command; /** * 值 */ private Object value; //省略getter、setter方法 @Override public String toString() { return "Response{" + "requestId='" + requestId + '\'' + ", puppetName='" + puppetName + '\'' + ", command=" + command + ", value=" + value + '}'; } }
其中id的做用有兩點:
Invocation類是一個基類,請求類(Request)和響應類(Response)在此基礎之上擴展。
Invocation類中有一個成員變量是命令command,咱們來看一下:
/** * @author cool-coding * 2018/7/27 * 命令 */ public enum Commands{ /** * 控制端或傀儡端鏈接服務器時的命令 */ CONNECT, /** * 控制命令 * 1.主人向服務器發送控制請求 * 2.服務器將控制命令發給傀儡 * 3.傀儡收到控制命令,將向服務器發送截屏 */ CONTROL, /** * 傀儡發送心跳給服務器 */ HEARTBEAT, /** * 傀儡發送屏幕截圖命令 */ SCREEN, /** * 控制端發送鍵盤事件 */ KEYBOARD, /** * 控制端發送鼠標事件 */ MOUSE, /** * 斷開控制傀儡 */ TERMINATE, /** * 清晰度 */ QUALITY }
目前一共有8個命令,有的命令是M和P共用,有的是一方單用。
public interface ICommandHandler<T> { /** * * @param ctx 當前channel處理器上下文 * @param inbound channel輸入對象 * @throws Exception 異常 */ void handle(ChannelHandlerContext ctx,T inbound) throws Exception; }
ICommandHandler接口是全部命令處理類的父接口,Netty ChannelHandler在處理請求時,根據不一樣的命令,尋找對應的處理類。
心跳和屏幕截圖都是定時向服務器發送,因此在設計時這二者同時只有一個活動便可。即發送心跳時不發送屏幕截圖,發送屏幕截圖時不發送心跳,控制結束後,繼續發送心跳。這二者之間的控制由Puppet模塊中ConnectCommandHandler類中的HeartBeatAndScreenSnapShotTaskManagement內部類控制。
經過對用例和流程的分析,發現命令出現的頻率比較高,因而考慮將命令處理單獨獨立出來,採起動態加載的方式,使其與ChannelHandler解耦,使用後期擴展,並且當命令不少時,不須要一次都加載,只是在使用時按需加載,減小JVM加載類的字節碼量,此處參考了SPI思想。而添加命令,勢必會修改界面,我使用模板模式,預留出菜單,界面體,界面屬性設置等,修改時只需繼續相關類並修改,而後在spring配置文件進行配置便可。
請求和響應類中都有ID屬性,其中一部分是經過序列號生成器生成的,因此提供了SequenceGenerate接口和一個簡單的實現類SimpleSequenceGenerator。同理還有當傀儡鏈接服務器時,服務器生成惟一的傀儡名,也提供了一個簡單的實現類SimplePuppetNameGenerator。
圖像的數據相對於純命令來講大了許多,因此須要想辦法減小圖像傳輸的數據,大體有兩種方式:
我嘗試了這兩種方式,沒有達到很好的效果,因爲時間有限,沒有更深刻研究,最終採起了壓縮圖像的方式。如有更好的方式,能夠經過繼承Puppet模塊中抽象類AbstractRobotReplay,實現屏幕截屏方法byte[] getScreenSnapshot(),而後繼承Master模塊中抽像類AbstractDisplayPuppet實現其中的paint方法(也能夠繼承現有的實現類PuppetScreen,覆蓋相應的方法),而後將自定義的類在spring配置文件中配置,替換掉如今的實現類便可。
/** * @author Cool-Coding * 2018/8/2 * 傀儡控制屏幕接口 */ public interface IDisplayPuppet { /** * 啓動窗口顯示傀儡桌面 */ void launch(); /** * 刷新桌面 * @param bytes */ void refresh(byte[] bytes); /** * * @return 傀儡名稱 */ String getPuppetName(); }
接口中這三個方法前兩個方法launch和refresh,都是主窗口啓動傀儡控制窗口和刷新屏幕必須的方法,第三個方法是因爲發送命令時,須要知道傀儡名稱,而實體之間是面向接口設計的,因此須要提供獲取傀儡自身名稱的方法。
個人心得是:
日誌
1. 記錄程序關鍵步驟的上下文信息,例如記錄請求或響應的數據以及附加的消息,記錄此處建議使用trace/debug級別。 2. 記錄業務流程的日誌,使用info/error級別,這一部分日誌主要是應用日誌,例如控制端發起控制,成功或失敗消息。 3. 日誌最好經過統一的口徑記錄,便於結構清晰和日誌管理
異常
有兩篇文章,我以爲不錯,推薦給你們,我也從中參考了一些方法。
Java 日誌管理最佳實踐
Java異常處理的10個最佳實踐
bug反饋及建議:https://github.com/Cool-Codin...
https://github.com/Cool-Codin...
若是以爲還不錯,Star支持一下吧,想繼續開發的朋友歡迎提PR,共同開發出一款好用的遠程桌面控制軟件