遠程桌面控制項目開發(Spring+Netty+Swing)


【目錄】
1.前言
2.初現端倪
3.款款深入
4.責任細分
5.功能層級圖
6.項目結構
7.關鍵類設計
8.一些設計想法
9.待優化
10.一點心得
11.效果演示
12.討論
13.GitHub源碼


## 前言 遠程桌面控制的產品已經有很多很多,我做此項目的初衷並不是要開發出一個商用的產品,只是出於興趣愛好,做一個開源的項目,之前也沒有閱讀過任何遠程桌面控制的項目源碼,只是根據自己已有的經驗設計開發,肯定有許多不足,有興趣的朋友歡迎留言討論。

初現端倪

一般需要遠程控制的場景發生在公司和家之間,由於公司和家里的電腦一般都在局域網內,所以不能直接相連,需要第三方中轉,所以至少有三方,如下圖。

負責中轉的第三方是服務器,控制端和傀儡端(被控制端)相對於服務器來說都是客戶端,都和服務器直接相連,也就是說控制端不和傀儡端相連。

款款深入

約定:

  • 控制端M(Master)
  • 服務器S(Server)
  • 傀儡端P(Puppet)

為了敘述方便,以下如不做特別說明,M表示控制端,S表示服務端,P表示傀儡端。

如果要達到控制傀儡的目的,應該怎么做呢?三方之間至少要發生什么交互呢?
三方會談

控制端、傀儡端的接收器和服務器中的轉發器都是一個,為便於流程的清晰,分開畫了。

責任細分

責任細分

可以看出三者交互主要通過命令形式(命令可以帶數據也可以不帶數據),發送、轉發、接收命令,然后做出相應的動作。
從上圖中看到,服務端不僅需要轉數據,還需要記錄存活的傀儡以及維護控制端和傀儡之間的關系,其實還得處理一些異常情況,比如遠程過程中,傀儡斷開,過一會又連接上,傀儡是否需要繼續給控制端發送屏幕截圖。

功能層級圖

粗粒度分一下,可以分為三層:Desktop層負責UI處理,CommandHandler層負責命令處理,Netty網絡層負責數據的網絡傳輸。

功能層級圖

具體來看一下commandHandler層:
commandhandler

CommandHandlerLoader工具類會根據Netty或Desktop層傳入的Command到配置文件commandhandlers中查找對應的處理類,動態加載,然后進行邏輯處理,這樣對於后期命令添加是非常方便的,命令與命令之間,以及命令與Netty/Deskto之間解耦。

項目結構

總體頂目結構

這個項目一共有四個子模塊:

  • server: 服務端
  • puppet: 傀儡端
  • master 控制端
  • common: 前面三者共用的一些類或接口。
    各個子模塊的包結構類似,我們看其中的一個子模塊puppet即可。
    puppet
包名 描述
commandhandler 命令處理器
constants 常量類,包括配置參數常量、異常消息常量、和消息常量
exception 自定義的一些業務異常類
netty Netty網絡通信的相關類
ui 界面操作的相關類
PuppetStarter 啟動器類
Resources/commandhandlers 命令對應的處理器配置文件

關鍵類設計

下面來看一下關鍵幾個類的設計:

請求/響應類 Invocation

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的作用有兩點:

  1. 用於標識是來自M的請求,還是P的請求。
  2. 用於標識一次請求或響應,可以將M和P串聯起來,用於請求追蹤。

Invocation類是一個基類,請求類(Request)和響應類(Response)在此基礎之上擴展。
Invocation類中有一個成員變量是命令command,我們來看一下:

命令類 Commands

/**
 * @author cool-coding
 * 2018/7/27
 * 命令
 */
public enum Commands{
    /**
     * 控制端或傀儡端連接服務器時的命令
     */
    CONNECT,

    /**
     * 控制命令
     * 1.主人向服務器發送控制請求
     * 2.服務器將控制命令發給傀儡
     * 3.傀儡收到控制命令,將向服務器發送截屏
     */
    CONTROL,

    /**
     * 傀儡發送心跳給服務器
     */
    HEARTBEAT,

    /**
     * 傀儡發送屏幕截圖命令
     */
    SCREEN,

    /**
     * 控制端發送鍵盤事件
     */
    KEYBOARD,

    /**
     * 控制端發送鼠標事件
     */
    MOUSE,

    /**
     * 斷開控制傀儡
     */
    TERMINATE,

    /**
     * 清晰度
     */
    QUALITY
}

目前一共有8個命令,有的命令是M和P共用,有的是一方單用。

命令處理接口 ICommandHandler

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配置文件進行配置即可。

序列號和Puppet名稱生成器

請求和響應類中都有ID屬性,其中一部分是通過序列號生成器生成的,所以提供了SequenceGenerate接口和一個簡單的實現類SimpleSequenceGenerator。同理還有當傀儡連接服務器時,服務器生成唯一的傀儡名,也提供了一個簡單的實現類SimplePuppetNameGenerator。

圖像處理

