18 декабря 2017 г.

Удаленный рабочий стол (RDP). WPF (MVVM).

Скачать исходный код можно по ссылке:

https://bitbucket.org/sergey_vaulin/remotedesktopterminalservice/src


Если у вас когда либо вставал вопрос о том, как реализовать в вашем приложении демонстрацию удаленного рабочего стола другого клиента, то скорее всего вам известны описанные ниже варианта:
  1. Делать скриншот на одной стороне, затем пересылать на сторону получателя и отображать на форме.
  2. Найти C# обёртки и реализации работы с VNC протоколом, который используется в opensource проекте UltraVNC
  3. Использование Windows Desktop Sharing в вашем приложении.
Сразу оговорюсь, что тут рассматривается схема, при которой запуск и остановку серверной части и демонстрация на другой стороне, осуществляется в прикладном коде нашего приложения.

(Пункт 1)
Использование скриншотов является самой простой в реализации, но достаточно трудоемкой операцией. Найденные примеры захвата снимка экрана и его обработка будет намного "тяжелее", чем используя обычный терминал удаленного рабочего стола. Попробуйте в Realtime поделать хотя бы 10 скриншотов и заметите, что ваше приложение будет активно отбирать ресурсы CPU. Да, сейчас компьютеры достаточно мощные и ваш компьютер может это не чувствовать, но клиенты могу сидеть и на Athlon II, имея загрузку в 30% процессорного времени, что неприемлемо. Выходом может стать нарезка скриншотов используя видео захват с дисплея через DirectX, но решения для copy+paste у меня нет.

(Пункт 2)
UltraVNC достаточно известное кросс платформенное приложение для демонстрации и управлением экраном. Находил обертки для .NET, поэтому, если все плохо, то можно посмотреть в эту сторону. Как мне известно, принцип работы VNC основан на захвате скриншотов клиента, и для того, что бы производить видео захват с дисплея, они используют свой драйвер, аккуратно подгружаемый при демонстрации. При этом сам драйвер идёт как отдельный устанавливаемый пакет и может быть скачан отдельно (искать UltraVNC Driver).

(Пункт 3)
Ну и наконец самый, но мой взгляд, простой способ реализовать демонстрацию удаленного экрана это использование нативного API под названием Windows Desktop Sharing (RDP). Используется стандартный механизм терминала удаленного рабочего стола, но в отличии от обычного подключения, удаленный компьютер не блокируется, как при терминале, и вы можете как в Teamviewer пользоваться одной мышкой и видеть экран одновременно. Далее я перечислю возможности, доступные при работе с Windows Desktop Sharing в формате плюсы и минусы:

Плюсы:
  • Демонстрацию удаленного экрана пользователя.
  • Подключение больше одного человека (код примера надо немного доработать).
  • Можно настраивать уровень взаимодействия для каждого подключающегося человека. Допустим одному дать полный доступ к управление, другому только для просмотра. За это отвечает CTRL_LEVEL выбор уровня. 
  • Мы можем в любой момент отключить пользователя, или приостановить показ.
  • Самой интересной особенностью считаю возможность "Фильтрации списка окон". Таким образом мы можем дать возможность видеть только то, что мы хотим показать, скрывая остальные окна.
  • Можно обеспечивать как прямое подключение клиента к серверу, так и сервера к клиенту. Тем самым можно решить проблему с NAT, инициируя подключение с другой стороны подсети.
  • Для авторизации можно использовать схему с логином и паролем.
  • Отображение идёт через ActiveX оснастку, которую можно разместить как на WPF, так и на WinForm.
  • Проекты можно без потери функциональности конвертировать под работы с .NET Framework 3.5.
Минусы:
  • Поддержка идет только начиная с Windows Vista.



Демонстрация работы описанного ниже функционала.


Перед тем, как изучать сказанное, логично увидеть результат работы. Поэтому я представлю два варианта подключения:
  1. Показ полного рабочего стола.
  2. Показ только тех окон, которые мы хотим показать. В моем случае я буду показывать только окно приложения
Внимание! Возможен еще вариант отображения определенного статического региона экрана, но я не добавлял этот функционал.
- Полный рабочий стол:



