Przetwarzanie równoległe
i rozproszone
laboratorium
Opracowanie:
Ewa Hadam
Bartłomiej Haraszczuk
Rzeszów, 2007
Ćwiczenie 1
Przygotowanie środowiska JAVA
Aby możliwe było wykonywanie wyznaczonych zadań należy zainstalować oraz odpowiednio skonfigurować środowisko JavaJDK. Nie musieliśmy tego robić, gdyż środowisko to zostało już zainstalowane na stanowiskach laboratoryjnych.
Program możemy przygotować w dowolnym edytorze tekstowym jednak najlepiej w edytorze typu Notepad++, który odpowiednio koloruje kod programu i łatwiej wtedy znaleźć ewentualne błędy.
Polecenie kompilacji:
java nazwa_pliku_źródłowego.java
Polecenie uruchomienia:
java nazwa_pliku_skompilowanego
Przy kompilacji i uruchamianiu programu należy zwracać szczególną uwagę na małe i duże litery.
Przykład1
Uruchomienie pliku HelloWorld.java o kodzie źródłowym:
Class HelloWorld{
public static void main(String args[]){
System.out.println(“Hello World”);
}
}
Wynikiem działania tego programu jest pojawienie się komunikatu: Hello Word.
Przykład2
Uruchomienie pliku ArithmeticTest.java o kodzie źródłowym:
class ArithmeticTest {
public static void main (String[] args) {
short x = 6; //definiowanie zmiennych
int y = 4;
float a = 12.5f;
float b = 7f;
System.out.println("x rowna sie " + x + ", y rowna sie "+ y);//wypisanie //wartości zmiennych x oraz y
System.out.println("x + y = " + (x + y));//wykonanie operacji dodawania
System.out.println("x - y = " + (x - y));//wykonanie operacji odejmowania
System.out.println("x / y = " + (x / y));//wykonanie operacji dzielenia //całkowitego
System.out.println("x % y = " + (x % y));//wykonanie operacji dzielenia
//z resztą
System.out.println("a rowna sie " + a + ", b rowna sie "+ b);//wypisanie //wartości zmiennych a oraz b
System.out.println("a / b = " + (a / b));//wykonanie operacji dzielenia
}
Jak widać przykład ten miał na celu sprawdzenie operatorów matematycznych czyli takich które służą do wykonywania operacji na argumentach.
Operatory to elementy języka służące do generacji nowych wartości na podstawie podanych argumentów (jeden lub więcej). Operator wiąże się więc najczęściej z określonym działaniem na zmiennych. W języku Java występują operatory takie jak w C/C++ i tak samo się je stosuje. Różnica polega na tym, że Java w wyniku dzielenia liczb całkowitych nie zaokrągla wyników do najbliższej wartości całkowitej, ale obcina do liczby całkowitej powstałą liczbę. W Javie podobnie jak w C dodatkowym elementem wykonywania operacji matematycznych jest skrócony zapis tych operacji pod warunkiem, że jest wykonywana operacja na zmiennej, która przechowuje zarazem wynik tej operacji.
Przykład3
Uruchomienie pliku PrePostFix.java o kodzie źródłowym:
class PrePostFixTest {
public static void main (String args[]) {
int x = 0;
int y = 0;
System.out.println("x i y wynosza " + x + " i " + y );
x++; //najpierw wykonanie operacji a następnie zwiększenie zmiennej
System.out.println("x++ daje w wyniku " + x);
++x; //najpierw zwiększenie zmiennej a następnie wykonanie operacji
System.out.println("++x daje w wyniku " + x);
System.out.println("Ustawiamy x na 0.");
x = 0;
System.out.println("------");
y = x++;
System.out.println("y = x++ (postikementacja) daje w wyniku:");
System.out.println("x wynosi " + x);
System.out.println("y wynosi " + y);
System.out.println("------");
y = ++x;
System.out.println("y = ++x (preinkrementacja) daje w wyniku:");
System.out.println("x wynosi " + x);
System.out.println("y wynosi " + y);
System.out.println("------");
}
}
Przykład ten pokazuje działanie i sposób używania operatorów postfixowych i prefixowych.
Zadanie1
Napisać program symulacji binaryzacji.
/** Klasa reprezentująca obrazek o rozmiarach 10x10 */
public class Obraz
{
/** konstruktor , inicjowanie tablicy*/
Obraz()
{
int[][] obraz = new int[10][10];
for (int i = 0; i < 10; i++)
for (int j = 0; j < 10; j++)
obraz[i][j] = (int)(Math.random() * 255);
}
/** Metoda przeprowadzająca binaryzuje obrazu */
public void Bin ()
{
int p = 100; //próg którym kierujemy się przy przetwarzaniu //tablicy
for (int i = 0; i < 10; i++)
for (int j = 0; j < 10; j++)
{
if (obraz[i][j] < p)
obraz[i][j] = 0;
else
obraz[i][j] = 255;
}
}
/** Główna metoda programu, wypisywanie wyniku */
public static void main(String args[])
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
System.out.print(„Wynik”+obraz[i][j] + "\t");
}
}
}
}
Zadanie2
Napisać program binaryzujący pole obraz będące tablicą o wymiarach 10x10 (wygenerowane w konstruktorze).
Kod programu Obraz.java binaryzującego obrazek:
/** Klasa reprezentująca obrazek o rozmiarach 10x10 */
public class Obraz
{
private int[][] obraz;
/** konstruktor */
Obraz()
{
obraz = new int[10][10];
for (int i = 0; i < 10; i++)
for (int j = 0; j < 10; j++)
obraz[i][j] = (int)(Math.random() * 255);
}
/** Metoda przeprowadzająca binaryzuje obrazu */
public void Binaryzacja()
{
int p = 100;
for (int i = 0; i < 10; i++)
for (int j = 0; j < 10; j++)
{
if (obraz[i][j] < p)
obraz[i][j] = 0;
else
obraz[i][j] = 255;
}
}
/** Metoda która wypisuje zawartość tablicy obraz*/
public void Wypisz()
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
System.out.print(obraz[i][j] + "\t");
}
}
}
/** Główna metoda programu. */
public static void main(String args[])
{
Obraz o = new Obraz();
System.out.println("Przed przeksztalceniem:");
o.Wypisz();
o.Binaryzacja();
System.out.println("Po przeksztalceniu:");
o.Wypisz();
}
}
Liczby generowane do tablicy należą do przedziału <0, 255>. Przeprowadzenie procesu binaryzacji polega na tym, że wybieramy sobie „prób binaryzacji” i jeżeli aktualna wartość tablicy jest większa do tego progu to do tablico zostaje podstawiona wartość 255, natomiast w przypadku, gdy aktualna wartość tablicy jest mniejsza to podstawiamy wartość 0. W programie wykonuje to metoda Binaryzacja(). Metoda Wypisz() , wypisuje na konsoli zawartość tablicy. Metoda main() tworzy nowy obiekt obrazek, wypisuje aktualną wartość tablicy tego właśnie obrazka (metoda Wypisz()) a następnie po binaryzacji (metoda Binaryzacja ()) ponownie wypisuje zawartość tablicy już zbinaryzowanej.
Wnioski:
Java jest obiektowym językiem programowania. Wszelkie dane i akcje na nich podejmowane są pogrupowane w klasy obiektów. O obiekcie można myśleć jako o samoistnej części programu, która może przyjmować określone stany i posiada określone zachowania, które mogą zmieniać te stany bądź przesyłać dane do innych obiektów. kod źródłowy programów pisanych w Javie kompiluje się do kodu pośredniego (tzw. bytecode). Powstały kod jest niezależny od systemu operacyjnego, a wykonuje go tzw. maszyna wirtualna, która (między innymi) tłumaczy kod uniwersalny na kod dostosowany do specyfiki konkretnego systemu operacyjnego.
Podczas pisania kodu źródłowego należy zwracać szczególną uwagę na to, aby nie pominąć żadnego nawiasu, średnika ani nie zmieniać wielkości liter.
Ćwiczenie 2
Przegląd składni – aplety Javy
Aplet jest programem wykonywanym w określonych ramach (nadbudowie maszyny wirtualnej). Ma więc możliwości takie jakie nadaje mu program uruchomieniowy. Kod może być grupowany w liczne wątki, które w wyniku interpretacji tworzą niezależnie wykonywane procesy współdzielące czas procesora.
Kompilację programów (podobnie jak wcześniej) przeprowadzamy poleceniem:
javac nazwa_pliku.java
Aplet jest formą aplikacji wywoływanej w ściśle określonym środowisku i nie jest wywoływany wprost przez kod klasy *.class lecz poprzez plik HTML w kodzie którego zawarto odniesienie do kodu apletu *.class np.:
<title>Graphics Applet</title>
<applet code=”GraphicsApplet.class” width=200 height=200>
</applet>
Zapis ten oznacza, że w oknie o szerokości 200 i takiej samej wysokości będzie uruchomiony aplet o kodzie GraphicsApplet.class.
Kod ten wystarczy wywołać w przeglądarce internetowej lub za pomocą narzędzia “appletviewer nazwa_pliku.html”.
Podczas ćwiczenia należało uruchomić gotowe przykłady apletów.
W programach tych dołączono biblioteki ‘awt’ oraz ‘applet’ za pomocą poleceń:
import java.awt.*;
import java.applet.*;
Biblioteka ‘awt’ zawiera procedury obsługi okien (np. wyświetlanie tekstu i grafiki, zmiana rozmiarów okien). Ogólnie do tworzenia graficznego interfejsu użytkownika (GUI).
Biblioteka ‘applet’ zawiera funkcje niezbędne dla przeglądarki.
Podczas tworzenia apletu tworzymy klasę, która dziedziczy z klasy Aplet. Wykorzystujemy wtedy takie metody jak: init(), start(), paint(), stop() i destroy(). Metodę main() wywołujemy podczas wywołania aplikacji, natomiast wywołując aplet wywołujemy metody wymienione wyżej w podanej kolejnośći. Init() i destroy() to metody wykonywane jednorazowo (metodę init() można uznać za konstruktor apletu) w przeciwieństwie do pozostałych metod, które modą być wywoływane wielokrotnie. Ponieważ Aplet korzysta z klasy Aplet oraz metod graficznych konieczne jest importowanie określonych pakietów.
Każdy aplet rozpoczyna pracę od wykonania początkowych instrukcji finkcji init(). Służy ona przede wszystkim do zainicjowania wszystkich zmiennych apletu i ma postać:
public void init()
{
// deklaracje zmiennych;
}
Po jej wykonaniu aplet wywołuje funkcję start(), która tworzy wątki apletu, zajmuje się wykonywaniem dalszych instrukcji. Jest wywoływana za każdym razem jeśli przeglądarka ponownie wyświetla stronę z apletem.
Zakończenie programu wiąże się z wywołaniem funkcji stop(), której zadaniem jest zatrzymywanie pracy wątków w żądanej kolejności. Wywoływana jest za każdym razem, gdy przeglądarka opuszcza stronę.
Zadaniem funkcji paint() jest m.in. odrysowywanie grafiki w oknie oraz całej pozostałej zawartości za każdym razem, gdy okno przeglądarki jest wyświetlane. Aplet Javy może odświeżyć zawartość własnego okna za każdym razem, gdy mu to zostanie nakazane. Należy w tym celu wywołać funkcję reprint().
Metoda destroy() jest wykonywana, gdy aplet jest ostatecznie usuwany z pamięci.
Cykl życia apletu obrazuje poniższy rysunek:
Zadanie 1
Przerobienie programów z poprzednich zajęć na aplety.
Program HelloWorld jako aplet posiada następujący kod:
import java.awt.*;
import java.applet.*;
public class HelloWorld extends Applet
{
public void paint(Graphics g)
{
g.drawString("Hello world",10,10);
System.out.println("Test");
}
}
Można powiedzieć, że wyświetlany za pomocą tego apletu komunikat jest „malowany” a nie wypisywany za pomocą metody paint().
Program Obrazek jako aplet posiada następujący kod:
import java.awt.*;
import java.applet.*;
class obraz extends Applet
{
int[][] obraz_ = new int[10][10];
obraz()
{
for (int i = 0; i < 10; i++)
for (int j = 0; j < 10; j++)
obraz_ [i][j] = (int)(Math.random() * 255);
}
public void binaryzacja()
{
int p = 100;
for (int i = 0; i < 10; i++)
for (int j = 0; j < 10; j++)
if (obraz_ [i][j] > p) obraz_ [i][j] = 255;
else obraz_ [i][j] = 0;
}
}
public class Obraz extends Applet
{
obraz Test = new obraz();
public void start()
{
Test.binaryzacja();
}
public void paint(Graphics g)
{
for (int i = 0; i < 10; i++)
for (int j = 0; j < 10; j++)
g.drawString(" " + Test.obraz_[i][j], i * 30, j * 10);
}
}
Operacja binaryzacji jest wykonywana za pomocą metody start() natomiast wypisanie („wymalowanie”) macierzy za pomocą metody paint(Graphics g). Obiekt g pozwala na wyświetlanie przez aplet grafiki i tekstu na ekranie.
Wnioski:
Aplety Javy umożliwiają uruchamianie z poziomu przeglądarki: gier, interaktywnych prezentacji multimedialnych lub nawet programów obliczeniowych. Poza klasycznymi apletami Javy wykonywanymi w obrębie przeglądarki internetowej (czyli po stronie klienta), istnieją jeszcze tzw. serwlety przeznaczone do wykonywania na stronach serwera.
Aplety Javy są bezpieczne w użyciu, nie mają bowiem prawa dostępu do plików znajdujących się na dysku użytkownika, zachowując przy tym możliwość wymiany danych z serwerem, z którego zostały ściągnięte.
Ćwiczenie 3
Wprowadzanie danych, wyprowadzanie wyników w Java. Interfejs tekstowy.
Java jako język obiektowy operuje na pojęciach ogólnych. Klasa (class) określa opis stałych, zmiennych, pól oraz metody (zachowanie).
Każde pojęcie, które chcemy opisać w języku musi być zawarte w definicji klasy. Klasa definiuje nowy typ danych, których wartościami są obiekty:
● klasa to szablon dla obiektów
● obiekt to egzemplarz klasy
Definicja Klasy wygląda następująco:
class nazwa {
//deklaracje pól
typ pole1;
typ poleN;
//deklaracje metod
typ metoda1(lista-parametrów) {
//treść metody
}
...
typ metodaM(lista-parametrów) {
//treść metody
}
}
Opis obiektu w klasie odbywa się poprzez modelowanie jego zachowania (metody) i stanu (pola). Może istnieć wiele obiektów danej klasy jednak każdy z nich jest jednostkowy i istnieje w pamięci komputera. Dostęp do obiektu jest możliwy za pomocą tzw. „uchwytu” (odwołania do pamięci, sterty, gdzie przechowywany jest obiekt). Nowy obiekt danej klasy tworzony jest za pomocą instrukcji new z podaniem nazwy klasy, np.:
Astronaut armstrong=new Astronaut();
Tworzony jest zatem nowy obiekt typu Astronaut, do którego przywiązany jest „uchwyt” Armstrong. Jeśli temu samemu „uchwytowi” przypisany zostanie inny obiekt, wówczas obiekt na który „uchwyt” wskazywał pierwotnie ginie. Zatem każdy obiekt jest jednostkowym wystąpieniem klasy i ma charakter dynamiczny.
Określenie pola kasy jako static oznacza, że jego stan jest jednostkowy dla wszystkich obiektów danej klasy, czyli jest własnością klasy a nie obiektów.
Tworzenie obiektu powoduje wywołanie procedury jego inicjowania nazywanej konstruktorem, który jest metodą o tej samej nazwie, co nazwa klasy, dla której tworzony jest obiekt i jest wywoływany automatycznie przy tworzeniu obiektu. Stosowany jest do podawania argumentów obiektowi oraz do grupy operacji startowych potrzebnej z punktu widzenia danej klasy.
Przykład 1
Uruchomić, skompilować i przeanalizować program (w plik Armstrong.java) o kodzie źródłowym:
class Astronaut //klasa reprezentująca astronauta
{
Double earthWeight;
Astronaut(double weight)
{
earthWeight=new Double(weight);
}
public double moonWeight()
{
return earthWeight.doubleValue()*.166;
}
}
class PlanetaryScale
{
Astronaut armstrong; //deklaracja zmiennej będącej odwołaniem do obiektu
public static void main (String args[]){
char c;
double earthWeight;
StringBuffer strng=new StringBuffer();
Double moonWeight;
Astronaut armstrong;
System.out.println("Ile ważysz na Ziemii ?");
try
{
while((c=(char)System.in.read())!='\n')
strng.append(c);
earthWeight=Double.valueOf(strng.toString()).doubleValue();
}
catch(java.io.IOException e)
{
earthWeight=0.0;
}
armstrong=new Astronaut(earthWeight);//przydzielenie pamięci i //zainicjowanie obeiaktu
System.out.println("Na Księżycu ważysz" +armstrong.moonWeight());
}
}
Powyższy program przelicza ziemską wagę na księżycową.
Obiekt typu StringBuffer został użyty w celu możliwości modyfikacji napisu, w przeciwieństwie do obiektów typu String, które gdy zostaną już raz utworzone nie można ich już zmieniać. Do czytania z klawiatury została użyta metoda appendChar(), należąca do klasy StringBuffer. Umożliwia ona dodawanie znaków do bufora w miarę wpisywania ich z klawiatury.
W kodzie programu z przykładu 1.6 zostały wykorzystane instrukcje try i catch, związane jest to z występowaniem w Javie (oprócz błędów) wyjątków, czyli określonych sytuacji, niewłaściwych ze względu na funkcjonowanie klas lub metod. Przykładem wyjątku może być dzielenie przez zero czy brak klasy lub pliku o podanej ścieżce. Metodę, która może spowodować wystąpienie wyjątku zamyka się w bloku kodu oznaczonego instrukcją warunkową try (spróbuj). Blok należy zakończyć działaniem przewidzianym dla sytuacji wyjątkowej w zależności od powstałego wyjątku. Detekcja rodzaju wyjątku odbywa się poprzez umieszczenie instrukcji catch (nazwa wyjątku i jego zmienna). Przykładowym działaniem może być wyświetlanie komunikatu na konsoli platformy.
Zadanie 1
Zmodyfikować powyższy program tak, aby interfejs użytkownika był oddzielony do reszty programu.
Tworzymy plik PlanetaryScale.java (zgodnie z podpowiedziami w instrukcjach do laboratorium) o kodzie źródłowym:
class Astronaut //klasa reprezentująca astronauta
{
Double earthWeight;//zmienna reprezentująca wagę ziemską
Astronaut(double weight)//konstruktor- ustawia wagę astronauty
{
earthWeight = new Double(weight);//obiekt
}
public double moonWeight() //metoda do zwracania wagi astronauty na //księżycu
{
return earthWeight.doubleValue() * .166;
}
}
/** Klasa przeliczająca wagę ziemską na księżycową. */
class PlanetaryScale //klasa do przeliczania wagi ziemskiej na księżycową
{
public void calculateWeight(String s)
{
Astronaut armstrong;
if (s == "") //warunek jeśli podano pustą wartość String
//czyli wagę, nastąpi oczytanie z klawiatury
{
System.out.println("Ile wazysz na Ziemii ?");
armstrong = new Astronaut(strToDouble(readLine()));
}
else
{
armstrong = new Astronaut(strToDouble(s));
}
System.out.println("Na Ksiezycu wazysz " + armstrong.moonWeight());
}
public String readLine()//metoda do pobierania doanych
//z klawiatury
{
String string;
try
{
char c;
StringBuffer str = new StringBuffer();
while ((c = (char)System.in.read()) != '\n')
str.append(c);
string = str.toString();
}
catch(java.io.IOException e)//obsługa wyjątku
{
string = "0.0";
}
return string;
}
public double strToDouble(String string)//metoda do konwersji
//ze string na double
{
double d;
try
{
d = Double.valueOf(string).doubleValue();
}
catch (java.lang.NumberFormatException e)
{
d = 0.0;
}
return d;
}
public static void main(String args[])//główna metoda programu
{
PlanetaryScale ps = new PlanetaryScale();
if (args.length == 0)
{
ps.calculateWeight("");
}
else
{
for (int i = 0; i < args.length; i++)
ps.calculateWeight(args[i]);
}
}
}
Wnioski:
Java posiada zestaw bibliotek umożliwiających interakcję z użytkownikiem za pomocą konsoli. Odczyt linii tekstu najłatwiej jest wykonać odczytując znak po znaku i tak zostało to zrealizowane w programie. Oddzielenie interfejsu użytkownika od reszty programu pozwala na łatwą implementację rozwiązania programu dla interfejsu graficznego, itp.
Ćwiczenie 4
Wielowątkowość. Synchronizacja wątków.
Na zajęciach poznawaliśmy zagadnienia związane z programowanie współbieżnym w Java. Pisaliśmy programy wielowątkowe, co nauczyło nas jak utworzyć i uruchomić wątek, a także jak określić czy już się wykonał. Na zajęciach poznawaliśmy zagadnienia związane z programowanie współbieżnym w Java. Pisaliśmy programy wielowątkowe, co nauczyło nas jak utworzyć i uruchomić wątek, a także jak określić czy już się wykonał.
Druga część zajęć była poświęcona synchronizacji wątków. Nauczyliśmy się jak zabezpieczyć wątki korzystające z tego samego zasobu. Poznaliśmy również architekturę Producent – Klient.
Część pierwsza (Wielowątkowość)
Wątek jest podprogramem (zbiorem wykonywanych operacji) umożliwiającym jego realizację w oderwaniu do innych wątków danego zadania. Relacja pomiędzy wątkami określana jest głównie przez mechanizmy: synchronizację i zasadę pierwszeństwa. W celu napisania kodu wątku konieczne jest albo bezpośrednie dziedziczenie z klasy Thread lub pośrednie poprzez implementację interfejsu Runnable.
W każdym wątku możemy spotkać charakterystyczne metody:
run() – określa instrukcje które wykonuje wątek.
stop() – zakończenie wątku.
start() – uruchomienie wątku.
wait() – zawieszenie wykonywania wątku.
sleep() – metoda usypia wątek na określony w parametrze okres czasu.
join() – metoda czeka aż wątek ją wywołujący zakończy się. W naszych programach wykorzystywaliśmy ją podczas zbierania od wątków pośrednich wyników obliczeń.
Zadanie 1
Należało napisać program obliczający całkę oznaczoną podanej funkcji. Wybraliśmy metodę trapezów, a każdy z wątków wyznaczał pole jednego trapezu. Zadany przedział dzieliliśmy przez liczbę wątków, co pozwoliło nam obliczyć szerokość prostokątów, wykorzystywaną podczas inicjalizacji wątków. Konstruktor wątku jako parametr pobierał współrzędne x przypisanego mu trapezu.
W metodzie run() obliczane jest pole trapezu. Jeśli wątek skończył obliczenia „zabieramy” od niego pośredni wynik obliczeń. Postępujemy tak aż zakończą się wszystkie wątki.
Poniżej znajduje się kod źródłowy:
class Watek extends Thread // dziedziczenie z klasy Thread
{
public double y, x1, x2;
public Watek(double x1, double x2)// konstruktor watku pobierajacy //prarametry x1, x2
{
this.x1 = x1;
this.x2 = x2;
this.y = 0.0;
}
public double Funkcja(double x)//metoda zwracająca obiekt z wartoscia //2 x do potegi 3
{
return 2*x*x*x;
}
public void run()//metoda run()obliczająca pole trapezu
{
y = (Funkcja(x1) + Funkcja(x2)) * (x2 - x1) / 2;
}
}
class Calka
{
public static void main(String args[]) throws Exception
{
Watek watki[];//deklaracja statycznej tablicy watkow
double y, x1, x2, dx;
watki = new Watek[20]; // przypisanie do pola danych watki //tablicy referencji do obiektów typu Watek
y = 0.0;
x1 = 0.0;
x2 = 1.0;
dx = (x2 - x1) / 20;
for (int i = 0; i < 20; i++)
{
watki[i] = new Watek(dx*i, dx*(i+1));
watki[i].start();
}
for (int i = 0; i < 20; i++)
{
watki[i].join();
y += watki[i].y;
}
System.out.println("Calka = " + y);//wypisywanie wartosci
}
}
Wynik działania programu dla różnej liczby wątków:
Realizowane zadanie wraz z prawidłowym wynikiem
Ilość wątków | Wynik działania programu |
---|---|
2 | 0,6250 |
3 | 0,5556 |
4 | 0,5312 |
5 | 0,5200 |
6 | 0,5139 |
10 | 0,505 |
Zadanie 2
Zadaniem drugiego programu było obliczenie iloczynu macierzy. Zauważyliśmy że jednostką obliczeniową w tym problemie jest obliczenie jednego elementu macierzy wyjściowej, do każdego takiego zadania przypisaliśmy jeden wątek. W konstruktorze wątku określamy jakiemu elementowi wyjściowemu odpowiada. Metoda run() mnoży odpowiednią kolumnę z odpowiednim wierszem macierzy wejściowych i zapamiętuje wynik. Po zakończeniu pracy pobieramy wynik pośredni od każdego wątku i obliczamy wynik końcowy.
Poniżej znajduje się kod źródłowy:
class Watek extends Thread // dziedziczenie z klasy Thread
{
public double a[][], b[][], wynik;// deklaracja tablic
public int row, column;
public Watek(double a[][], double b[][], int row, int column)
//konsturktor Watek
{
this.a = a;
this.b = b;
this.row = row;
this.column = column;
this.wynik = 0.0;
}
public void run()//Metoda run() mnozoca odpowiednia kolumne z //odpowiednim wierszem macierzy wejściowych i zapamiętujaca wynik
{
for (int i = 0; i < 4; i++)
wynik += a[row][i] * b[i][column];
}
}
class Macierz
{
static double a[][] = new double [3][3];//przypisanie do pola danych //tablicy referencji do obiektow typu double
static double b[][] = new double [3][3];
static double cm[][] = new double [3][3];
public static void main(String args[]) throws Exception//glowna //metoda inicjujaca dzialanie calego programu
{
for (int r = 0; r < 3; r++)
for (int c = 0; c < 3; c++)
{
a[r][c] = 2*(r+c);
b[c][r] = 3*(r+c)+1;
}
pomnoz();
System.out.println("Macierz A = ");//wypisywanie macierzy A
wypisz(a);
System.out.println("Macierz B = ");//wypisywanie macierzy B
wypisz(b);
System.out.println("Macierz C = ");//wypisywanie macierzy C
wypisz(cm);
}
public static void wypisz(double a[][]) throws Exception// statyczna metoda wypisujaca wartosci
{
for (int r = 0; r < 3; r++)
{
for (int c = 0; c < 3; c++)
System.out.print(" " + a[r][c] + " ");
System.out.println("");
}
}
public static void pomnoz() throws Exception// statyczna metoda //mnozaca element tablicy
{
Watek watki[][] = new Watek[3][3];//tworzenie nowego watku
for (int r = 0; r < 3; r++)
for (int c = 0; c < 3; c++)
{
watki[r][c] = new Watek(a, b, r, c);//przypisywanie //wartosci
watki[r][c].start();
}
for (int r = 0; r < 3; r++)
for (int c = 0; c < 3; c++)
{
watki[r][c].join();
cm[r][c] = watki[r][c].wynik;
}
}
}
Część druga (Synchronizacja wątków)
Program przykładowy ilustrował zasadę pracy architektury producent konsument, definiował klasę producenta, konsumenta i pudełka. Obiekt klasy pudełko jest współdzielony przez konsumenta i producenta. Dostęp do niego musi być synchronizowany. Synchronizacji wymagały dwie metody wez() i wloz(). Synchronizacja gwarantuje że, tylko jeden obiekt będzie wykonywał metodę synchronizowaną na danym obiekcie. Pozwoli to poprawnie odczytać konsumentowi wszystkie dane umieszczone w pudełku przez producenta.
Na zajęciach mieliśmy zrealizować trzy zadania, polegające na modyfikacji przykładowego programu.
Zadanie 1
Zmodyfikowaliśmy konstruktor producenta, aby mu przekazać pudełka dodatkowych konsumentów. Zmieniliśmy również mechanizm wkładania do pudełek tak, aby obsługiwał dodane pudełka.
Poniżej znajduje się kod źródłowy:
class Producent extends Thread //klasa Producent dziedziczy z Thread
{
private Pudelko pud[] = new Pudelko[3];//inicjacja obiektu Pudelko
private Pudelko pud_in[] = new Pudelko[3];
private int m_nLiczba;//inicjalizacja obiektu m_nLiczba
public Producent(Pudelko p1, Pudelko p2, Pudelko p3, Pudelko p1_in, Pudelko p2_in, Pudelko p3_in, int liczba) //metoda Producent()
{
pud[0] = p1;
pud[1] = p2;
pud[2] = p3;
pud_in[0] = p1_in;
pud_in[1] = p2_in;
pud_in[2] = p3_in;
this.m_nLiczba = liczba;
}
public void run() //metoda run()korzystajaca z metod wloz() i wez()
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
pud[j].wloz(i);
pud[j].wloz(i + 100);
System.out.println("Producent #" + this.m_nLiczba + " wlozyl: " + (i) + " i " + (i + 100) + " do pudelka:" + (j + 1));
try
{
sleep((int)(Math.random() * 100));
}
catch (InterruptedException e) { }
int v1 = pud_in[j].wez();
int v2 = pud_in[j].wez();
System.out.println("Producent #" + this.m_nLiczba + " wyjal " + v1 + " i " + v2);
}
}
}
}
class Konsument extends Thread //klasa Konsument dziedziczaca z Thread
{
private Pudelko pudelko, pud_in;//deklaracja zmiennych
private int m_nLiczba;
public Konsument(Pudelko p1, Pudelko p2, int Liczba)//konstruktor //Konsument
{
pudelko = p1;
pud_in = p2;
this.m_nLiczba = Liczba;
}
public void run()//metoda run() okreslajaca instrukcje z których //korzysta watek
{
int wartosc = 0;
for (int i = 0; i < 3; i++)
{
wartosc = pudelko.wez();//przypisywanie zmiennym wartości //zwracanych przez metode pudelko.wez()
int wartosc2 = pudelko.wez();
System.out.println("Konsument #" + this.m_nLiczba + " wyjal: " + wartosc + " i " + wartosc2);
pud_in.wloz(wartosc + 1);
pud_in.wloz(wartosc2 + 100);
System.out.println("Konsument #" + this.m_nLiczba + " wlozyl " + (wartosc + 1) + " i " + (wartosc2 + 100));
}
}
}
class Pudelko //klasa Pudelko
{
private int m_nZawartosc[] = new int[3]; // to jest zmienna warunkowa
private int m_LicznikZapisu = 0;
private int m_LicznikOdczytu = 0;
// do której dostęp synchronizujemy, (omówione później)
private boolean m_bDostepne = false;
public synchronized int wez()
{
while (m_LicznikZapisu == m_LicznikOdczytu)
{
try
{
wait();
}
catch (InterruptedException e) { }
}
//m_bDostepne = false;
int v = m_nZawartosc[m_LicznikOdczytu];
m_LicznikOdczytu = (m_LicznikOdczytu +1)%3;
notifyAll();
return v;
}
public synchronized void wloz(int wartosc)//metoda wloz()
{
while ((m_LicznikZapisu+1) % 3 == m_LicznikOdczytu)
{
try
{
wait();
}
catch (InterruptedException e) { }
}
m_nZawartosc[m_LicznikZapisu] = wartosc;
m_LicznikZapisu = (m_LicznikZapisu+1) % 3;
//m_bDostepne = true;
notifyAll();
}
}
class ProdKonsBuf//klasa ProdKonsBuf
{
public static void main(String[] args) throws Exception//metoda //glowna powodujaca uruchomienie programu
{
Pudelko pud1 = new Pudelko();//tworzenie obiektow Pudelko
Pudelko pud2 = new Pudelko();//tworzenie obiektow Pudelko
Pudelko pud3 = new Pudelko();//tworzenie obiektow Pudelko
Pudelko pud1_in = new Pudelko();//tworzenie obiektow Pudelko
Pudelko pud2_in = new Pudelko();//tworzenie obiektow Pudelko
Pudelko pud3_in = new Pudelko();//tworzenie obiektow Pudelko
Producent p1 = new Producent(pud1, pud2, pud3, pud1_in, pud2_in, pud3_in, 1);
//Producent p2 = new Producent(pud1, pud2, pud3, pud1_in, pud2_in, pud3_in, 2);
Konsument c1 = new Konsument(pud1, pud1_in, 1); //tworzenie //obiektow Konsument
Konsument c2 = new Konsument(pud2,pud2_in, 2);
Konsument c3 = new Konsument(pud3,pud3_in, 3);
p1.start();//uruchomienie watku
c1.start();//uruchomienie watku
c2.start();//uruchomienie watku
c3.start();//uruchomienie watku
c1.join();//metoda zbierajaca wyniki posrednie
System.out.println("Konsument 1 zakonczyl dzialanie");
c2.join();
System.out.println("Konsument 2 zakonczyl dzialanie");
c3.join();
System.out.println("Konsument 3 zakonczyl dzialanie");
p1.join();
System.out.println("Producent zakonczyl dzialanie");
pauza(); //metoda zawieszajaca wykonywanie watku
}
static void pauza() throws java.io.IOException
{
System.out.println("Nacisnij Enter...");
System.in.read();
}
}
Fragment wyniku działania programu:
Producent #1 wlozyl: 0 do pudelka:1
Konsument #1 wyjal: 0
Producent #1 wlozyl: 0 do pudelka:2
Konsument #2 wyjal: 0
Producent #1 wlozyl: 0 do pudelka:3
Konsument #3 wyjal: 0
Producent #1 wlozyl: 1 do pudelka:1
Konsument #1 wyjal: 1
Zadanie 2
Druga modyfikacja polegała na wprowadzeniu komunikacji dwukierunkowej między producentem a klientem. Rozwiązanie tego problemu wymagało przypisania każdemu klientowi dodatkowego pudełka na dane wyjściowe. Producent musi znać te pudełka, wiec zmieniliśmy jego konstruktor tak aby jego parametrami były pudelka wejściowe i wyjściowe obsługiwanych konsumentów. Konsument przeprowadzał na danych producenta proste operacje, a ich wynik zapisywał w pudełku wyjściowym.
Poniżej znajduje się kod źródłowy:
class Producent extends Thread//klasa dziedziczaca z klasy Thread
{
private Pudelko pud[] = new Pudelko[3];//tworzenie obiektow
private int m_nLiczba;
public Producent(Pudelko p1, Pudelko p2, Pudelko p3, int liczba) //konstruktor
{
pud[0] = p1;
pud[1] = p2;
pud[2] = p3;
this.m_nLiczba = liczba;
}
public void run()//metoda run()okresla instr. ktore wykonuje watek
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 3; j++)
{
pud[j].wloz(i);
System.out.println("Producent #" + this.m_nLiczba + " wlozyl: " + i + " do pudelka:" + (j + 1));
try
{
sleep((int)(Math.random() * 100));
}
catch (InterruptedException e) { }
}
}
}
}
class Konsument extends Thread//klasa Konsument dziedziczaca z Thread
{
private Pudelko pudelko;//deklaracja zmiennych
private int m_nLiczba; ;//deklaracja zmiennych
public Konsument(Pudelko c, int Liczba)//konstruktor Konsument
{
pudelko = c;
this.m_nLiczba = Liczba;
}
public void run() //metoda run() okresla instr. ktore wykon. watek
{
int wartosc = 0;
for (int i = 0; i < 10; i++)
{
wartosc = pudelko.wez();
System.out.println("Konsument #" + this.m_nLiczba + " wyjal: " + wartosc);
}
}
}
class Pudelko //klasa Pudelko
{
private int m_nZawartosc; // to jest znienna warunkowa
// do której dostęp synchronizujemy, (omówione później)
private boolean m_bDostepne = false;
public synchronized int wez()
{
while (m_bDostepne == false)
{
try
{ wait(); }
catch (InterruptedException e) { }
}
m_bDostepne = false;
notifyAll();
return m_nZawartosc;
}
public synchronized void wloz(int wartosc)//metoda wloz()
{
while (m_bDostepne == true)
{
try
{ wait(); }
catch (InterruptedException e) { }
}
m_nZawartosc = wartosc;
m_bDostepne = true;
notifyAll();
}
}
class ProdKonsTest //klasa ProdKonsTest w ktorej dziala metoda main()
{
public static void main(String[] args) throws Exception//glowan //metoda inicjujaca wszystkie metody
{
Pudelko pud1 = new Pudelko(); //tworzenie obiektu pud1
Pudelko pud2 = new Pudelko(); //tworzenie obiektu pud2
Pudelko pud3 = new Pudelko();//tworzenie obiektu pud3
Producent p1 = new Producent(pud1, pud2, pud3, 1);
Konsument c1 = new Konsument(pud1, 1); //tworzenie ob c1
Konsument c2 = new Konsument(pud2, 2); //tworzenie ob c1
Konsument c3 = new Konsument(pud3, 3); //tworzenie ob c1
p1.start(); // metoda ta uruchamia watek
c1.start(); // metoda ta uruchamia watek
c2.start(); // metoda ta uruchamia watek
c3.start(); // metoda ta uruchamia watek
p1.join(); // metoda ta uruchamia watek
c1.join(); // metoda ta uruchamia watek
c2.join(); // metoda ta uruchamia watek
c3.join(); // metoda ta uruchamia watek
pauza(); // metoda zawieszajaca dzialanie watku
}
static void pauza() throws java.io.IOException
{
System.out.println("Nacisnij Enter...");
System.in.read();
}
}
Fragment wyniku działania programu:
Producent #1 wlozyl: 0 do pudelka:1
Konsument #1 wyjal: 0
Producent #1 wyjal 0 z pudelka 0
Producent #1 wlozyl: 0 do pudelka:2
Konsument #1 wlozyl 0
Konsument #2 wyjal: 0
Konsument #2 wlozyl 0
Producent #1 wyjal 0 z pudelka 1
Zadanie 3
Trzecia zmiana wprowadzała zamiast pudełka bufor cykliczny. Program wymagał zmiany interfejsu klasy Pudełko. Dodaliśmy bufor który był tablicą, oraz wskaźnik odczytu i zapisu. Duże zmiany zaszły w metodach pobierających i zapisujących dane.
W metodzie weź() badamy czy wskaźnik odczytu jest równy wskaźnikowi zapisu (oznacza to brak danych w buforze), jeśli warunek jest nie spełniony dane są pobierane a wskaźnik odczytu jest zwiększany.
Metoda wloz() sprawdza czy bufor nie jest pełny (wskaźnik zapisu + 1 jest równy wskaźnikowi odczytu), jeśli warunek nie jest spełniony następuje zapis danych i zwiększenie wskaźnika zapisu.
Poniżej znajduje się kod źródłowy:
class Producent extends Thread //klasa Producent dziedziczaca z Thread
{
private Pudelko pud[] = new Pudelko[3]; //tworzenie ob. pud
private Pudelko pud_in[] = new Pudelko[3]; //tworzenie ob. Pud_in
private int m_nLiczba;
public Producent(Pudelko p1, Pudelko p2, Pudelko p3, Pudelko p1_in, Pudelko p2_in, Pudelko p3_in, int liczba) //konstruktor Producent
{
pud[0] = p1;//uzupelnianie tablicy pud[]
pud[1] = p2;
pud[2] = p3;
pud_in[0] = p1_in;
pud_in[1] = p2_in;
pud_in[2] = p3_in;
this.m_nLiczba = liczba;
}
public void run() //metoda okreslajaca instr. ktore wykonuje watek
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 3; j++)
{
pud[j].wloz(i);
System.out.println("Producent #" + this.m_nLiczba + " wlozyl: " + i + " do pudelka:" + (j + 1));
try
{
sleep((int)(Math.random() * 100));
}
catch (InterruptedException e) { }
int v = pud_in[j].wez();
System.out.println("Producent #" + this.m_nLiczba + " wyjal " + v + " z pudelka " + j);
}
}
}
}
class Konsument extends Thread //klasa Konsument
{
private Pudelko pudelko, pud_in;
private int m_nLiczba;
public Konsument(Pudelko p1, Pudelko p2, int Liczba)
{
pudelko = p1;
pud_in = p2;
this.m_nLiczba = Liczba;
}
public void run()// metoda okreslajaca instr. ktore wykonuje watek
{
int wartosc = 0;
for (int i = 0; i < 10; i++)
{
wartosc = pudelko.wez();
System.out.println("Konsument #" + this.m_nLiczba + " wyjal: " + wartosc);
pud_in.wloz(wartosc * 100);
System.out.println("Konsument #" + this.m_nLiczba + " wlozyl " + (wartosc * 100));
}
}
}
class Pudelko //klasa Pudelko
{
private int m_nZawartosc; // to jest znienna warunkowa
// do której dostęp synchronizujemy, (omówione później)
private boolean m_bDostepne = false;
public synchronized int wez()
{
while (m_bDostepne == false)
{
try
{ wait(); }
catch (InterruptedException e) { }
}
m_bDostepne = false;
notifyAll();
return m_nZawartosc;
}
public synchronized void wloz(int wartosc) //metoda wloz()
{
while (m_bDostepne == true)
{
try
{ wait(); }
catch (InterruptedException e) { }
}
m_nZawartosc = wartosc;
m_bDostepne = true;
notifyAll();
}
}
class ProdKonsTest2 //klasa glowna w ktorej wystepuje metoda main()
{
public static void main(String[] args) throws Exception
{
Pudelko pud1 = new Pudelko(); //tworzenie obiektu pud1
Pudelko pud2 = new Pudelko(); //tworzenie obiektu pud1
Pudelko pud3 = new Pudelko(); //tworzenie obiektu pud1
Pudelko pud1_in = new Pudelko();
Pudelko pud2_in = new Pudelko();
Pudelko pud3_in = new Pudelko();
Producent p1 = new Producent(pud1, pud2, pud3, pud1_in, pud2_in, pud3_in, 1);
Konsument c1 = new Konsument(pud1, pud1_in, 1);
Konsument c2 = new Konsument(pud2,pud2_in, 2);
Konsument c3 = new Konsument(pud3,pud3_in, 3);
p1.start(); //metoda inicjalizujaca watki
c1.start();
c2.start();
c3.start();
p1.join();
c1.join();
c2.join();
c3.join();
pauza();
}
static void pauza() throws java.io.IOException
{
System.out.println("Nacisnij Enter...");
System.in.read();
}
}
Fragment wyniku działania programu:
Producent #1 wlozyl: 0 i 100 do pudelka:1
Konsument #1 wyjal: 0 i 100
Konsument #1 wlozyl 1 i 200
Producent #1 wyjal 1 i 200
Konsument #2 wyjal: 0 i 100
Konsument #2 wlozyl 1 i 200
Producent #1 wlozyl: 0 i 100 do pudelka:2
Producent #1 wyjal 1 i 200
Konsument #3 wyjal: 0 i 100
Konsument #3 wlozyl 1 i 200
Producent #1 wlozyl: 0 i 100 do pudelka:3
Producent #1 wyjal 1 i 200
Konsument #1 wyjal: 1 i 101
Konsument #1 wlozyl 2 i 201
Producent #1 wlozyl: 1 i 101 do pudelka:1
Wnioski:
Programy wielowątkowe mimo iż zazwyczaj nie prowadzą do przyspieszenia pracy aplikacji pozwalają zwiększyć komfort pracy.
Wielowątkowość jest wbudowana w język Java, więc programy takie będą wielowątkowe w każdym systemie operacyjnym z zainstalowaną wirtualną maszyną Java.
Synchronizacja jest niezbędnym elementem większości programów wielowątkowych. Pozwala bezpiecznie współdzielić zasoby komputera przez wątki. W Javie synchronizacja powinna się odbywać na poziomie metod, ale dla klas które nie definiują swoich metod jako synchronizowanych można użyć bloków synchronizacji.
Synchronizacja jest pożytecznym mechanizmem, ale niesie ze sobą niebezpieczeństwa takie jak zakleszczenie lub zagłodzenie wątków. Błędy tego typu są trudne do wykrycia podczas testowania programu i powinno się je wyeliminować na etapie projektowania.
Ćwiczenie 5
Komunikacja sieciowa przy pomocy „gniazdek”
W pierwszej kolejności napisaliśmy klienta (SimpleClient.java), który potrafi komunikować się z przykładowym serwerem. Wykorzystana została klasa Socket do nawiązania połączenia. Następnie z obiektu klasy Socket pobrane zostały strumienie wejściowe i wyjściowe, do czytania i pisania do gniazdka. Klient po połączenie z serwerem czeka na wpisanie linii tekstu z klawiatury, po czym wysyła go do serwera i oczekuje na odpowiedź od serwera. Wysyłanie tekstu do serwera trwa dopóki klient nie wpisze słowa „quit”, wtedy klient zamyka połączenie i kończy działanie.
Następnie zmodyfikowaliśmy serwer (SimpleServer.java) tak, aby potrafił obsłużyć dodatkowe komendy. Dodaliśmy kilka komend w postaci kolejnych warunków sprawdzających czy przesłany tekst jest taki sam jak dana komenda. Jeśli przesłany tekst jest równy „quit”, serwer zamyka połączenie i kończy działania. Ostatecznie dodaliśmy pętlę, w której serwer potrafi obsłużyć kolejnych klientów. Jedynie na przesłanie komendy „stop” serwer zareaguje zakończeniem działania.
Poniżej znajduje się kod źródłowy klienta:
import java.io.*;
import java.net.*;
class SimpleClient {
public final static int TESTPORT = 5000;
public static void main(String args[])//deklaracja gniazdka //serwera i serwera dla klientadeklaracja strumienia //wejściowego i wyjściowego
{
Socket clientSocket = null;
BufferedReader is = null;
DataOutputStream os = null;
BufferedReader stdin = null;
String Text;
// Próba otwarcia gniazdka serwera na porcie TESTPORT
try
{
clientSocket = new Socket("LocalHost", TESTPORT);
is = new BufferedReader(new
InputStreamReader(clientSocket.getInputStream()));
os = new DataOutputStream(clientSocket.getOutputStream());
stdin = new BufferedReader(new InputStreamReader(System.in));
}
catch(Exception e)
{
System.out.println(e);
}
// Stworzenie obiektu gniazdka z gniazdka klienta do //nasłuchu przyjęcia połączenia serwera
// Otwarcie strumienia wejściowego i wyjściowego
System.out.println("Uruchomiony...");
try
{
do
{
Text = stdin.readLine();
os.writeBytes(Text+"\n");
System.out.println("> " + is.readLine());
} while((Text.compareTo("quit") != 0) && (Text.compareTo("stop") != 0));
}
catch(IOException e)
{
System.out.println(e);
}
try {
os.close();
is.close();
clientSocket.close();
} catch(IOException ic) {
ic.printStackTrace();
}
}
}
Poniżej znajduje się kod źródłowy Serwera:
import java.io.*;
import java.net.*;
/**
* @(#)SimpleServer.java
* @author Qusay H. Mahmoud
*/
public class SimpleServer {
public final static int TESTPORT = 5000;
public static void main(String args[]) {
// deklaracja gniazdka serwera i klienta dla serwera
// deklaracja strumienia wejściowego i wyjściowego
ServerSocket checkServer = null;
String line;
BufferedReader is = null;
DataOutputStream os = null;
Socket clientSocket = null;
boolean stop = false;
// Próba otwarcia gniazdka serwera na porcie TESTPORT
try {
checkServer = new ServerSocket(TESTPORT);
System.out.println("SimpleServer Wystartowany.....");
} catch (IOException e) {
System.out.println(e);
}
while (!stop) {
// Stworzenie obiektu gniazdka z gniazdka serwera do nasłuchy przyjęcia
// połączenia klienta
// Otwarcie strumienia wejściowego i wyjściowego
try {
clientSocket = checkServer.accept();
is = new BufferedReader(new
InputStreamReader(clientSocket.getInputStream()));
os = new DataOutputStream(clientSocket.getOutputStream());
} catch(Exception ei) {
ei.printStackTrace();
}
// odebranie komunikatu od klienta I sprawdzenie jego zawartości czy
//zawiera: "Greetings".
try {
boolean quit = false;
do {
line = is.readLine();
System.out.println("> " + line);
if (line.compareTo("czesc") == 0)
os.writeBytes("witam!\n");
else if (line.compareTo("ktora godzina") == 0)
os.writeBytes("nie wiem! :/\n");
else if (line.compareTo("co to jest") == 0)
os.writeBytes("to jest prosty serwer w Javie :)\n");
else if (line.compareTo("stop") == 0)
stop = true;
else if (line.compareTo("quit") == 0)
quit = true;
else
os.writeBytes("nieznane polecenie\n");
} while (!stop && !quit);
} catch (IOException e) {
System.out.println(e);
}
try {
os.close();
is.close();
clientSocket.close();
} catch(IOException ic) {
ic.printStackTrace();
}
}
}
}
Testy programu:
Log serwera:
SimpleServer Wystartowany.....
> czesc
> ktora godzina
> co to jest
> quit
> co to jest <- kolejny klient
> stop
Log klienta:
Uruchomiony...
czesc
> witam!
ktora godzina
> nie wiem! :/
co to jest
> to jest prosty serwer w Javie :)
quit
> null
Log kolejnego klienta:
Uruchomiony...
co to jest
> to jest prosty serwer w Javie :)
stop
> null
Wnioski:
Język Java zawiera zestaw klas umożliwiających tworzenie połączeń sieciowych z wykorzystaniem gniazdek. Wykorzystanie tych klas jest łatwe i pozwala skupić się programiście na rozwiązaniu problemu nad którym pracuje zamiast nad realizacją połączenia. Serwer ServerSocket jest specjalnym gniazdem. Gniazdo serwera służy do nasłuchiwania na danym porcie przychodzących połączeń. Kiedy takie połączenie przychodzi tworzony jest nowy obiekt klasy Socket, który realizuje obsługę tego połączenia.
Ćwiczenie 6
Serwer wielowątkowy
Należy stworzyć dwa aplety, które będą odpowiedzialne za nawiązanie komunikacji między sobą. Jeden aplet będzie serwerem, a drugi klientem.
Serwer po naciśnięciu przycisku tworzy gniazdo serwera w wątku WatekSerwera i oczekuje na żądanie połączenia. Po połączeniu serwer odbiera przychodzące dane, wyświetla je w polu tekstowym t1 i wysyła je z powrotem. Klient nawiązuje połączenie po naciśnięciu przycisku i wysyła dane pobrane z pola tekstowego t1 i odbiera dane, które wpisuje z kolei do pola t2.
Kod źródłowy serwera Java.java:
import java.awt.*;
import javax.swing.*;
import java.io.*;
import java.net.*;
import java.awt.event.*;
public class Java extends JApplet {
ServerSocket s;
Socket socket;
WatekSerwera w1;
public static final int PORT = 8080;
JButton b1,b2;
JLabel e1 = new JLabel ("");
JLabel e2 = new JLabel ("Odebrano:");
JTextField t1 = new JTextField (15);
boolean polaczenie = false;
public void init (){
// tworzenie wątku serwera po naciśnięciu przycisku „Utwórz gniazdo serwera"
b1 = new JButton ("Utw\u00F3rz gniazdo serwera");
b1.addActionListener(
new ActionListener(){
public void actionPerformed (ActionEvent e){
t1.setText("");
w1 = new WatekSerwera();
polaczenie = true;
w1.start();
}
});
// zamykanie gniazd po naciśnięciu przycisku „Rozlącz"
b2 = new JButton ("Roz\u0142\u0105cz");
b2.addActionListener(
new ActionListener(){
public void actionPerformed (ActionEvent e){
polaczenie = false;
try{
socket.shutdownOutput();
socket.close();
s.close ();
e1.setText ("Roz\u0142\u0105czenie." );
}catch (IOException e2) {}
}
});
b2.setEnabled(false);
Container cp=getContentPane();
cp.setLayout(new FlowLayout());
JPanel p1 = new JPanel();
p1.setLayout (new GridLayout(7,1));
p1.add(new JLabel(""));
p1.add(b1);
p1.add(b2);
p1.add(e1);
p1.add(e2);
p1.add(t1);
p1.add(new JLabel(""));
cp.add(p1);
}
//klasa odpowiedzialna za otwieranie gniazd oraz komunikację
public class WatekSerwera extends Thread{
boolean połączenie = true;
public void run(){
try{
e1.setText("Oczekiwanie na po\u0142\u0105czenie ..." );
s=new ServerSocket(PORT); // tworzenie gniazda serwera
try{
socket=s.accept(); // oczekiwanie na żądanie połączenia
try{
e1.setText("Po\u0142\u0105czono.");
b1.setEnabled(false);
b2.setEnabled(true);
// tworzenie strumieni do odbierania i wysyłania danych
BufferedReader in = new BufferedReader(new InputStreamReader (socket.getInputStream( )));
PrintWriter out = new PrintWriter (new BufferedWriter (new OutputStreamWriter (socket.getOutputStream () ) ), true);
// pętla, w której następuje wymiana danych
while(połączenie){
String str = in.readLine();
out.println(str);
if (str.equals ("END")){
połączenie = false;
break;
}
else
t1.setText(str);
}
}finally{socket.close();} // zamknięcie gniazda
} finally{ s.close();} // zaniknięcie gniazda serwera
e1.setText ("Klient roz\u0142\u0105czy\u0142 sie.");
}catch (IOException e){}
t1.setText("");
b1.setEnabled(true);
b2.setEnabled(false) ;
}
}
}
Kod źródłowy serwera Java2.java:
import java. awt.* ;
import javax. swing.* ;
import java. io.*;
import java. net.*;
import java. awt.event.*;
public class Java2 extends JApplet{
WatekKlienta w2 = null;
public static final int PORT = 8080;
JButton b1, b2, b3;
JLabel e1 = new JLabel ("");
JLabel e2 = new JLabel ("Odebrano :");
JTextField t1 = new JTextField (15);
JTextField t2 = new JTextField (15);
boolean polaczenie;
public void init (){
b1 = new JButton ("Utworz gniazdko klienta");
b1.addActionListener(
new ActionListener(){
public void actionPerformed (ActionEvent e){
e1.setText ("Polaczenie ..." );
w2 = new WatekKlienta();
polaczenie = true;
w2.start();
}
});
b2 = new JButton("Rozlacz");
b2.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e){
e1.setText("");
polaczenie = false;
b1.setEnabled(true);
b2.setEnabled(false);
b3.setEnabled(false);
}
});
b2.setEnabled(false);
b3 = new JButton("Wyslij");
b3.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e){
if(w2 != null)
w2.wyslij = true;
}
});
b3.setEnabled(false);
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
JPanel p1 = new JPanel();
p1.setLayout(new GridLayout(9,1));
p1.add(new JLabel (""));
p1.add(b1);
p1.add(b2);
p1.add(e1);
p1.add(b3);
p1.add(t1);
p1.add(e2);
p1.add(t2);
p1.add(new JLabel(""));
cp.add (p1);
}//koniec init()
public class WatekKlienta extends Thread{
public boolean wyslij = false;
int i=0;
public void run (){
try{
InetAddress addr = InetAddress.getByName("10.10.1.177");
Socket socket = new Socket(addr, PORT);
try{
e1.setText("Po\u0142\u0105czono z serwerem.");
b1.setEnabled(false);
b2.setEnabled(true);
b3.setEnabled(true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
while (polaczenie){
if (wyslij){
out.println (t1.getText ());
wyslij = false;
String str = in.readLine();
t2.setText(str);
}
}
out.println("END");
}finally { socket.close();}
e1.setText("Roz\u0142\u0105czono.");
}catch (IOException e) {}
e1.setText("Brak po\u0142\u0105czenia.");
t1.setText("");
}//koniec run()
}//koniec class WatekKlienta
}//koniec class Java2
Kod źródłowy serwera Klient.java:
import java. awt.* ;
import javax. swing.* ;
import java. io.*;
import java. net.*;
import java. awt.event.*;
public class Klient extends JApplet{
WatekKlienta w2 = null;
public static final int PORT = 8080;
JButton b1, b2, b3;
JLabel e1 = new JLabel ("");
JLabel e2 = new JLabel ("Odebrano :");
JTextField t1 = new JTextField (15);
JTextField t2 = new JTextField (15);
boolean polaczenie;
public void init (){
b1 = new JButton ("Utworz gniazdko klienta");
b1.addActionListener(
new ActionListener(){
public void actionPerformed (ActionEvent e){
e1.setText ("Polaczenie ..." );
w2 = new WatekKlienta();
polaczenie = true;
w2.start();
}
});
b2 = new JButton("Rozlacz");
b2.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e){
e1.setText("");
polaczenie = false;
b1.setEnabled(true);
b2.setEnabled(false);
b3.setEnabled(false);
}
});
b2.setEnabled(false);
b3 = new JButton("Wyslij");
b3.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e){
if(w2 != null)
w2.wyslij = true;
}
});
b3.setEnabled(false);
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
JPanel p1 = new JPanel();
p1.setLayout(new GridLayout(9,1));
p1.add(new JLabel (""));
p1.add(b1);
p1.add(b2);
p1.add(e1);
p1.add(b3);
p1.add(t1);
p1.add(e2);
p1.add(t2);
p1.add(new JLabel(""));
cp.add (p1);
}//koniec init()
public class WatekKlienta extends Thread{
public boolean wyslij = false;
int i=0;
public void run (){
try{
InetAddress addr = InetAddress.getByName("localhost");
Socket socket = new Socket(addr, PORT);
try{
e1.setText("Po\u0142\u0105czono z serwerem.");
b1.setEnabled(false);
b2.setEnabled(true);
b3.setEnabled(true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
while (polaczenie){
if (wyslij){
out.println (t1.getText ());
wyslij = false;
String str = in.readLine();
t2.setText(str);
}
}
out.println("END");
}finally { socket.close();}
e1.setText("Roz\u0142\u0105czono.");
}catch (IOException e) {}
e1.setText("Brak po\u0142\u0105czenia.");
t1.setText("");
}//koniec run()
}//koniec class WatekKlienta
}//koniec class Java2
Kod źródłowy serwera Serwer.java:
import java.awt.*;
import javax.swing.*;
import java.io.*;
import java.net.*;
import java.awt.event.*;
public class Serwer extends JApplet {
WatekSerwera w1;
public static final int PORT = 8080;
JButton b1,b2;
JLabel e1 = new JLabel ("");
JLabel e2 = new JLabel ("Odebrano:");
JTextField t1 = new JTextField (15);
boolean polaczenie = false;
public void init (){
// tworzenie wątku serwera po naciśnięciu przycisku „Utwórz gniazdo serwera"
b1 = new JButton ("Utw\u00F3rz gniazdo serwera");
b1.addActionListener(
new ActionListener(){
public void actionPerformed (ActionEvent e){
t1.setText("");
w1 = new WatekSerwera();
polaczenie = true;
w1.start();
b1.setEnabled(false);
b2.setEnabled(true);
}
});
// zamykanie gniazd po naciśnięciu przycisku „Rozlącz"
b2 = new JButton ("Roz\u0142\u0105cz");
b2.addActionListener(
new ActionListener(){
public void actionPerformed (ActionEvent e){
polaczenie = false;
//try{
//socket.shutdownOutput();
//socket.close();
//s.close ();
e1.setText ("Roz\u0142\u0105czenie." );
//}catch (IOException e2) {}
}
});
b2.setEnabled(false);
Container cp=getContentPane();
cp.setLayout(new FlowLayout());
JPanel p1 = new JPanel();
p1.setLayout (new GridLayout(7,1));
p1.add(new JLabel(""));
p1.add(b1);
p1.add(b2);
p1.add(e1);
p1.add(e2);
p1.add(t1);
p1.add(new JLabel(""));
cp.add(p1);
}
//klasa odpowiedzialna za otwieranie nasłuchu w serwere
public class WatekSerwera extends Thread{
boolean połączenie = true;
ServerSocket s=null;
Socket socket[] = new Socket[5];
WatekForClient []client=new WatekForClient[5];
int clients=0;//Liczba uruchomionych klientów
public void run(){
try{
e1.setText("Oczekiwanie na po\u0142\u0105czenie ..." );
s=new ServerSocket(PORT); // tworzenie gniazda serwera
try{
while(true){
System.out.println("Nasluchuje...");
socket[clients]=s.accept();// oczekiwanie na żądanie połączenia
System.out.println("Połczono");
client[clients]=new WatekForClient(socket[clients]);//Wywołanie nowego watku dla klienta
System.out.println("Przygotowanie dla nowego watku");
client[clients].start();
System.out.println("Nowy watek klienta wystartowany...");
clients++;
}//Koniec While
}finally{
s.close();// zaniknięcie gniazda serwera
e1.setText ("Klient roz\u0142\u0105czy\u0142 sie.");
}
}catch (IOException e){}
t1.setText("");
b1.setEnabled(true);
b2.setEnabled(false) ;
}
}
//klasa odpowiedzialna za oobsługę pojedynczego klienta
public class WatekForClient extends Thread{
boolean połączenie = true;
Socket socket=null;
String str="";
WatekForClient(Socket socket){
this.socket=socket;
System.out.println("Nowy watek wystartowany");
}
public void run(){
try{
try{
// tworzenie strumieni do odbierania i wysyłania danych
BufferedReader in = new BufferedReader(new InputStreamReader (this.socket.getInputStream( )));
PrintWriter out = new PrintWriter (new BufferedWriter (new OutputStreamWriter (this.socket.getOutputStream () ) ), true);
// pętla, w której następuje wymiana danych
while(połączenie){
str = in.readLine();//Odbiór danych
out.println(str);//Wysłanie danych
if (str.equals ("END")){
połączenie = false;
break;
}
else
t1.setText(str);
System.out.println("Serwer otrzymal: " + t1);
}
}finally{this.socket.close();} // zamknięcie gniazda
}catch (IOException e){}
}
}
}
Wnioski:
Gniazda są programową abstrakcją używaną do reprezentowania końcówek połączenia pomiędzy dwoma maszynami. Dla każdego połączenia istnieją gniazda na obydwu uczestniczących w nim komputerach. Rodzaj okablowania łączącego maszyny jest dla nas zupełnie nieznany, z punktu widzenia gniazd, nie interesuje to nas. Istotnym zagadnieniem jest wymiana informacji pomiędzy gniazdami. Z gniazda uzyskujemy strumienie InputStream oraz OutputStream, poprzez które traktujemy gniazda jako strumień danych. Do reprezentowania gniazd służą obiekty ServerSocket, którego serwer używa do nasłuchiwania przychodzących połączeń, oraz Socket, którego klient używa do zainicjowania połączenia. Po tym, jak klient połączy się z serwerem obiekt ServerSocket zwraca (poprzez metodę accept() ) odpowiedni obiekt, przez który po stronie serwera odbywa się komunikacja. Tworząc obiekt ServerSocket, podajemy jedynie numer portu (adres IP jest już przypisany do komputera), natomiast przy tworzeniu elementu Socket musimy podać numer portu, jak i IP komputera, z którym chcemy się połączyć.