Portmapper z RPC po TCP

Stworzyć prostą aplikację portmapera z RPC dla komunikacji TCP. Przyjmujemy, że wszystkie usługi rejetrowane w portmaperze wczytują zawsze jedną linię od klienta, po czym odsyłają zawsze jedną linię z odpowiedzią. Portmapper powinien działać na ustalonym porcie (domyslnie 12345) i obsługiwać następujące komendy (wysyłane tekstowo w jednej lini):
  1. REGISTER nazwa ip port
    gdzie nazwa to nazwa usługi rejestrowanej w portmaperze, a ip i port to adres i port, na którym działa usługa; komenda ta powinna zarejestrować usługę pod zadaną nazwą i odpowiedzieć klientowi rejestrującemu usługę jedną linjką z napisaem "OK", w przypadku powodzenia zarejestrowania usługi, lub informacją o błędzie w przeciwnym razie
  2. GET nazwa
    gdzie nazwa to nazwa usługi zarejestrowanej uprzednio w portmaperze; komenda ta powinna odpowiedzieć klientowi jedną linijką w formacie "ip port" zawierającą adres ip i port zarejestrowanej usługi
  3. CALL nazwa args...
    gdzie nazwa to nazwa usługi zarejestrowanej uprzednio w portmaperze; komenda ta powinna połączyć się z usługą nazwa zarejestrowaną uprzednio w portmaperze pod zadanym adresem ip i portem, przesłać jej linię "args..." (tj. podnapisa linni komendy CALL począwszy od pierwszego niebiałego znaku po nazwie usługi), odebrać linię odpowiedzi i przekazać ja klientowi wywołującemu komendę

Pierwsze zadanie zaliczeniowe

Pojawiła się finalna wersja pierwszego zadania zaliczeniowego.

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.

Zadanie ćwiczeniowe z TCP

  1. Stworzyć aplikację klienta, która wczyta dwie liczby naturalne 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ć aplikację serwera: serwer powinien wczytać dwie liczby X, Y od klienta i odesłać jedną linię zawierającą sumę X i Y.

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

Warriors of the Net

Film "Warriors of the Net" można ściągnąć z nasepującej strony: www.warriorsofthe.net

Pliki

Wielkość datagramu (UDP): UDP.java
Przykład prostego klienta (UDP): UDPClient.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