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

  1. 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.
  2. 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

  1. 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.
  2. Stworzyć eacho-serwer: serwer powinien wczytać dwie linie X, Y od klienta i odesłać jedną linię: echo: X, Y.
  3. 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.
  4. (Dla chętnych) Stworzyć aplikację sieciową symulującą prosty atak DDoS.

Gniazda UDP

UDP communication
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, ...)
W Javie gniazda UDP (zarówno unicastowe jak i broadcastowe; gniazdami multicastowymi nie będziemy zajmować się na zajęciach) reprezentowane są przez klasę DatagramSocket.

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);
    
Podobnie wygląda obsługa klienta.

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

TCP communication
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
W Javie gniazda nasłuchowe reprezentowane są przez klasę ServerSocket, a gniazda połączeniowe przez Socket.

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();
Natomiast typowy scenariusz działania klienta przedstawia się tak:
  • 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