圖像的數據相對於純命令來說大了許多,所以需要想辦法減少圖像傳輸的數據,大致有兩種方式:

  • 選擇合適的圖片格式,並進行壓縮:我這里選擇了jpg格式,並使用Google Thumbnailator工具進行等寬高壓縮,因為jpg具有較高的壓縮比,但是代價是壓縮后圖像的質量不是太理想。
  • 只傳輸變化的圖像:很多時候圖像變化的部分並不太多,可以只傳輸變化的區域,傳輸到控制端后,控制端只繪制變化的區域。
    (1). 像素級別: 我的思路是在傀儡端保持前一次傳輸時的截屏,和本次截屏圖像進行像素級的比較,將不同的像素保存到一個對象數組中,記錄像素的位置和像素值,傳輸到控制端后,根據像素位置和要替換的像素進行繪制
    (2). 區域級別:只記錄變化圖像的開始點(左上角)和結束點(右下角),然后繪制以這兩個點框定的矩形式區域。
    我嘗試了這兩種方式,沒有達到很好的效果,由於時間有限,沒有更深入研究,最終采取了壓縮圖像的方式。若有更好的方式,可以通過繼承Puppet模塊中抽象類AbstractRobotReplay,實現屏幕截屏方法byte[] getScreenSnapshot(),然后繼承Master模塊中抽像類AbstractDisplayPuppet實現其中的paint方法(也可以繼承現有的實現類PuppetScreen,覆蓋相應的方法),然后將自定義的類在spring配置文件中配置,替換掉現在的實現類即可。

待優化

  • 快速按鍵的情況、雙擊時響應的比較慢。傳輸命令需要時間,所以快速按鍵時命令產生滯后現象,而傀儡端圖像傳輸到控制端后,Swing是單線程處理AWT事件(鼠標、鍵盤、繪圖等),若此時仍在按鍵,則會阻塞,等到按鍵結束之后,再進行圖像的繪制,進行了如下嘗試:
    1. 將命令發送采用異步方式,將命令存放在隊列中,開啟一個線程依次處理,這樣可以減輕awt工作負擔,加快響應屏幕刷新。經測試,屏幕刷新確定快了,但是命令發送的不及時,響應變慢,最終放棄這種方式,依然使用同步發送。
    2. 鼠標移動時,在移動過程中不發送命令,等待移動結束發送:實現方式是移動事件響應方式中添加一個計數器,再采用一個延遲線程,判斷計數器值是否變化,如果延遲時間到時仍沒有變化,則發送“移動命令”,但當移動后單擊,會先發送單擊命令,再發送鼠標移動命令,也不可行。
    3. 傀儡端在發送屏幕截圖時,與上一次進行比較,如果沒有變化,則不發送,減少發送數據量,也減少awt負擔。

一點心得

  • 需求分析很重要,分析需求中各對象的屬性和行為,以及對象之間的關系,這是后面功能、領域模型、靜態/動態模型分析的基礎。
  • 設計靜態模型時,需要根據SOLID原則進行設計,例如遠程控制中命令較多,就抽像出一層,為每個命令單獨寫處理邏輯(當然多個命令也可以共用同一處理邏輯),既符合單一職責原則,又符合開閉原則,將影響降到最低,具體很大的靈活性。又如Master模塊中的IDisplayPuppet接口,此接口是控制端顯示傀儡屏幕的接口,供控制端主窗口MasterDesktop和*Listener調用。

/**
 * @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. 日志最好通過統一的口徑記錄,便於結構清晰和日志管理

    異常

    1. 一定不要catch異常不處理,而且不要catch Throwable,因為Throwable包括了Error和Exception,Error一般都是不可恢復的錯誤,無法在程序中手工處理,不應該catch住。

    2. 一般下層在記錄異常日志,並向上拋出后,上層不需要處理,直接繼續向上拋出即可,如果為了讓異常具體業務含義,便於異常問題查找,可以封裝一些關鍵的業務異常。

    3. 異常最好集中處理,如springmvc:將異常集中在一個異常處理類中處理。

有兩篇文章,我覺得不錯,推薦給大家,我也從中參考了一些方法。
Java 日志管理最佳實踐
Java異常處理的10個最佳實踐

效果演示

  • Centos6.5:傀儡端
  • Windows: 控制端、服務器
  1. 啟動服務器、傀儡、控制端

  2. 復制傀儡名
    傀儡名也可以通過也可以通過日志獲取:

  3. 將名稱輸入控制端

  4. 控制端打開一個遠程屏幕

  5. 可以進行鼠標(單擊,雙擊,右鍵,拖動等)或鍵盤(單鍵或組合鍵等)操作,並可調整屏幕清晰度。

討論

bug反饋及建議https://github.com/Cool-Coding/remote-desktop-control/issues

GitHub源碼

https://github.com/Cool-Coding/remote-desktop-control

如果覺得還不錯,Star支持一下吧,歡迎有興趣的朋友提PR,共同開發出一款好用的遠程桌面控制軟件。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM