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.
- W pętli:
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.