https://github.com/kamix-08/directdesk
abstract
DirectDesk to aplikacja inspirowana przez RustDesk, napisana jako projekt końcowy w ramach przedmiotu Algorytmy i Techniki Programowania.
struktura
DirectDesk jest podzielony na 3 części - streamer, reciever i shared.
streamer wykorzystuje DirectX w celu przechwytywania zawartości ekranu, Windows Media Foundation, w celu enkodowania klatek do H.264 na GPU, które wysyła do recievera przez UDP za pomocą Winsock.
reciever otrzymuje klatki H.264, które następnie dekoduje na GPU za pomocą Windows Media Foundation i wyświetla z użyciem DirectX oraz Windows.h. reciever również wysyła do streamera dane potrzebne do pełnej funkcjonalności zdalnego pulpitu - ruchy i kliknięcia myszki, oraz inputy klawiatury.
shared zawiera wspólne fragmenty kodu - klasy wykorzystywane do zarządzania połączeniami, oraz klasy do GUI.
połączenie
Nawiązywanie połączeń pomiędzy streamerem a recieverem składa się z 3 kroków:
- STUN
Obaj klienci wysyłają zapytanie do serwera STUN, w celu poznania swojego publicznego adresu IP.
Ważne jest, żeby żaden z klienów nie był połączony do sieci z symetrycznym NATem. Symetryczny NAT dla każdego “celu” przypisuje osobny port. Oznacza to, że połączenie z serwerem STUN zwróci adres, który nie będzie mógł zostać wykorzystany do połączenia P2P. Najprostszy sposób na sprawdzenie, czy używana sieć ma symetryczny NAT to zapytanie dwóch niezależnych serwerów STUN o adres - jeśli ich odpowiedzi są różne, P2P będzie niemożliwe.
Sprawdzenie to nie jest przeze mnie wykonywane - jedynie reciever zapytuje dwa serwery a ich odpowiedź jest wypisywana w konsoli, w celach testowych.
Wykorzystywane serwery to stun1.l.google.com (domyślny) i stun2.l.google.com (secondary dla recievera).
- Signaling
Następnie obaj klienci łączą się do signaling serwera, który przekazuje każdemu z użytkowników adres drugiego, z którym ma się połączyć.
Signaling serwer przyjmuje adres oraz ID urządzenia. ID jest generowane losowo dla streamera, a reciever musi je podać ręcznie. W ten sposób serwer łączy dwóch użytkowników w pary.
Komunikacja z signaling serwerem odbywa się za pomocą TCP.
Użyty został customowy serwer, napisany w Go.
- P2P
Klienci znając swoje wzajemne adresy (i nie będąc połączonym do sieci z symetrycznym NATem) są w stanie stworzyć bezpośrednie połączenie UDP P2P za pomocą hole punching.
To połączenie jest wykorzystywane do dwustronnej komunikacji - streamer wysyła klatki, a reciever wysyła inputy.
kilka słów o UDP
Warto wspomnieć, że pojedyncza klatka H.264 zazwyczaj jest kilkukrotnie większa, niż rozmiar maksymalnej paczki UDP (1500 bajtów). Z tego powodu zastosowana została fragmentacja klatek.
Problemem UDP jest możliwość utraty pakietów, lub dotarcia ich w innej niż oczekiwana kolejności. Problem ten jest minimalizowany, poprzez przypisywanie klatkom i fragmentom ID, które następnie są używane przy rekonstrukcji.
Zdarzyć się jednak może, że pakiet zostanie utracony całkowicie. Wtedy niemożliwe jest odtworzenie klatki, więc jest ona pomijana.
Jest to znaczącym problemem przy korzystaniu z formatu H.264, gdyż większość klatek zapisuje jedynie zmiany od poprzedniej, więc utrata jednej klatki może prowadzić do artefaktów, które znikną dopiero z dotarciem kolejnego I-slice (klatki bazowej w H.264).
Sposobem na ograniczeniem tego problemu jest stałe “dopytywanie” streamera o brakujące fragemnty, tworząc coś w rodzaju TCP, ale w UDP. Wiązałoby się to jednak ze spowolnienem działania aplikacji i zmniejszeniem FPSów, przez co nie zostało zastosowane.