- Отображение конкретного окна:



Как с этом работать?


Всё API у Windows Desktop Sharing находится в библиотеке "C:\Windows\System32\RdpEncom.dll". Серверный хостинг осуществляется через RDPSession класс:


Для демонстрации стоит использовать IRDPSRAPIViewer, который представляет из себя ActiveX оснастку:


Поэтому, для того, что бы нам извлечь обертку для последующего использования, нам понадобится воспользоваться утилитой Aximp.exe (Windows Forms ActiveX Control Importer), которая извлекает AcitveX обертку (так же мы можем добавлять ActiveX control в Visual Studio Toolbox). К сожалению, сама утилита не входит в комплект поставки Visual Studio, но присутствует в Windows SDKs, поэтому, ради нее, придется поставить SDK. Версия не должна влияет,  поэтому после того как установка завершится, переходите в папку "C:\Program Files (x86)\Microsoft SDKs\Windows\" и поиском находите этот файл.


Наконец то можно создать недостающие классы обертки и приступить к работе:


Команда "AxImp.exe C:\Windows\System32\rdpencom.dll" создала два файла:
  1. AxRDPCOMAPILib.dll
  2. RDPCOMAPILib.dll
Которые можно без проблем добавить в ваш проект и начать работу с RDP API.

В AxRDPCOMAPILib.dll содержатся:
  • _IRDPSessionEvents
  • AxRDPViewer
В RDPCOMAPILib.dll содержатся:
  • RDPSession
  • IRDPSRAPIApplication
  • _IRDPSessionEvents
  • IRDPSRAPIApplicationFilter
  • IRDPSRAPIApplicationList
  • IRDPSRAPIAttendee
  • IRDPSRAPIAttendeeDisconnectInfo
  • IRDPSRAPIAttendeeManager
  • IRDPSRAPIInvitation
  • IRDPSRAPIInvitationManager
  • IRDPSRAPISessionProperties
  • IRDPSRAPISharingSession
  • IRDPSRAPITcpConnectionInfo
  • IRDPSRAPIViewer
  • IRDPSRAPIVirtualChannel
  • IRDPSRAPIVirtualChannelManager
  • IRDPSRAPIWindow
  • IRDPSRAPIWindowList
  • А так же ClassInterface для каждого из интерфейсов выше.
Внимание! В этой схеме есть одно НО! Мы обязанный таскать с собой эти два файла, что, на мой взгляд, не очень удобно. А ведь в них содержатся только обертки для работы с Native API. Далее будет описано как избавить от них.


Архитектура модуля.


Теперь я хочу описать сделанный мной пример (ссылка на исходник в начале статьи), в котором я добавил некоторые упрощения для работы в WPF приложениях с шаблоном MVVM. Я постарался максимально отдалить вас от подготовки RDP описанной выше, доведя использование до простого размещения Control на форме и возможности сразу его использовать. А так же, что бы было еще быстрее понять как с ним работать, я разместил пример его использования.

Дерево иерархии:



Про все тонкости использования, и о том что где лежит, я напишу поподробнее:

Проект Rdp.Terminal.Core .


По сути является унифицированной сборкой, которую достаточно поместить в вашем решении.
  • Client\Controls\RemoteTerminal.xml - служит для демонстрации удаленного рабочего стола на стороне клиента
  • Server\RdpSessionServer.cs - содержит примитивные методы, для запуска серверной части.
  • WinApi - что бы избавиться от потребности таскать с собой AxRDPCOMAPILib.dll и RDPCOMAPILib.dll я декомпилятором извлек все обертки и разместил их в модуле в папке winapi.
  • WinApi\SupportUtils.cs содержит метод проверки поддержки текущей версии Windows на возможность использования RDP

Проект Rdp.Demostration.


Демонстрационный проект, в котором можно узнать как работать с каждым из компонентов.
  • Prism - содержит нужные, для демонстрации использования MVVM шаблона, классы.
  • Views\MainWindow.xaml - главная View окна, выполняющая как роль серверной так и клиентской стороны.
  • ViewModels\MainWindowViewModel.cs - содержащая всю логику для работы со view MainWindow.xaml
  • Свойство SmartSizing. Если его значение True, тогда удаленный экран будет полностью растянут под размер доступной области. Иначе если False, тогда появятся два бегунка и картинка будет видна полностью, без растягивания.
