Programowanie obiektowe Komunikacja z użyciem gniazd aplikacje klient-serwer Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki Politechniki Wrocławskiej pawel.rogalinski @ pwr.wroc.pl Architektura klient-serwer Architektura klient-serwer to technologia budowy systemów informatycznych, w których współdziałają dwie kategorie programów (procesów): klienci i serwery. Klient to program (proces), który łączy się z innym programem (procesem) zwanym serwerem, i poprzez kanały komunikacyjne zleca mu wykonanie określonych działań (np. dostarczenie pewnych danych). Serwer to program (proces), który na zlecenie klientów świadczy określone usługi (np. dostarcza określone dane lub wyniki przetwarzania przesłanych danych). Pojęcie klient-serwer jest natury softwarowej, a nie hardwarowej tzn. dotyczy programów (procesów lub wątków) a nie sprzętu. Architekturę klient serwer możemy zastosować do budowy systemu działającego na jednym komputerze w jednym systemie operacyjnym, a nawet systemu składającego się z jednego programu. Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr1 / 25 Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr2 / 25 Architektura klient-serwer Komunikację pomiędzy procesami-serwerami i procesami-klientami w różnych systemach operacyjnych zapewniają mechanizmy komunikacji międzyprocesowej (IPC interprocess communication): potoki, wspólne (dzielone) obszary pamięci, kolejki komunikatów, semafory. Powyższe środki współdziałania aplikacji klient-serwer mają dwie wady: są różne, co do sposobu i zakresu, w różnych systemach operacyjnych, nie dają się łatwo uogólniać na interakcję pomiędzy serwerami i klientami w środowisku sieciowym Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr3 / 25 Protokoły interakcji sieciowej Komunikację w środowisku sieciowym zapewniają specjalne protokoły interakcji sieciowej. Protokół to swoisty język i zasady, za pomocą których komunikują się programy i procesy, w szczególności serwery i klienci. Protokoły interakcji sieciowej są podzielone na warstwy, które wyznaczają poziomy komunikacji. Dzięki temu złożony problem interakcji sieciowej jest podzielony na podproblemy, za rozwiązanie których odpowiadają protokoły poszczególnych warstw. Model ISO OSI ogólny standard podziału komunikacji sieciowej na 7 warstw współpracujących ze sobą w ściśle określony sposób. (OSI - Open Standards Interconnection) Model ISO - OSI jest traktowany jako model odniesienia (wzorzec) dla większości rodzin protokołów komunikacyjnych. Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr4 / 25
Model ISO - OSI Model TCP/IP Typową realizacją modelu OSI stosowaną w Internecie jest Model TCP/IP, który wykorzystuje protokoły TCP i UDP w warstwie transportoerj oraz protokół IP w warstwie sieciowej Warstwa aplikacji, prezentacji i sesji HTTP, FTP, SMTP, DNS, RMI, CORBA, RPC, SOAP Warstwa transportowa TCP UDP gniazda (sockets) gniazda (sockets) TCP UDP Warstwa sieciowa IP sieć IP Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr5 / 25 Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr6 / 25 Protokół TCP/IP Protokół TCP/IP Protokół IP (Internet Protokole) protokół bezpołączeniowy (zawodny), który umożliwia przesyłanie pakietów pomiędzy komputerami połączonymi w Internecie i identyfikowanymi za pomocą adresu IP. Protokół TCP (Transport Control Protocol) - protokół połączeniowy, który zapewnia że dane posyłane przez gniazda docierają w całości i w odpowiedniej kolejności. Protokół UDP (User Datagrams Protocol) protokół bezpołączeniowy, w którym dane są przesyłane jako datagramy, które mogą docierać w dowolnej kolejności, lub mogą zostać zagubione. Protokoły TCP i UDP są protokołami point-to-point czyli zapewniają komunikację pomiędzy dwoma procesami identyfikowanymi za pomocą portów. Komunikacja implementowana jest za pomocą gniazd, które mają przypisany określony numer portu. port port port serwer (nr. IP) pakiet TCP/UDP port nr. IP dane Komunikacja klientów z serwerem poprzez porty Identyfikacja hosta nr. IP Identyfikacja aplikacji nr. portu klient klient klient Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr7 / 25 Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr8 / 25
Model komunikacji klient-serwer Model komunikacji klient-serwer Serwer tworzy gniazdo związane z określonym portem i na tym kanale komunikacyjnym czeka na prośbę połączenia od klienta. Inicjatywa połączenia wychodzi od klienta. Klient musi znać host serwera oraz numer portu otwartego do przyjmowania połączeń. Podaje te informacje przy tworzeniu gniazda. Serwer akceptuje połączenie od klienta i aby pozostać dostępnym dla innych klientów na kanale połączeniowym, tworzy inne gniazdo do komunikacji z danym klientem. Z punktu widzenia klienta jest to samo gniazdo, na którym zainicjowano połączenie. Strumienie wejściowy i wyjściowy związane z tym gniazdem służą do komunikacji pomiędzy klientem i serwerem. KLIENT gniazdo, port IN OUT strumienie 1 inicjacja połączenia 3 wymiana danych Socket SERWER gniazdo serwera, port 2334 akceptacja i utworzenie gniazda 2 gniazdo do komunikacji IN OUT strumienie ServerSocket Gniazda serwerowe używane przy programowaniu serwerów (w Javie klasa ServerSocket ) Gniazda klienckie używane w programowaniu klientów oraz serwerów po zaakceptowaniu połączenia (w Javie klasa Socket ) Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr9 / 25 Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr10 / 25 Aplikacja klienta (szablon) Oprogramowanie klienta, komunikującego się z serwerem, polega na wykonaniu następujących czynności: utworzenie gniazda czyli obiektu klasy Socket, pobranie od tego obiektu strumieni wejściowego i wyjściowego, związanych z gniazdem, posyłanie zleceń do serwera poprzez zapis danych do strumienia wyjściowego gniazda odczytywanie odpowiedzi serwera poprzez odczyt ze strumienia wejściowego, zamknięcie strumieni zamknięcie gniazda. Operacje zapisu i odczytu można wielokrotnie powtarzać. Strumienie wejściowy i wyjściowy można opakowywać by zapewnić określony rodzaj przetwarzania np. buforowanie, kodowanie, odczyt danych binarnych. Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr11 / 25 Aplikacja klienta (szablon) String serverhost = "localhost"; // adres IP serwera int serverport = 12312; // numer portu, na którym // nasłuchuje serwer Socket socket = new Socket(serverHost, serverport); // uzyskanie strumieni do komunikacji OutputStream sockout = socket.getoutputstream(); InputStream sockin = socket.getinputstream(); // wysłanie zlecenia do serwera sockout.write(...); // odczyt odpowiedzi serwera sockin.read(...); // zamknięcie strumieni i gniazda sockout.close(); sockin.close(); socket.close(); catch (UnknownHostException e) { // nieznany host catch(socketexception e){ // wyjątki związane z komunikacją przez gniazdo catch (IOException e) { // inne wejątki wej/wyj Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr12 / 25
Aplikacja serwera (szablon) Typowe działanie serwera polega na wykonaniu następujących czynności: utworzenie gniazda serwera czyli obiektu klasy ServerSocket, związanie gniazda z określonym adresem i numerem portu, oczekiwanie na połączenie od klienta na tym gnieździe, utworzenie nowego gniazda do wymiany informacji z klientem czyli obiektu klasy Socket, pobranie strumieni wejściowego i wyjściowego, związanych z nowym gniazdem, odczytywanie zleceń klienta poprzez odczyt ze strumienia wejściowego, posyłanie odpowiedzi do klienta poprzez zapis danych do strumienia wyjściowego, zamknięcie strumieni oraz gniazda do komunikacji z klientem kontynuacja nasłuchu połączeń od innych klientów na gnieździe serwera zamknięcie gniazda serwera. Aplikacja serwera (szablon) Operacje związane z nowym gniazdem mogą być wykonywane w dodatkowym wątku współbieżnie z wątkiem realizującym nasłuch na gnieździe serwera. Operacje odczytu i zapisu można wielokrotnie powtarzać. Strumienie wejściowy i wyjściowy można opakowywać by zapewnić określony rodzaj przetwarzania np. buforowanie, kodowanie, odczyt danych binarnych. Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr13 / 25 Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr14 / 25 Aplikacja serwera (szablon) try{ String host = "localhost"; // nazwa hosta, na którym pracuje serwer int port = 12312; // numer portu, na którym nasłuchuje serwer //Utworzenie gniazda serwera ServerSocket serversock = new ServerSocket(port); // Akceptacja połączeń od klientów // Zwykle powtarzane w pętli w oczekiwaniu na kolejnych klientów while(true){ // Akceptacja połączenia i utworzenia nowego gniazda Socket socket = serversock.accept(); // uzyskanie strumieni do komunikacji OutputStream sockout = socket.getoutputstream(); InputStream sockin = socket.getinputstream(); // odczyt zlecenia oraz wysłanie odpowiedzi do klienta sockin.read(...); sockout.write(...); // zamknięcie strumieni i gniazda sockout.close(); sockin.close(); socket.close(); serversock.close(); // Zamknięcie gniazda serwera catch(ioexception){ // obsługa wyjątków Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr15 / 25 Książka telefoniczna typu klient-serwer Założenia Aplikacja składa się z trzech klas: PhoneBook implementacja operacji na kolekcji typu ConcurrentMap, w której są przechowywane pary <imię, numer telefonu> PhoneBookServer implementacja serwera, przyjmującego zlecenia od klientów i wykonującego operacje na kolekcji PhoneBookClient implementacja prostego klienta wysyłającego ciąg zleceń do klienta Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr16 / 25
Książka telefoniczna typu klient-serwer Założenia (cd) Polecenia klienta akceptowane przez serwer: LOAD nazwa_pliku - wczytanie danych z pliku o podanej nazwie, SAVE nazwa_pliku - zapis danych do pliku o podanej nazwie, GET imię - pobranie numeru telefonu osoby o podanym imieniu, PUT imię numer - zapis numeru telefonu osoby o podanym imieniu, REPLACE imie numer - zmiana numeru telefonu dla osoby o podanym mieniu, DELETE imie - usunięcie z kolekcji osoby o podanym imieniu, LIST - przesłanie listy imion, które są zapamiętane w kolekcji, CLOSE - zakończenie nasłuchu połączeń od nowych klientów i zamknięcie gniazda serwera BYE - zakończenie komunikacji klienta z serwerem i zamknięcie strumieni danych oraz gniazda. Książka telefoniczna typu klient-serwer Założenia (cd) Odpowiedzi serwera wysyłane do klienta: ERROR komunikat - podczas wykonywania ostatniego polecenia wystąpił błąd komunikat zawiera informację o przyczynie błędu, OK - polecenie LOAD, SAVE, PUT, REPLACE, DELETE, CLOSE, BYE zostało wykonane poprawnie, OK numer - polecenie GET zostało wykonane poprawnie numer zawiera numer osoby, której imię było podane w poleceniu, OK imie1 imie2 - polecenie LIST zostało wykonane poprawnie, - komunikat zawiera listę imion osób pamiętanych w kolekcji. Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr17 / 25 Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr18 / 25 Klasa PhoneBook public class PhoneBook { // Kolekcja do przechowywania par <imie numer> private ConcurrentMap<String, String> contacts = new ConcurrentHashMap<String, String>(); // Implementacja polecenia LOAD public String LOAD(String fname) { BufferedReader br = new BufferedReader(new FileReader(fname)); String line; String command[]; while ((line = br.readline())!= null) { command = line.split(" +"); contacts.put(command[0], command[1]); br.close(); catch (FileNotFoundException e) { return "ERROR Nie znaleziono pliku " + fname; catch (IOException e) { return "ERROR Błąd podczas odczytu z pliku " + fname; Klasa PhoneBook (cd) // Implementacja polecenia SAVE public String SAVE(String fname) { PrintWriter out = new PrintWriter(fname); Iterator<String> iter = contacts.keyset().iterator(); while (iter.hasnext()) { String name = iter.next(); String phone = contacts.get(name); out.println(name + " " + phone); out.close(); catch (FileNotFoundException e) { return "ERROR Błąd podczas zapisu do pliku " + fname; // Implementacja polecenia GET public String GET(String name){ String phone; if ((phone = contacts.get(name)) == null) return "ERROR Nieznane imię " + name; return "OK " + phone; Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr19 / 25 Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr20 / 25
Klasa PhoneBook (cd) Klasa PhoneBook (cd) // Implementacja polecenia PUT public String PUT(String name, String phone) { if (contacts.containskey(name)) return "ERROR Imię " + name + " już istnieje"; contacts.put(name, phone); // Implementacja polecenia REPLACE public String REPLACE(String name, String phone) { if (contacts.containskey(name)==false) return "ERROR Nie ma imienia " + name; contacts.put(name, phone); // Implementacja polecenia LIST public String LIST(){ String lista[]; lista = contacts.keyset().toarray(new String[0]); String response = "OK"; for(string s : lista){ response += " " + s; return response; // Implementacja polecenia DELETE public String DELETE(String name){ if (contacts.remove(name) == null) return "ERROR Nie ma imienia " + name; Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr21 / 25 Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr22 / 25 Klasa PhoneBookServer public class PhoneBookServer { static final int PORT_NUMBER = 12315; // Nr. portu, na którym słucha serwer private PhoneBook phonebook = new PhoneBook(); private ServerSocket serversock = null; Klasa PhoneBookServer (cd) public PhoneBookServer() { serversock = new ServerSocket(PORT_NUMBER); System.out.println("Serwer uruchomiony na porcie "+ PORT_NUMBER); while (true) { if (serversock.isclosed()) break; public static void main(string[] args) { new PhoneBookServer(); Socket socket = serversock.accept(); if (socket!= null) { System.out.println("Połączenie nawiązane"); serviceclient(socket); else break; if (serversock.isclosed()==false) serversock.close(); serversock = null; System.out.println("Serwer został zamknięty"); catch (IOException e) { e.printstacktrace(); Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr23 / 25 Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr24 / 25
Klasa PhoneBookServer (cd) public void serviceclient(socket socket){ BufferedReader in; PrintWriter out; String line, response; String [] command; in = new BufferedReader( new InputStreamReader(socket.getInputStream())); out = new PrintWriter(socket.getOutputStream()); while((line = in.readline())!= null){ System.out.println("Server: <<== " + line); command = line.split(" +"); // Podziel polecenie na słowa if(command[0].equals("load")){ response = phonebook.load(command[1]); else if (command[0].equals("xxx")) // Pozostałe polecenia response = phonebook.xxx(...); else{ response = "ERROR Nieznana kommenda"; System.out.println("Server: ==>> " + response); out.println(response); out.flush(); if(command[0].equals("bye")) break; // wyjście z pętli while in.close(); out.close(); socket.close(); System.out.println("Polączenie z klientem zostało zamknięte" ); catch (IOException e) { Komunikacja z użyciem gniazd aplikacje klient-serwer Autor: Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki PWr25 / 25