Draft trzeciego zadania dla studentów dziennych
Pojawił się draft trzeciego zadania.
Drugie zadanie dla studentów dziennych
Oficjalnie pojawiło się drugie zadanie zaliczeniowe.
Draft pierwszego zadania dla studentów dziennych
Pojawił się draft pierwszego zadania.
Warriors of the Net
Film "Warriors of the Net" można ściągnąć z nasepującej strony: www.warriorsofthe.net
Zadania ćwiczeniowe UDP
- Stworzyć aplikację klienta, która wyśle 10 kolejnych liczb naturalnych do wskazanego na ćwiczeniach serwera (kod źródłowy serwera dostępny jest w plikach). Po wysłaniu kazdej z liczb odbierze jedną i wypisze ją na standardowe wyjście.
- Stworzyć eacho-serwer: serwer powinien wczytać dwie linie X, Y z dwóch kolejnych datagramów wysłanych przez tego samego klienta, po czym odpowiedzieć jednym datagramem zawierającym jedną linię: echo: X, Y. Uwaga: należy zadbać o obsługę wielu kilentów jednocześnie.
Zadania ćwiczeniowe TCP
- Stworzyć aplikację klienta, która wczyta dwie linie ze standardowego wejścia, wyśle je do serwera, po czym przyjmie od serwera jedną linię i wypisze ją na standardowe wyjście.
- Stworzyć eacho-serwer: serwer powinien wczytać dwie linie X, Y od klienta i odesłać jedną linię: echo: X, Y.
- Stworzyć skaner portów: aplikacja powinna przyjmować adres IP i zakres portów TCP do skanowania, po czym sukcesywnie próbować nawiązać połączenie TCP z potencjalnym serwerem znajdującym się pod zadanym adresem IP i nasłuchującym na porcie z zakresu - w przypadku powodzenie wypisujemy informację na standardowe wyjście.
- (Dla chętnych) Stworzyć aplikację sieciową symulującą prosty atak DDoS.
Gniazda UDP
Typowe sytuacje, gdzie protokół UDP ma zastosowanie to:
- aplikacje generujące o wiele więcej danych niż jest się skłonnym przesłać, a wybór, które z generowanych danych akurat przesłać jest dość arbitralny (np. transmisje audio/wideo)
- istnienie wielu odbiorców dla jednej wiadomości (np. broadcast, multicast), kiedy przesyłanie wielu kopii jest z jakichś względów niepraktyczne
- ogólnie - konieczność sprawowania dokładnej kontroli nad transmisją danych (DHCP, DNS, serwery gier, ...)
Możliwy scenariusz działania serwera wygląda następująco:
- ustawienie gniazda datagramowego na znanym porcie; ewentualnie na pierwszym wolnym (konstruktor bezargumentowy) i przekazanie o tym informacji:
server = new DatagramSocket();
- Przygotowanie datagramu do odbioru danych
buff = new byte[MAX_DATAGRAM_SIZE]; datagram = new DatagramPacket(buff, buff.length);
- czekanie na pojawienie się datagramu
server.recive(datagram);
- utworzenie osobnego wątku obsługującego klienta (i powrót do przygotowania do odbioru kolejnego datagramu)
new Thread() { //... public void run() { // obsługa klienta //... } }.start();
- w prologu obsługi klienta pobranie informacji o gnieździe nadawcy datagramu i utworzenie datagramu do wysłania:
clientAddress = datagram.getAddress(); clientPort = datagram.getPort(); // przygotownie danych // ... datagram = DatagramPacket(buff, buff.length, clientAddress, clientPort);
Jak ustalić wielkość datagramu?
Oczywiście zgodnie ze specyfiką problemu, który się rozwiązuje. Lepszym pytaniem jest: jakiej maksymalnej wielkości datagramy powinniśmy przesyłać? Prosta odpowiedź to: takiej aby datagramy mieściły się w buforach urządzeń znajdujących się na ścieżkach, którymi owe datagramy się poruszają - choć warstwa IP potrafi dzielić datagramy na fragmenty, zazwyczaj takie podziały prowadzą do sporych nieefektywności w komunikacji. Niestety, nie istnieje dobry sposób na określenie tej wielkości. Standard gwarantuje tylko 576 bajtów na datagram w warstwie IP - odejmując od tego 60 bajtów maksymlnej wielkości nagłówka IP (tj. 4 * (2^4 - 1)) i 8 bajtów wielkości nagłówka UDP, zostajemy z gwarantowaną obsługą 508 bajtów danych.Gniazda TCP
Wyróżniamy dwa rodzaje gniazd TCP:
- nasłuchowe - wykorzystywane po stronie aplikacji serwera do nawiązywania połączeń z klientami; takie gniazda są unikalnie identyfikowane przez adres hosta i numer portu, na którym stoją
- połączeniowe - wykorzystywane do wymiany danych; takie gniazda (po związaniu) są unikalnie identyfikowane przez czwórkę - host źródłowy, port źródłowy, host docelowy, port docelowy
Typowy scenariusz działania serwera wygląda następująco:
- ustawienie gniazda nasłuchowego na znanym porcie; ewentualnie na pierwszym wolnym (wartość 0) i przekazanie o tym informacji:
server = new ServerSocket(0);
- czekanie na akceptację połączenia z klientem
- akceptacja połączenia, podczas której dokonywany jest standardowy handshake TCP i w wyniku której zwrócone zostaje gniazdo połączeniowe, ustawione na tym samym hoście i porcie co gniazdo nasłuchowe, związane z gniazdem połączeniowym klienta
client = server.accept();
- utworzenie osobnego wątku obsługującego klienta (i powrót do nasłuchiwania)
new Thread() { //... public void run() { // obsługa klienta //... } }.start();
- utworzenie gniazda komunikacyjnego i związanie go z gniazdem po stronie serwera; takie gniazdo zostanie utworzone na dowolnym wolnym porcie:
client = new Socket(serverAddress, serverPort);
- komunikacja z serwerem i czynności porządkowe
Pliki
Przykład serwera TCP obsługującego wiele połączeń w jednym wątku: MultipleTCP.java |
Wielkość datagramu (UDP): UDP.java |
Przykład prostego serwera (UDP): UDPServer.java, Factorial.java |
Szkielet prostego klienta (TCP): ClientTemplate.java |
Przykład prostego serwera (TCP): TestServer.java, ServerThread.java |
Informacje o hostach: TestAddr.java |