Внимание! Для работы с RemoteTerminal, содержащим AxRDPViewer, необходимо создать во ViewModel свойство с типом RdpManager и произвести привязку на свойство с таким же названием у RemoteTerminal <controls:RemoteTerminal RdpManager="{Binding RdpManager}" />. После чего, во ViewModel, через это объект можно управлять терминалом. 

Процесс удаленного подключения.


При запуске сервера настраивается в каком режиме он будет запускать (с фильтрацией окон или без неё). Далее мы создаём так называемое "Приглашение" вызывая метод CreateInvitation. Оно представляет из себя XML, содержащий информацию доступных сетевых интерфейсах сервера, используемый порт для подключений, MAC и идентификатор зашифрованный паролем, который надо ввести клиенту. Вся эта информацию поможет клиентам с подключением, так как вам не потребуется выяснять какой точно интерфейс доступен клиенту. В случае, если сервер закрыт NAT, клиент может выступить в качестве сервера обратно подключения, но при этом демонстрацию будет осуществлять серверный компьютер. В ситуации, когда когда обе стороны разделяет NAT соединение невозможно.
После подключения модуля удаленного управление вам необходимо будет позаботиться о доставке строки подключения между клиентом и сервером.

Внимание! Не все методы имеются в RdpManager, поэтому в зависимости от потребности придётся их пробросить вам самим.

