Zielony Smok - logo witryny

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() Klienta 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:

Struktura plików projektu
Rys. 163. Struktura plików projektu

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ść