Strumienie, gniazda sieciowe i wątki - komunikator internetowy (zestaw I)
Ważnym obszarem zastosowań platformy Java jest programowanie aplikacji sieciowych. Java Platform API oferuje doskonałe wsparcie dla tego rodzaju zastosowań, w postaci klas bibliotecznych, pozwalających na wykorzystanie obiektów reprezentujących tego rodzaju pojęcia jak gniazda sieciowe, strumienie danych, czy wątki. Podstawowy system obsługi wejścia-wyjścia w Javie (pakiet java.io) jest oparty o cztery abstrakcyjne klasy biblioteczne InputStream, OutputStream, Reader i Writer (oraz odpowiednie klasy konkretne, np. BufferedReader, PrintWriter). InputStreami OutputStream reprezentują strumienie binarne, natomiast Reader i Writer reprezentują strumienie tekstowe. Nasza implementacja tekstowego komunikatora internetowego będzie wykorzystywała strumienie tekstowe.
ćw. 1
Poniżej znajdziesz kod źródłowy klas Serwer i Klient. Zestaw z nich dwie oddzielne jednostki kompilacji, skompiluj każdą z nich, uruchom (najpierw serwer, potem klienta) i przetestuj. Oczywiście ćwiczenie to możesz przeprowadzić także z kolegą/koleżanką używając dwóch komputerów (z dostępem do sieci).
Zarówno klient jak i serwer posiadają statyczne pole PORT przechowujące numer portu (int z przedziału 0 do 65535 - numery portów od 0 do 1023 są określane jako "well known ports" i zarezerwowane na standardowe usługi takie, jak www, ssh czy poczta elektroniczna, numery od 1024 do 49151 są określane jako "registered", a numery od 49152 do 65535 jako "dynamic/private"). Klasa Klient posiada także pole statyczne HOST przechowujące adres serwera z którym klient nawiąże połączenie.
import java.io.*; import java.net.*;
public class Serwer { public static final int PORT=50007; public static void main(String args[]) throws IOException { //tworzenie gniazda serwerowego ServerSocket serv; serv=new ServerSocket(PORT); //oczekiwanie na polaczenie i tworzenie gniazda sieciowego System.out.println("Nasluchuje: "+serv); Socket sock; sock=serv.accept(); System.out.println("Jest polaczenie: "+sock); //tworzenie strumienia danych pobieranych z gniazda sieciowego BufferedReader inp; inp=new BufferedReader(new InputStreamReader(sock.getInputStream())); //komunikacja - czytanie danych ze strumienia String str; str=inp.readLine(); System.out.println("<Nadeszlo:> " + str); //zamykanie polaczenia inp.close(); sock.close(); serv.close(); } } |
import java.io.*; import java.net.*;
public class Klient { public static final int PORT=50007; public static final String HOST = "127.0.0.1"; public static void main(String[] args) throws IOException { //nawiazanie polaczenia z serwerem Socket sock; sock=new Socket(HOST,PORT); System.out.println("Nawiazalem polaczenie: "+sock); //tworzenie strumieni danych pobieranych z klawiatury i dostarczanych do socketu BufferedReader klaw; klaw=new BufferedReader(new InputStreamReader(System.in)); PrintWriter outp; outp=new PrintWriter(sock.getOutputStream()); //komunikacja - czytanie danych z klawiatury i przekazywanie ich do strumienia System.out.print("<Wysylamy:> "); String str=klaw.readLine(); outp.println(str); outp.flush(); //zamykanie polaczenia klaw.close(); outp.close(); sock.close(); } } |
ćw. 2
Rozwiń powyższy przykład w taki sposób żeby po przesłaniu jednego komunikatu nie kończył działania, ale pozwalał klientowi na przesyłanie kolejnych komunikatów do serwera.
ćw. 3
Na podstawie powyższego przykładu i informacji z wykładu napisz prostą implementację tekstowego komunikatora internetowego działającego w trybie simpleks. Twoja implementacja powinna obejmować następujące etapy - (1) nawiązanie połączenia (ten etap został już zrobiony w przykładzie), (2) utworzenie obiektów reprezentujących strumienie danych pobieranych z klawiatury i z gniazda sieciowego oraz strumienie danych dostarczanych do gniazda sieciowego, (3) etap właściwej komunikacji w trybie simpleks (pętla wykonująca naprzemian odbieranie i wysyłanie komunikatów tekstowych) oraz (4) zakończenie komunikacji i (5) zamknięcie połączenia. Komunikacja powinna się odbywać do momentu przesłania przez którąkolwiek ze stron komunikatu "koniec" albo "KONIEC" (pomocna może tu być metoda equalsIgnoreCase() klasy String). Po przesłaniu takiego komunikatu zarówno klient jak i serwer powinny wypisać komunikat "Koniec polaczenia" i zakończyć działanie.
ćw. 4
Po stronie klienta obsłuż wyjątek java.net.UnknownHostException (try{}catch()) który wystąpi np. wtedy kiedy klient próbuje nawiązać połączenie z serwerem w sytuacji kiedy pod wskazanym adresem i numerem portu nie działa żaden serwer. W przypadku wystąpienia tego wyjątku Twoj komunikator powinien wypisać komunikat "Polaczenie zostalo przerwane" i zakończyć działanie.
ćw. 5
Po stronie klienta obsłuż wyjątek java.net.SocketException (try/catch) oznaczający wystąpienie błędu związanego z protokołem TCP. Wystąpienie tego wyjątku można zasymulować wyciągając wtyczkę z gniazda karty sieciowej.