Skip to content

Prosty serwer plików UDP

Kod serwera realizującego ten protokół:

Dostępne pliki

Na serwerze (zarówno w wersji TCP, jak i UDP) uruchomionym pod adresem serwer.sieci.tcs.ovh:4567 są dostępne następujące pliki:

-rw-r--r-- 1 root root 16777216 Nov 21 09:47 16M
-rw-r--r-- 1 root root     1024 Nov 21 09:47 1K
-rw-r--r-- 1 root root  1048576 Nov 21 09:47 1M

Naiwna implementacja klienta

Schemat działania programu:

  • Klient wysyła pakiet inicjalizacyjny.
  • Klient otrzymuje informację o liczbie fragmentów.
  • W pętli:
    • Klient wysyła prośbę o \(i\)-ty fragment.
    • Klient otrzymuje \(i\)-ty fragment.

Taka implementacja będzie pobierać pliki powoli i może się zaciąć, jeżeli któryś z komunikatów się zagubi.

Prosta implementacja klienta

Schemat działania programu jest taki sam jak w wersji naiwnej, ale dodatkowo obsługuje timeout w wywołaniach recvfrom. Taka implementacja będzie pobierać pliki powoli, ale zadziała nawet jeżeli któryś z komunikatów się zagubi.

Naiwnie przyspieszona implementacja

Schemat działania programu:

  • Klient wysyła pakiet inicjalizacyjny.
  • Klient otrzymuje informację o liczbie fragmentów.
  • W pętli:
    • Klient wysyła prośbę o \(i\)-ty fragment.
  • W pętli
    • Klient otrzymuje \(i\)-ty fragment.

Taka implementacja przy większej liczbie fragmentów będzie przekraczać bufory i większość komunikatów się zagubi. Spowoduje to zacięcie się programu.

Poprawiona naiwnie przyspieszona implementacja

Schemat działania programu:

  • Klient wysyła pakiet inicjalizacyjny.
  • Klient otrzymuje informację o liczbie fragmentów.
  • W pętli (dopóki jakiś fragment wciąż nie jest pobrany):
    • W pętli:
      • Klient wysyła prośbę o każdy brakujący fragment.
    • W pętli:
      • Klient otrzymuje fragmenty aż do timeout.

Taka implementacja się nie zatnie, ale będzie pobierać pliki powoli i niepotrzebnie przeciążać sieć.

Implementacja pobierająca z zadaną prędkością \(D\) KB/s

Zakładając, że fragmenty mają po 1024 bajty i chcemy pobierać plik z prędkością \(D\) KB/s, musimy pobierać średnio \(D\) fragmentów na sekundę. Można to zaimplementować w jednym wątku przeplatając sendto i recvfrom, ale łatwiej jest podzielić program na dwa wątki: Wątek wysyłający prośby o fragmenty:

  • W pętli (dopóki jakiś fragment wciąż nie jest pobrany):
    • Klient wysyła prośbę o kolejny brakujący fragment.
    • Klient zasypia na \(1/D\) sekundy ( time.sleep(1/D) ).

Wątek odbierający fragmenty:

  • W pętli (dopóki jakiś fragment wciąż nie jest pobrany):
    • Klient otrzymuje fragment.

Schemat działania programu:

  • Klient wysyła pakiet inicjalizacyjny.
  • Klient otrzymuje informację o liczbie fragmentów.
  • Inicjalizacja tablicy fragmentów.
  • Startuje równolegle wątek wysyłający i odbierający.
  • Czeka na zakończenie wątków.

Taka implementacja będzie pobierać pliki z zadaną prędkością i będzie odporna na zagubione pakiety. Jeżeli serwer nie działa, lub zostanie wyłączony (lub odcięty), to program się zatnie. Tą sytuację można wykryć w wątku odbierającym i odpowiednio obsłużyć - zakończyć działanie obu wątków i zgłosić błąd.

Implementacja pobierająca z automatycznie dobraną prędkością

Schemat działania programu jest taki sam jak w wersji poprzedniej, ale wątek odbierający może sterować wartością \(D\). Poprzednia wersja programu potrzebuje następujących modyfikacji. W wątku wysyłającym prośby:

  • Wątek wysyłający prośby patrzy na zegarek ( time.monotonic() ) przed wysłaniem komunikatu.
  • Wątek wysyłający wpisuje aktualny czas do pola transaction_id w wysyłanym komunikacie.
  • Wątek wysyłający dopisuje aktulany czas do tablicy log przechowującej momenty, w których zostały wysłane prośby.

Zbieranie statystyk w wątku odbierającym odpowiedzi:

  • Wątek odbierający każdy pakiet może porównać pole transaction_id w odebranym komunikacie z aktualnym czasem ( time.monotonic() ).
  • Pozwala to mierzyć Round-Trip-Time na każdej udanej transmisji.
  • Można obliczyć średnią wartość RTT i średnie odchylenie RTT.
  • Wątek odbierający powinien czyścić tablicę log w momencie odebrania odpowiedzi na wysłaną prośbę.
  • Należy uznać, że komunikaty wysłane wcześniej niż średnia RTT + 4 odchylenia RTT się zagubiły.

Sterowanie prędkością:

  • Wątek odbierający powinien zwiększać prędkość tak długo jak nie ma zagubionych komunikatów.
  • Wątek odbierający powinien zmniejszać prędkość w reakcji na zagubione komunikaty.
  • Całkiem niezłą strategią może być zwiększanie prędkości o stałą po każdy odebranym komunikacie i zmniejszanie prędkości do połowy po wykryciu zagubionych komunikatów.

Uwagi

Dokumenty RFC 8085, RFC 5405 i RFC 5348 zawierają różne uwagi o tym jak projektować transmisje UDP.

Dokument RFC 6298 opisuje jak RTT jest obliczane w protokole TCP.