24 комментария:

  1. Огромное спасибо. А не подскажите где место у сервера где он определяет что к нему кто то подключился или отключился

    ОтветитьУдалить
  2. Все не надо нашол.Rdp.Terminal.Core.Server.RdpSessionServer подключить add_OnAttendeeDisconnected

    ОтветитьУдалить
  3. мастер ййййййййй29 ноября 2018 г., 02:53

    гж полезная штука, о великий бог из машин поведай мне могу ли я как нибудь прикрутить звук к сеей колдовской утилите чтобы с ноута в контру со звуком играть?

    ОтветитьУдалить
    Ответы
    1. Если честно про звук не знаю, какого то явного свойства для вкл и выкл его я не нашёл. Правда искал в 5 базовых интерфейсах из RDPCOMAPILib

      Удалить
    2. мастер ййййййййй30 ноября 2018 г., 00:38

      ну ладн все равно гж, а то ремоте десктоп без проброса портов не работает а этот работает

      Удалить
  4. Чет интересно получается)) когда запускать её на одном компе то все работает кнопки щелкает а вот когда запустить сервер и клиент на разных компах то клики мыши не передаются((

    ОтветитьУдалить
    Ответы
    1. По сети работать должно. А когда вы жмёте на клиенте у вас нет звука, как будто отрыто где то модальное окно? Если да, то встречал эту проблему так как то хитро правилось возратом фокуса

      Удалить
    2. Под возвратом фокуса что понимается? На звук даже и не обращал внимания. Но мышь двигается и даже при наведении на пуск он в виндовс 10 даже выделяется

      Удалить
    3. Если дзыньканье идёт то (вроде так правилось) подписка на потерю фокуса в контроле делается и если текущее окно активно а контрол фокус поменял то SetFocus() делается.

      Удалить
  5. Этот комментарий был удален автором.

    ОтветитьУдалить
  6. Здравствуйте. Простите, хотел поинтересоваться нету ли документации к вами написанной программы с комментариями ? Так как в целом имею из вашего архива ряд заранее внесённых стандартов, моделей. Но хотелось бы самостоятельно создать, построить проект, для осознания приблизительных фундаментальных значений. К примеру мне хочу сделать моделирование объектов, но не понимаю откуда взяты основные объекты, код есть, в принципе думаю рано или поздно сам пойму. Но Можете если не сложно и не настаиваю) Прокомментировать свою работу ?

    ОтветитьУдалить
    Ответы
    1. Добрый вечер.

      По поводу документации. Исходный код можно без проблем скачать по ссылке Download (https://bitbucket.org/sergey_vaulin/remotedesktopterminalservice/src) я старался все публичные методы писать с комментариями. По сути дела мой код это обёртка для упрощения работы с Windows Desktop Sharing API производителем которой является Microsoft. В проекте Rdp.Terminal.Core находится работа с этим API а в Rdp.Demostration пример работы, можете добавлять либо весь проект себе и использовать в комерческих целях. Цель моих трудов упростить работу с этим API. Описание работы со всеми COM классами и структурами можно найти тут https://docs.microsoft.com/en-us/windows/desktop/api/rdpencomapi/

      Удалить
    2. Спасибо большое) буду изучать! Спасибо за ваш труд, благодаря вам, моя область восприятия становится шире, а жизнь проще)

      Удалить
  7. И прочитав принцип работы построения туннеля по порту UDP, не понимаю с помощью чего реализуется подключение от меня из под NAT, к машине которая находится в своей NAT. Получается по умолчанию ваш сервис в конфигурации имеет отлаженную настройку на посредника с помощью которого подключается по портам к серверу ? Если это так, то где находится эта строка ? Если нет, то как мне удалось подключится ? Допустим внутри сети подключился к машине, таблица занесла это подключение в список, и получается уже не зависимо от того в какой сети буду находится смогу подключиться конкретно к этому компьютеру ? Ещё заметил что при запуске сервера, там выдаётся IPv6, IPv4 и порт на каждый адрес, как он это делает скажите пожалуйста ? Где находить эти конфигурации ? Он по умолчанию присваивает порты эти или по принципу отладки находит свободные и выпускает их ? Простите за возможную туфту, максимально обьёмно и просто старался описать суть вопросов, но возможно не верно, за что простите. Но прошу, пожалуйста вступите со мной в диалог, буду очень рад если дадите ответы на мои вопросы

    ОтветитьУдалить
    Ответы
    1. Помогу чем смогу.

      По поводу NAT сейчас не спомню все но Windows Desktop Sharing но метод ConnectToClient инициирует подключение если сервер за NAT по адресу https://msdn.microsoft.com/en-us/mt446078 можно почитать описание метода RDPSession. Саму строку создаю не я, прослушиваемые адреса задаю тоже не я но думаю можно как то конкретные выбрать.

      Если хочется все сделать с 0 то в статье есть описание того, как это сделать. Но советую себе просто перекопировать классы Interop-ы из Rdp.Terminal.Core проекта в папке WinAPI.

      Прямая ссылка на эти классы
      https://bitbucket.org/sergey_vaulin/remotedesktopterminalservice/src/115b7b3119bcbd3e1dbcc90f80bb4cbdebb48395/Rdp.Terminal.Core/WinApi/?at=default

      Удалить
    2. Спасибо большое) а по поводу классов, пробовал обёртку вытаскивать самостоятельно, вышли два файла, но не понимаю как их правильно интегрировать в VS, что бы отобразились все классы, но думаю нативно пойму) Спасибо большое! Век IT Рыцарям!

      Удалить
    3. Не за что, рад что полезное нашли у меня) По прямой ссылке на классы содержатся те же самые Interop-ы, то есть можно обойтись без этих файлов, либо по старинке добавлять ссылки в References проекта

      Удалить
  8. Здравствуйте, подскажите пожалуйста, можно ли в данной программе также реализовать обмен файлами либо связь в виде текстового чата? Если да, то как и в каком направлении искать информацию по этой теме?

    ОтветитьУдалить
    Ответы
    1. Добрый день, я думал насчет передачи файлов, но не сделал, вернее не доразобрался. В исходниках посотретите 2-ю ветку там мои наработки

      Удалить
  9. Анонимный1 июня 2019 г., 17:28

    Добрый день, можно ли переделать данный проект под Windows Forms?

    ОтветитьУдалить
    Ответы
    1. Приветсвую, да конечно, там используется ActiveX контрол, который без проблем должен уметь размещаться на Form. Попробуйте из проекта добавить класс AxRDPViewer : AxHost на форму

      Удалить