Program działa na jednym komputerze, ale jest tak napisany aby działał po rozdzieleniu plików na dwa (klient – serwer) lub trzy (klient, proxy, serwer) komputery. Wtedy oczywiście trzeba by zmienić numery IP w plikach konfiguracyjnych. Plik MainKS
testuje układ Klient-Serwer-Klient. Plik MainKPS
testuje układ Klient-Proxy-Serwer-Proxy-Klient. Oba układy korzystają z innych plików konfiguracyjnych, co oczywiste bo musza podawać różne numery IP i portów.
Gdyby wykonywać program na trzech różnych komputerach można by użyć tych samych numerów portów na każdym komputerze. W przypadku emulacji układu na jednym komputerze porty nie mogą się powtarzać bo kompilator zgłosi błąd. Gdyby nie było nieskończonej pętli w metodzie run()
, a w metodzie wyslij()
Klienta w pętli wysłano by na przykład 20 wiadomości to Proxy i Serwer wykonały by tę czynność tylko raz, a w Kliencie metoda odbierz()
tez była by wykonana tylko raz, czyli wysłano by 20 wiadomości, ale wróciła by tylko jedna wiadomość. W metodzie run() Klient
a zastosowano warunki ograniczające dla wiadomości podane w pliku konfiguracyjnym.
Plik konfiguracyjny czytany przez Klienta
:
– IP
dotyczy komputera zewnętrznego do którego zostanie wysłana wiadomość
– portout
, dotyczy portu komputera zewnętrznego na który zostanie przesłana wiadomość
– portin
– dotyczy portu nasłuchującego wiadomości na komputerze Klient
– port wysyłający jest albo ustalany przez sam komputer albo tak jak jest tutaj – na z góry ustalonym porcie
– podane nr portów są bardzo wysokie i mieszczą się w przestrzeni adresowej nie używanej przez system lecz zostawionej na programy użytkownika. Niższe nr stwarzają ryzyko, ze system już używa tego portu i program wyrzuci błąd.
Program został bardzo szczegółowo omówiony w tekście.
Przesyłanie datagramów
Struktura systemu plików projektu została pokazana na rys. 163:
Pliki konfiguracyjne
klient1.txt
IP=127.0.0.1; portin=51500; portout=55500; port1in=0; port1out=0; count=5; interval=1000; preload=8; pattern=Komunikat specjalny; packetsize=19;
klient2.txt
IP=127.0.0.1; portout=52500; portin=51500; port1out=0; port1in=0; count=5; interval=1000; preload=8; pattern=Komunikat specjalny; packetsize=19;
proxy1.txt
IP=127.0.0.1; IP1=127.0.0.1; portin=52500; portout=55500; port1in=52600; port1out=51500;
server1.txt
IP=127.0.0.1; portin=55500; portout=51500; port1in=0; port1out=0;
server2.txt
IP=127.0.0.1; portin=55500; portout=52600;
Klasy
ConfigFileReader.java
package datagramy; import java.io.*; import java.util.*; enum Config {//wyliczenie zawierajace nazwy opcji z pliku konfiguracyjnego IP, IP1, portin, portout, port1in, port1out, count, interval, preload, pattern, packetsize } public class ConfigFileReader { public final HashMap<String, String> hm;//przechowuje klucz i wartosc z pliku //konfiguracyjnego public final ArrayList<String> al;//pomocnicza tablica public final File file;//plik konfiguracyjny public ConfigFileReader(File p_file) { file = p_file; hm = new HashMap<>();//inicjalizacja al = new ArrayList<>();//inicjalizacja read(file);//uruchomienie funkcji tokenize();//uruchomienie funkcji } // wczytywanie zawartosci pliku konfiguracyjnego do ArrayList //po podzieleniu na kolejne linie public void read(File p_file) { // StringBuilder sb = new StringBuilder(); try { try (BufferedReader in = new BufferedReader(new FileReader(file))) { String s;// while ((s = in.readLine()) != null) {//czyta kolejna linijke //sb.append(s);//dodaje linijke na koniec sb (te linijke //mozna skreslic razem z inicjalizujaca sb al.add(s); //dodaje linijke do Array List } } //zamkniecie strumienia } catch (IOException ioe) { throw new RuntimeException(ioe); } } //podzial na klucz np "count" i wartosc np. "10" public void tokenize() { String temp; String klucz; String value; int pos;//pozycja znaku "=" w stringu for (String s : al) {//iteracja po tablicy al;; temp = s;//pobiera element (linijke) pos = temp.indexOf('=');//sprawdza polozenie znaku "=" klucz = temp.substring(0, pos); //wczytuje to co jest przed "=" try { Enum.valueOf(Config.class, klucz);//sprawdza czy to //co jest przed "=" jest elementem wyliczenia Config czy nie //jesli nie: drukuje komunikat i przechodzi do nastepnego //iteratora. Jesli sprawdzenie nie generuje bledu to value = temp.substring(pos + 1, temp.length() - 1); //wczytuje //to co jest za "=" hm.put(klucz, value);//wstawia do hashmapy } catch (IllegalArgumentException iae) { //drukuje szczegoly bledu: niewlasciwa nazwe klucza i plik z //ktorego ta wartosc pochodzi System.out.println("Niewłaściwa nazwa klucza [" + klucz + "] w pliku " + file); } } } }
Klient.java
package datagramy; import java.io.*; import java.net.*; public class Klient implements Runnable { final File plik; final String nazwaWatku; final ConfigFileReader cfr; final Thread watek; InetAddress doceloweIP;//IP z pliku konfiguracyjnego int portDocelowy;//portout z pliku konfiguracyjnego int portNasluchujacy;//na tym komputerze: portin z pliku konfiguracyjnego int portWysylajacy;//na tym komputerze: dowolnie ustalony port wysylajacy DatagramSocket gniazdoWysylajace;//na tym komputerze: zamykane po wyslaniu DatagramSocket gniazdoNasluchujace;//na tym komputerze: nie zamykane; String wiadomoscWysylana; byte[] bufor;//wiadomosc w postaci tablicy bitow przygotowana do wysylki int wielkoscBufora; DatagramPacket datagramDoWyslania; DatagramPacket datagramOtrzymany; int liczbaPakietowWysylanych;//parametr count; int odstepCzasowy;//parametr interval; int pakietyBezOdpowiedzi;//parametr preload String komunikat;//parametr pattern int rozmiarPakietu;//parametr packetsize volatile int licznikWychodzacych; // volatile zapewnia pobranie //aktualnej wartosci z pamieci, a nie z pamieci typu cache volatile int licznikPrzychodzacych; volatile int roznica; public Klient(File p_plik) { plik = p_plik; nazwaWatku = "threadK"; cfr = new ConfigFileReader(plik);//odczytanie pliku konfiguracyjnego watek = new Thread(this, nazwaWatku);//utworzenie watku watek.start();//uruchomienie watku } @Override public void run() { przygotujPorty();//wykonywane raz while (true) {//nieskonczona petka if (licznikWychodzacych == liczbaPakietowWysylanych) { return;//sprawdzanie warunku okreslonego parametrem //count } przygotujWiadomosc(); wyslij(); odbierz(); roznica = licznikWychodzacych - licznikPrzychodzacych; if (roznica >= pakietyBezOdpowiedzi) { return;//wymuszenie reagowania na zagubione pakiety //jesli ich liczba jest wieksa niz okreslona parametrem //preload to run konczy dzialanie } try { Thread.sleep(odstepCzasowy);//wykonanie warunku //okreslonego w parametrze interval; } catch (InterruptedException e) { e.printStackTrace(); } } } public void przygotujPorty() { try { //czytanie parametrow z pliku konfiguracyjnego doceloweIP = InetAddress.getByName(cfr.hm.get("IP"));//ustalenie IP //wysylkowego } catch (UnknownHostException e) { e.printStackTrace(); } portDocelowy = Integer.parseInt(cfr.hm.get("portout"));//52500 portNasluchujacy = Integer.parseInt(cfr.hm.get("portin"));//51500 liczbaPakietowWysylanych = Integer.parseInt(cfr.hm.get("count")); odstepCzasowy = Integer.parseInt(cfr.hm.get("interval")); pakietyBezOdpowiedzi = Integer.parseInt(cfr.hm.get("preload")); komunikat = cfr.hm.get("pattern"); rozmiarPakietu = Integer.parseInt(cfr.hm.get("packetsize")); try { gniazdoWysylajace = new DatagramSocket(51100);//utworzenie //gniazda na tym porcie do wysylania wiadomosci } catch (SocketException e) { e.printStackTrace(); } try { gniazdoNasluchujace = new DatagramSocket(portNasluchujacy); //gniazda na tym porcie do nasluchiwania wiadomosci } catch (SocketException e) { e.printStackTrace(); } } public void przygotujWiadomosc() { wiadomoscWysylana = komunikat;//tresc wiadomosci pobrana z pliku //konfiguracyjnego bufor = wiadomoscWysylana.getBytes();//przetworzenie na tablice bitow wielkoscBufora = bufor.length;//okreslenie wielkosci bufora //wielkoscBufora = packetsize/ kreslenie wielkosci bufora - uzycie //wielkosci z pliku konfiguracyjnego moze skonczyc sie zawieszeniem //komputera; datagramDoWyslania = new DatagramPacket(bufor, wielkoscBufora, doceloweIP, portDocelowy); } public void wyslij() { try { System.out.println("Wiadomość: " + (licznikWychodzacych + 1)); gniazdoWysylajace.send(datagramDoWyslania);//wylanie wiadomosci } catch (IOException e) { e.printStackTrace(); } //noinspection NonAtomicOperationOnVolatileField licznikWychodzacych++; System.out.println("Klient: Wysłałam wiadomość "); } public void odbierz() { int wielkoscBufora1 = 100; byte[] bufor1 = new byte[wielkoscBufora1]; datagramOtrzymany = new DatagramPacket(bufor1, wielkoscBufora1); try { gniazdoNasluchujace.receive(datagramOtrzymany);//odebranie wiadomosci } catch (IOException e) { e.printStackTrace(); } System.out.println("Klient: Otrzymałam wiadomość "); //noinspection NonAtomicOperationOnVolatileField licznikPrzychodzacych++; } }
MainKPS.java
package datagramy; import java.io.*; import datagramy.*; public class MainKPS { public static void main(String[] args) { new Klient(new File("nets/src/assets/klient2.txt")); new Proxy(new File("nets/src/assets/proxy1.txt")); new Serwer(new File("nets/src/assets/serwer2.txt")); } }
MainKS.java
package datagramy; import java.io.*; import datagramy.*; public class MainKS { public static void main(String[] args) { new Klient(new File("nets/src/assets/klient1.txt")); new Serwer(new File("nets/src/assets/serwer1.txt")); } }
Proxy.java
package datagramy; import java.io.*; import java.net.*; public class Proxy implements Runnable { final File plik; final String nazwaWatku; final ConfigFileReader cfr; final Thread watek; InetAddress doceloweIP; InetAddress doceloweIP1; int portDocelowy;//portout z pliku konfiguracyjnego int portNasluchujacy;//na tym komputerze: portin z pliku konfiguracyjnego int portDocelowy1;//portout1 z pliku konfiguracyjnego int portNasluchujacy1;//na tym komputerze: portin2 z pliku konfiguracyjnego int portWysylajacy;//na tym komputerze: dowolnie ustalony port wysylajacy int portWysylajacy1; //j.w. nie jest potrzebny - moznaby wysylac z jednego //ale dla unikniecia kolejek ... DatagramSocket gniazdoNasluchujace; DatagramSocket gniazdoNasluchujace1; DatagramSocket gniazdoWysylajace; DatagramSocket gniazdoWysylajace1; DatagramPacket datagramOtrzymany; DatagramPacket datagramOtrzymany1; DatagramPacket datagramDoWyslania; DatagramPacket datagramDoWyslania1; byte[] bufor;//bufory na wiadomosci przychodzace, wychodzace i tymczaoswe int wielkoscBufora; byte[] bufor1; int wielkoscBufora1; byte[] bufor2; int wielkoscBufora2; byte[] bufor3; int wielkoscBufora3; byte[] bufor4; int wielkoscBufora4; public Proxy(File p_plik) { plik = p_plik; nazwaWatku = "threadP"; cfr = new ConfigFileReader(plik);//czyta dane konfiguracyjne watek = new Thread(this, nazwaWatku); watek.start(); } @Override public void run() { przygotujPorty();//uruchmia tylko raz while (true) {//nieskonczona petla odbierz(); przygotujWiadomosc(); wyslij(); odbierz1(); przygotujWiadomosc1(); wyslij1(); } } public void przygotujPorty() {//pobiera dane z cfr o wymaganej konfiguracji //sprzetu try { doceloweIP = InetAddress.getByName(cfr.hm.get("IP")); doceloweIP1 = InetAddress.getByName(cfr.hm.get("IP1")); } catch (UnknownHostException e) { e.printStackTrace(); } portDocelowy = Integer.parseInt(cfr.hm.get("portout"));//55500 portNasluchujacy = Integer.parseInt(cfr.hm.get("portin"));//52500 portDocelowy1 = Integer.parseInt(cfr.hm.get("port1out"));//51500 portNasluchujacy1 = Integer.parseInt(cfr.hm.get("port1in"));//52600 try {//tworzy dwa gniazda do obioru wiadomosci z serwera i od klienta gniazdoNasluchujace = new DatagramSocket(portNasluchujacy); gniazdoNasluchujace1 = new DatagramSocket(portNasluchujacy1); } catch (SocketException e) { e.printStackTrace(); } try {//tworzy gniazda wysylajace - o narzuconych numerach - //jesli numerkow nie ma komputer sam wybiera port gniazdoWysylajace = new DatagramSocket(52100); gniazdoWysylajace1 = new DatagramSocket(52200); } catch (SocketException e) { e.printStackTrace(); } } public void odbierz() { //odebranie wiadomosci wielkoscBufora = 50;//wielkosc bufora dla datagramu bufor = new byte[wielkoscBufora];//tablica na dane //tworzy puste datagramy dla wiadomosci datagramOtrzymany = new DatagramPacket(bufor, wielkoscBufora); try {//pobiera datagramy z gniazda, gniazda nie sa zamykane gniazdoNasluchujace.receive(datagramOtrzymany); } catch (IOException e) { e.printStackTrace(); } //pochwal sie wiadomosciami System.out.println("Proxy: Otrzymałam wiadomość "); } public void odbierz1() { bufor1 = new byte[wielkoscBufora]; //tworzy puste datagramy dla wiadomosci datagramOtrzymany1 = new DatagramPacket(bufor1, wielkoscBufora); try {//odbierz wiadomosc gniazdoNasluchujace1.receive(datagramOtrzymany1); } catch (IOException e) { e.printStackTrace(); } //pochwal sie wiadomosciami System.out.println("Proxy: Otrzymałam wiadomoœæ "); } public void przygotujWiadomosc() { //jak w kliencie bufor3 = new String(datagramOtrzymany.getData()).getBytes(); wielkoscBufora3 = bufor3.length; datagramDoWyslania = new DatagramPacket(bufor3, wielkoscBufora2, doceloweIP, portDocelowy); } public void przygotujWiadomosc1() { //jak w kliencie bufor4 = new String(datagramOtrzymany.getData()).getBytes(); wielkoscBufora4 = bufor4.length; datagramDoWyslania1 = new DatagramPacket(bufor4, wielkoscBufora3, doceloweIP1, portDocelowy1); } public void wyslij() { try { //wysylanie TAM gniazdoWysylajace.send(datagramDoWyslania); } catch (IOException e) { e.printStackTrace(); } System.out.println("Proxy: Wysłałam wiadomość "); } public void wyslij1() { try {//wysylanie Z POWROTEM gniazdoWysylajace1.send(datagramDoWyslania1); } catch (IOException e) { e.printStackTrace(); } System.out.println("Proxy: Wysłałam wiadomość "); } }
Server.java
package datagramy; import java.io.*; import java.net.*; public class Serwer implements Runnable { final File plik; final String nazwaWatku; final ConfigFileReader cfr; final Thread watek; InetAddress doceloweIP;//IP z pliku konfiguracyjnego int portDocelowy;//portout z pliku konfiguracyjnego int portNasluchujacy;//na tym komputerze: portin z pliku konfiguracyjnego int portWysylajacy;//na tym komputerze: dowolnie ustalony port wysylajacy DatagramSocket gniazdoWysylajace;//na tym komputerze DatagramSocket gniazdoNasluchujace;//na tym komputerze; String wiadomoscWysylana; byte[] bufor;//wiadomosc w postaci tablicy bitow przygotowana do wysylki int wielkoscBufora; byte[] bufor1;//wiadomosc do odeslania po przetworzeniu int wielkoscBufora1; DatagramPacket datagramDoWyslania; DatagramPacket datagramOtrzymany; public Serwer(File p_plik) { plik = p_plik; nazwaWatku = "threadS"; cfr = new ConfigFileReader(plik);//odczytanie pliku konfiguracyjnego watek = new Thread(this, nazwaWatku); watek.start();//uruchomienie watku } @Override public void run() { //wykonuje kolejno metody przygotujPorty();//wykonuje jeden raz while (true) {// //tworzy nieskonczona petle inaczej program wykonalby sie //jeden raz odbierz(); przygotujWiadomosc(); wyslij(); } } public void przygotujPorty() { try { //czytanie wartosci z pliku konfiguracyjnego doceloweIP = InetAddress.getByName(cfr.hm.get("IP"));//ustalenie IP //wysylkowego } catch (UnknownHostException e) { e.printStackTrace(); } portDocelowy = Integer.parseInt(cfr.hm.get("portout"));//51500 portNasluchujacy = Integer.parseInt(cfr.hm.get("portin"));//55500 try { gniazdoNasluchujace = new DatagramSocket(portNasluchujacy);//utworzzenie //gniazda na tym porcie do nasluchu wiadomosci } catch (SocketException e) { e.printStackTrace(); } try { gniazdoWysylajace = new DatagramSocket(55100);//utworzenie //gniazda na tym porcie do wysylania wiadomosci - numer ustalony //arbitralnie. Gdyby numerka nie bylo //komputer sam wybralby pierwszy nie zajety port } catch (SocketException e) { e.printStackTrace(); } } public void odbierz() {//odbiera wiadomosc wielkoscBufora = 150;//bufor dla wiadomosci bufor = new byte[wielkoscBufora1]; ///tablica bitow na wiadomosc datagramOtrzymany = new DatagramPacket(bufor, wielkoscBufora1);//pusty //datagram na wiadomosc try { gniazdoNasluchujace.receive(datagramOtrzymany);//odebranie wiadomosci //port dziala jakis czas - mozna ten czas okreslic } catch (IOException e) { e.printStackTrace(); } System.out.println("Serwer: Otrzymałam wiadomość "); } public void przygotujWiadomosc() {//podmiana wiadomosci. Mozna uzyc //tej samej wiadomosci, a zmienic adres w wiadomosci(metoda //setAddress()) i port docelowy (setPort), ale ja wybralem bardziej //wyrazna metode odczytu starej i utworzenia nowej wiadomosci - //identycznej ze stara; //dodac cos do wiadomosci np. + "A kuku!" bufor1 = new String(datagramOtrzymany.getData()).getBytes(); wielkoscBufora1 = bufor1.length; datagramDoWyslania = new DatagramPacket(bufor1, wielkoscBufora1, doceloweIP, portDocelowy);//przygotowanie datagramu } public void wyslij() { try { gniazdoWysylajace.send(datagramDoWyslania);//wyslanie wiadomosci } catch (IOException e) { e.printStackTrace(); } System.out.println("Serwer: Wysłałam wiadomość "); } }
module-info.java
Plik pusty
Wyniki
Klasa MaiKPS
Po uruchomieniu klasy na konsoli otrzymujemy:
Wiadomość: 1 title="Jak przesłać datagram" Klient: Wysłałam wiadomość Proxy: Otrzymałam wiadomość Proxy: Wysłałam wiadomość Serwer: Otrzymałam wiadomość Serwer: Wysłałam wiadomość Proxy: Otrzymałam wiadomoœæ Proxy: Wysłałam wiadomość Klient: Otrzymałam wiadomość Wiadomość: 2 Klient: Wysłałam wiadomość Proxy: Otrzymałam wiadomość Proxy: Wysłałam wiadomość Serwer: Otrzymałam wiadomość Serwer: Wysłałam wiadomość Proxy: Otrzymałam wiadomoœæ Proxy: Wysłałam wiadomość Klient: Otrzymałam wiadomość Wiadomość: 3 Klient: Wysłałam wiadomość Proxy: Otrzymałam wiadomość Proxy: Wysłałam wiadomość Serwer: Otrzymałam wiadomość Serwer: Wysłałam wiadomość Proxy: Otrzymałam wiadomoœæ Proxy: Wysłałam wiadomość Klient: Otrzymałam wiadomość Wiadomość: 4 Proxy: Otrzymałam wiadomość Klient: Wysłałam wiadomość Serwer: Otrzymałam wiadomość Serwer: Wysłałam wiadomość Proxy: Wysłałam wiadomość Proxy: Otrzymałam wiadomoœæ Proxy: Wysłałam wiadomość Klient: Otrzymałam wiadomość Wiadomość: 5 Klient: Wysłałam wiadomość Proxy: Otrzymałam wiadomość Proxy: Wysłałam wiadomość Serwer: Otrzymałam wiadomość Serwer: Wysłałam wiadomość Proxy: Otrzymałam wiadomość Proxy: Wysłałam wiadomość Klient: Otrzymałam wiadomość
Klasa MainKS.java
Po uruchomieniu klasy na konsoli otrzymujemy:
Wiadomość: 1 Klient: Wysłałam wiadomość Serwer: Otrzymałam wiadomość Serwer: Wysłałam wiadomość Klient: Otrzymałam wiadomość Wiadomość: 2 Klient: Wysłałam wiadomość Serwer: Otrzymałam wiadomość Serwer: Wysłałam wiadomość Klient: Otrzymałam wiadomość Wiadomość: 3 Serwer: Otrzymałam wiadomość Serwer: Wysłałam wiadomość Klient: Wysłałam wiadomość Klient: Otrzymałam wiadomość Wiadomość: 4 Klient: Wysłałam wiadomość Serwer: Otrzymałam wiadomość Serwer: Wysłałam wiadomość Klient: Otrzymałam wiadomość Wiadomość: 5 Serwer: Otrzymałam wiadomość Serwer: Wysłałam wiadomość Klient: Wysłałam wiadomość Klient: Otrzymałam wiadomość