Java posiada bogatą bibliotekę standardowych pakietów, i tak do podstawowych należą:
W dalszej części rozdziału omówimy następujące pakiety biblioteczne: java.awt, java.io i java.net. Pozostałe, zawierają klasy, z których część wykorzystano w pracy, natomiast dokładne omówienie wszystkich klas tych pakietów można znaleźć w dokumentacji dostarczanej z każdym środowiskiem programistycznym lub w Internecie pod adresem:
W każdym aplecie (lub aplikacji używającej elementów graficznego interfejsu użytkownika, ang. Graphic User Interface, GUI ) pojawiają się elementy pakietu java.awt.*. Pakiet Abstract Window Toolkit dostarcza komponentów graficznych używanych w programach Javy oraz elementów sterujących ich zachowaniem (generacja i obsługa zdarzeń). W biblioteka AWT składa się z następujących pakietów:
java.awt
java.awt.datatransfer
java.awt.event
java.awt.image
java.awt.peer
Klasą, z której dziedziczy większość klas pakietu AWT jest klasa Component. W rzeczywistości także klasa Applet jest częścią AWT. Klasy dziedziczące z klasy Component zestawiono poniżej:
* nowa klasa w JDK 1.1
Przedstawione na rysunku klasy AWT możemy podzielić na dwa rodzaje: proste komponenty (np. Button, Label) i pojemniki przeznaczone do przechowywania prostych komponentów (np. Panel, Frame).
Klasa AppletComponent, może więc zawierać komponenty AWT. Dodajmy więc kilka komponentów do apletu:
Jak widać, aplet ten zawiera znaczniki (ang. checkbox), przełączniki (ang. radio button), przycisk (ang. button), pole wyboru (ang. choice) i etykietę (ang. label). Poszczególne komponenty są częścią paneli (pojemników) oznaczonych kolorami, prócz etykiety, która wraz z innymi pojemnikami przypisana jest bezpośrednio do pojemnika apletu. Kod apletu Komponenty wraz z opisem przedstawiono poniżej.
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class Komponenty extends Applet implements ActionListener, ItemListener
{
Label lblEtykieta; // etykieta
Button btnPrzycisk; // przycisk
Choice chcTakNie; // pole wyboru
// panele na których umieszczone zostaną: znaczniki (p1),
// przełączniki (p2) oraz przycisk i pole wyboru (p3),
Panel p1, p2, p3;
Checkbox cb1, cb2; // znaczniki
Checkbox cb3, cb4; // przełączniki
CheckboxGroup cbg; // grupa przełączników
public void init()
{
//Budowanie pierwszego panelu zawierającego znaczniki
cb1 = new Checkbox();
cb1.setLabel("Checkbox 1");
cb2 = new Checkbox("Checkbox 2");
cb2.setState(true); // znacznik cb2 domyślnie zaznaczony
p1 = new Panel();
// ustawienie zarządcy rozmieszczenia komponentów dla panelu p1
p1.setLayout(new FlowLayout());
p1.setBackground(Color.yellow); // panel p1 kolor żółty
p1.add(cb1);
p1.add(cb2);
// Budowanie drugiego panelu zawierającego przełączniki
cbg = new CheckboxGroup();
cb3 = new Checkbox("RadioBtn 1", cbg, false);
cb4 = new Checkbox("RadioBtn 1", cbg, false);
p2 = new Panel();
// ustawienie zarządcy rozmieszczenia komponentów dla panelu p2
p2.setLayout(new FlowLayout());
p2.setBackground(Color.blue); // panel p1 kolor niebieski
p2.add(cb3);
p2.add(cb4);
// Budowanie trzeciego panelu zawierającego
// pole wyboru i przycisk
btnPrzycisk = new Button("Button");
chcTakNie = new Choice();
chcTakNie.add("Tak");
chcTakNie.add("Nie");
p3 = new Panel();
// ustawienie zarządcy rozmieszczenia komponentów dla panelu p3
p3.setLayout(new FlowLayout());
p3.setBackground(Color.red); // panel p1 kolor czerwony
p3.add(btnPrzycisk);
p3.add(chcTakNie);
//Dodanie paneli i etykiety do pojemnika apletu
lblEtykieta = new Label("Etykieta", Label.CENTER);
lblEtykieta.setFont(new Font("Arial", Font.BOLD, 15));
// ustawienie zarządcy rozmieszczenia komponentów dla apletu
setLayout(new GridLayout(2, 2));
add(p1);
add(p2);
add(p3);
add(lblEtykieta);
// Rejestracja klas nasłuchujących
btnPrzycisk.addActionListener(this);
chcTakNie.addItemListener(this);
validate();
}
// reakcja na naciśnięcie przycisku: tekst wyświetlany przez
// etykietę zmieni się na "klik"
public void actionPerformed(ActionEvent evt)
{
if (evt.getSource() instanceof Button)
lblEtykieta.setText("klik");
}
// reakcja na wybranie elementu w polu wyboru: etykieta przyjmie
// wartość równą nazwie wybranego elementu pola wyboru
public void itemStateChanged(ItemEvent e)
{
lblEtykieta.setText(chcTakNie.getSelectedItem());
}
}
Obsługa poszczególnych komponentów jest prosta - ogólnie: zadajemy właściwości komponentu oraz jego reakcje na generowane zdarzenia. Nie jest celem tej pracy opisywanie użycia komponentów graficznych. Szczegółowy opis metod każdej klasy komponentu Czytelnik znajdzie w specyfikacji Java API (Application Programming Interface), dołączanej np. do JDK firmy Sun (lub zawartej w innych środowiskach programistycznych Javy).
Komponenty.Komponenty, (tak jak poprzedni) zależny od rozmiaru okna apletu.
Gdy do pojemnika apletu dołączane są nowe elementy, pojemnik rozmieszcza dodane elementy zgodnie z przypisanym do niego zarządcą rozmieszczenia (ang. Layout Manager). Zarządca rozmieszczenia określa sposób wyświetlania komponentów dołączonych do pojemnika. W naszym przykładzie, każdy panel posiada swojego zarządcę określającego rozmieszczenie komponentów, aplet Komponenty także ma zarządcę określającego sposób rozmieszczenia paneli i etykiety w oknie apletu. W Javie do dyspozycji mamy pięć rodzajów zarządców rozmieszczenia:
a) FlowLayout - rozmieszczenie ciągłe, elementów jednego za drugim w pojemniku; gdy dany komponent nie mieści się w wierszu, przenoszony jest do następnego wiersza,
b) BorderLayout - rozmieszczenie brzegowe, polega na rozmieszczeniu komponentów na obrzeżach i w środku pojemnika; miejsca umieszczenia w pojemniku określane są za pomocą nazw stron świata: West, East, North, South, Center; rozmieszczenie to nazywane jest także rozmieszczeniem grawitacyjnym,
c) GridLayout - rozmieszczenie siatkowe, polega na rozmieszczeniu komponentów w prostokątnej siatce o podanej liczbie kolumn i wierszy; wszystkie klatki tej siatki mają identyczne rozmiary,
d) GridBagLayout - rozmieszczenie "torebkowe", tu komponenty rozmieszane są w prostokątnym układzie torebek o podanych rozmiarach; jednak rozmiary torebek nie muszą być, jak w przypadku GridLayut, identyczne; sposób rozmieszczenia elementów w torebkach określają obiekt typu GridBagConstraints
e) CardLayout - rozmieszczenie kartkowe; komponenty rozmieszczane są na wzajemnie zasłaniających się kartkach.
Jak widać w aplecie Komponenty dla paneli ustawiono zarządcę typu FlowLayout,GridLayout. Możemy także rozmieścić elementy w komponencie bez użycia zarządcy, musimy wtedy jednak określić położenie i rozmiar każdego z komponentów.
Podczas tworzenia pojemnika przypisany jest mu zarządca domyślny (dla apletu i panelu FlowLayout, a dla okna ( Window) i ramki ( Frame) BorderLayout).
Użycie różnych zarządców rozmieszczenia ilustruje poniższy kod.
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class Rozklady extends Applet implements ActionListener
{
CardLayout cardLayout = new CardLayout();
Panel panel1 = rozkladTypuKarta(cardLayout);
Panel panel2 = rozkladTypuGridLayout();
Panel panel3 = rozkladTypuBorderLayout();
Button btnNastepnaKarta = new Button("Nastepna karta");
Panel rozkladTypuKarta(CardLayout cardLayout)
{
Panel cardPanel = new Panel();
cardPanel.setLayout(cardLayout);
for (int i = 0; i < 5; i++)
{
Panel karta = new Panel();
karta.setBackground(Color.yellow);
karta.setLayout(new BorderLayout());
karta.add("North",new Label("Oto karta nr "+i));
karta.add("Center",new Checkbox("Ta karte widzialem!"));
cardPanel.add("karta"+i,karta);
}
return cardPanel;
}
Panel rozkladTypuGridLayout()
{
Panel panel = new Panel();
panel.setBackground(Color.blue);
panel.setLayout(new GridLayout(2,2,10,5));
for (int i = 0; i< 2; i++)
{
for (int j = 0; j< 2; j++)
panel.add(new Button("Siatka "+i+","+j));
}
return panel;
}
Panel rozkladTypuBorderLayout()
{
Panel panel = new Panel();
panel.setBackground(Color.gray);
panel.setLayout(new BorderLayout());
panel.add("North", new Button("Polnoc"));
panel.add("Center", new Label("Srodek"));
panel.add("West", new Button("Zachod"));
panel.add("East", new Button("Wschod"));
panel.add("South", new TextArea("Poludniowy element!",2,10));
return panel;
}
public void init()
{
btnNastepnaKarta.addActionListener(this);
// ustawienie zarządcy rozmieszczenia dla apletu
setLayout(new BorderLayout());
add("North",panel1);
add("East",btnNastepnaKarta);
add("West",panel2);
add("South",panel3);
validate();
}
public void actionPerformed(ActionEvent evt)
{
if (evt.getSource() == btnNastepnaKarta)
cardLayout.next(panel1);
}
}
Po uruchomieniu apletu, przyjmie on wygląd pokazany na rysunku. Ponieważ dla apletu ustawiono zarządcę rozmieszczenia elementów typu BorderLayout,
Po naciśnięciu przycisku "Następna karta" możemy przeglądać poszczególne karty z rozkładu typu CardLayout (u góry apletu) i zaznaczyć karty już przez nas oglądane (znacznik: "Tę kartę widziałem!").
Operacje wejścia wyjścia (ang. Input / Output, I/O) w Javie zrealizowane są według tego samego modelu, co w C++. Wszystkie operacje I/O (wejścia / wyjścia) realizowane są za pomocą klas zawartych w pakiecie java.io. Programy Javy używają strumieni (ang. streams) do wykonywania operacji wejścia /wyjścia, z tym, że zarówno źródłem wejściowym (ang. input source) jak i źródłem wyjściowym (ang. output source) może być wszystko, co zawiera dane: pliki, łańcuchy znakowe (ang. strings) lub pamięć. Począwszy od wersji Javy 1.1, oprócz dawnych klas czytających strumienie bajtów, zdefiniowano nowe, przeznaczone do poprawnego czytania strumieni znaków w standardzie Unicode. Operacje I/O w Javie są bezpieczne pod względem typu, co oznacza, że niemożliwe jest wykonanie błędnej operacji I/O dla danego typu danych.
Klasy obsługujące operacje I/O w Javie mają model warstwowy, co oznacza, że poszczególne klasy w Javie nie mają zbyt wielkich zdolności (ponieważ nie wszystkie są zawsze potrzebne). Zamiast tego programista może uzyskać potrzebne właściwości poprzez dodanie wyspecjalizowanych klas. Przykładowo, najprostsza klasa wejściowa: InputStream nie jest buforowana, ale klasa BufferedInputStreamInputStream.
Pakiet java.ioInputStreamOutputStream,
Klasa InputStream
Gdy tworzymy nowy strumień wejściowy, jest on automatycznie otwierany. Strumień wejściowy zostaje zamknięty albo poprzez użycie metody close, albo automatycznie, gdy do obiektu InputSteam
Klasa OutputStreamclose, albo automatycznie, gdy do obiektu OutputSteam
Pakiet java.ioInputStreamOutputStream, które implementują specyficzne funkcje wejścia i wyjścia. Przykładowo FileInputStreamFileOutputStream są strumieniami wejściowymi i wyjściowymi, które operują na plikach.
Jak widać, główną klasą wśród klas reprezentujących strumienie wejściowe jest InputStream. Z klasy InputStreamFilterInputStream (mająca cztery podklasy) oraz nowa w JDK1.1 klasa ObjectInputStream (nowe klasy pakietu JDK1.1 zostaną opisane później w tym rozdziale).
Rysunek 2-11 Hierarchia klas bajtowych strumieni wyjściowych pakietu java.io
Wśród klas reprezentujących strumienie wyjściowe podstawową jest OutputStream. Z klasy OutputStream dziedziczy pięć klas, w tym klasa abstrakcyjna FilterOutputStream (mająca trzy podklasy, w tym nową w JDK1.1 PrintStream) oraz nowa w JDK1.1 klasa ObjectOutputStream (opisana później w tym rozdziale).
Poniżej przedstawiono krótki opis klas dziedziczących bezpośrednio z klas InputStreamOutputStream:
FileInputStreamFileOutputStream
Czytają i zapisują dane do plików i urządzeń.
PipedInputStreamPipedOutputStream
Implementują potoki (ang. pipe) (każdy potok to kolejka FIFO). Służą do przesyłania danych z jednego programu lub wątku do drugiego. PipedInputStreamPipedOutputStreamPipedOutputStream z PipedInputStream. Przesyłanie danych jest zsynchronizowane, jeśli kolejka jest pusta obiekt odbierający dane czeka na jej wypełnienie przez dane z obiektu wysyłającego.
ByteArrayInputStreamByteArrayOutputStream
Czytają i zapisują dane do tablicy bajtów w pamięci.
SequenceInputStream
Łączy wiele strumieni wejściowych w jeden strumień wejściowy
StringBufferInputStream
Pozwala programom czytać z obiektu typu StringBuffer jeśli jest on strumieniem wejściowym.
Klasy abstrakcyjne FilterInputStream i FilterOutputStreamInputStream i OutputStream. Klasy te definiują interfejs dla strumieni filtrowanych, które przetwarzają dane w czasie ich czytania lub zapisywania.
DataInputStreamDataOutputStream
Czytają i zapisują dane prostych typów Javy w niezależnym od platformy sprzętowo-programowej formacie.
BufferedInputStreamBufferedOutputStream
Buforują dane w czasie czytania i zapisywania, przez co zmniejszają liczbę potrzebnych dostępów do źródła danych i przyspieszają czytanie i zapisywanie danych w porównaniu do strumieni nie buforowanych.
LineNumberInputStream
Przechowują liczbę przeczytanych linii (do czytania znaków zaleca się stosować nowszą klasę LineNumberReader, która poprawnie odczytuje np. znaki Unicode).
PushbackInputStream
Strumień wejściowy z buforem zwrotnym. Jest to przydatne np. przy czytaniu danych w postaci ciągu bajtów o różnej długości zakończonych bajtem oznaczającym koniec danych. Po odczytaniu bajtu oznaczającego koniec danych możemy cofnąć bufor do ciągu wejściowego i następna operacja read odczyta daną o określonej długości.
PrintStream
Strumień wyjściowy z przydatnymi metodami drukującymi.
W wersji Java1.1 do pakietu java.io dodano nowe klasy do czytania strumieni zawierających znaki (ang. character streams). Klasy te działają podobnie jak dotychczasowe klasy czytające bajty, ale zamiast czytać 8 bitowe bajty czytają i zapisują 16 bitowe znaki Unicode. Dzięki temu łatwo można pisać programy niezależne od standardu kodowania znaków, które mogą używać różnych alfabetów narodowych.
W programach operujących na znakach zaleca się zatem stosować nowe odpowiedniki dotychczasowych klas. Wyjątkiem jest praca z protokołami sieciowymi, gdzie 7 bitów bajtu określa znak ASCII.
Strumienie bajtowe są nadal użyteczne przy wykonywaniu wielu operacji wejścia wyjścia (do pliku lub sieci), jak np. odczytywanie dźwięków, zapisywanie danych binarnych czy implementacja protokołów sieciowych.
W większości przypadków najlepiej jednak używać nowych strumieni znakowych. Na poniższych rysunkach przedstawiono nowe klasy pakietu java.io implementujące strumienie znakowe.
Jak widać, większość klas wejściowych strumieni bajtowych ma swoje odpowiedniki znakowe. Podobnie sytuacja przedstawia się dla bajtowych strumieni wyjściowych, na poniższym rysunku przedstawiono hierarchie klas znakowych strumieni wyjściowych.
File
Klasa ta reprezentuje plik. Możemy tworzyć nowy plik (niezależnie od systemu, w jakim program jest uruchamiany) lub odczytywać potrzebne nam informacje o pliku.
FileDescriptor
Reprezentuje deskryptor otwartego pliku lub gniazdka (ang. socket).
RandomAccessFile
Pozwala na odczytywanie bajtów z dowolnego miejsca w pliku.
StreamTokenizer
Dzieli zawartość strumienia na leksemy (ang. token), przeważnie słowa i liczby.
Począwszy od wersji Javy 1.1 do dyspozycji mamy nowe klasy:
ObjectOutputStreamObjectInputStream
Użycie tych klas pozwala na zapisywanie i odczytywanie obiektów wraz z ich stanem do strumieni. W Javie proces ten nazywany jest serializacją (ang. serialization).
Obiekt klasy ObjectInputStreamObjectOutputStream. Użycie tych klas pozwala na implementacje np. trwałych (ang. persistent) obiektów w Javie, trwałych, to znaczy zachowujący swój stan w chwili przerwania działania. Możemy dzięki użyciu obiektów tych klas wspólnie z obiektami typu FileOutputStream i FileInputStream
Serializowane mogą być tylko obiekty, które implementują interfejsy java.io.Serializablejava.io.Externalizable. Do odczytania ze strumienia używamy metody readObject, której wynikiem jest obiekt typu Object, następnie odczytany obiekt zostaje przekonwertowany do pożądanego typu.
Przykład zapisania obiektów do pliku obiekty.oooostream klasy ObjectOutputStream:
FileOutputStream ostream = new FileOutputStream("obiekty.ooo");
ObjectOutputStream p = new ObjectOutputStream(ostream);
p.writeInt(12345);
p.writeObject("Today");
p.writeObject(new Date());
p.flush();
ostream.close();
Dane (pola danych lub zmienne lokalne) typów wewnętrznych (np. int, char) są zapisywane i odczytywane przy użyciu odpowiednich metod. Przy odczytywaniu obiektu przywracane są zawartości wszystkich pól danych z wyjątkiem pól typu statictransient, które są ignorowane. Referencje do innych obiektów w odczytywanym obiekcie powodują odczytanie tych obiektów ze strumienia w razie potrzeby.
Przykład odczytania obiektów z pliku z obiekty.oooObjectOutputStream:
FileInputStream istream = new FileInputStream("obiekty.ooo");
ObjectInputStream p = new ObjectInputStream(istream);
int i = p.readInt();
String dzis = (String)p.readObject();
Date date = (Date)p.readObject();
istream.close();
ObjectStreamClass
Klasa ObjectStreamClassObjectOutputStream. ObjectStreamClasslookup:
public static ObjectStreamClass lookup(Class cl)
Wynikiem wykonania tej metody jest null gdy, klasa nie implementuje interfejsu java.io.Serializable lub java.io.Externalizable.
DataInputDataOutput
Te dwa interfejsy opisują strumienie, które mogą czytać i zapisywać dane typów wewnętrznych Javy w niezależnym od platformy sprzętowo-programowej formacie. Klasy DataInputStream, DataOutputStreamRandomAccessFile
FilenameFilter
Metoda listFileFilenameFilter (filtr nazw plików) do ustawienia rodzajów plików, jakie mają być wynikiem wykonania metody list.
W wersji Javy 1.1 dodano nowe interfejsy:
Serializable
Dzięki implementacji interfejsu java.io.Serializable możemy zapisywać i odczytywać obiekty ze strumienia. Wszystkie podklasy klasy implementującej ten interfejs uzyskują tę możliwość. Interfejs Serializable
Externalizable
Ten interfejs pozwala klasom go implementującym na zdefiniowanie własnych metod do serializacji. Dwie metody interfejsu Externalizable: writeExternal i readExternal
ObjectInputObjectOutput
Interfejsy ObjectInputObjectOutput rozszerzają interfejsy odpowiednio: DataInputDataOutput aby umożliwić zapis i odczyt obiektów. DataInputDataOutput posiadają metody umożliwiające zapis danych typów wewnętrznych Javy, ObjectInputObjectOutput rozszerzają te klasy umożliwiając odpowiednio zapis i odczyt obiektów, tablic i danych typu String.
ObjectInputValidation
Interfejs pozwalający sprawdzić obiekty w grafie obiektów. Pozwala na to, aby obiekt był wołany gdy cały graf obiektów został odtworzony (ang. deserialized).
W pierwszym przykładzie do operacji ze strumieniami użyto klas strumieni bajtowych. Natomiast w drugim przykładzie wykorzystano klasy strumieni znakowych.
Wykonanie poniższej aplikacji powoduje utworzenie pliku grupaI9.dat
import java.io.*;
class TestIO
{
public static void main(String[] args)
{
try
{
// Utworzenie strumienia wyjściowego danych zapisującego
// do strumienia plikowego (czyli do pliku grupaI9.dat)
DataOutputStream dos = new DataOutputStream(new FileOutputStream("grupaI9.dat"));
String studenci[] = { "Jan Well", "Ola Jawa", "Oleg Tork","Ho Si Wan", "Radek Nowak" };
double srednie[] = { 3.21, 4.65, 4.01, 4.12, 3.42 };
// zapisanie danych do pliku
for (int i = 0; i < srednie.length; i ++)
{
dos.writeDouble(srednie[i]);
dos.writeChar('\t');
dos.writeChars(studenci[i]);
dos.writeChar('\n');
}
dos.close();
}
catch (IOException e)
{ System.err.println("DataIOTest: " + e); }
try
{
// utworzenie strumienia wejściowego danych, którego źródłem
// jest plikowy strumień wejściowy (czytanie z pliku)
DataInputStream dis = new DataInputStream( new FileInputStream("grupaI9.dat"));
double srednia;
double sredniaGrupy = 0.0d;
char chr;
byte licz = 0;
try
{
// odczytanie danych z pliku i wyprowadzenie ich na ekran
while (true)
{
srednia = dis.readDouble();
StringBuffer bufor = new StringBuffer(10);
while ((chr = dis.readChar()) != '\n')
bufor.append(chr);
System.out.println("Student: " + bufor + " uzyskal(a) srednia ocen: " + srednia);
sredniaGrupy = sredniaGrupy + srednia;
licz++;
}
}
catch (EOFException e) { }
// Wyprowadzenie na ekran średniej ocen grupy studentów
System.out.println("Srednia grupy: " + sredniaGrupy/licz);
dis.close();
}
catch (FileNotFoundException e)
{System.err.println("Blad przy operacji na pliku: " + e);}
catch (IOException e)
{System.err.println("Blad wejscia wyjscia: " + e);}
}
}
Po wykonaniu powyższej aplikacji na ekranie otrzymamy:
D:\!Prace J++\IO streams
1.1>java TestIO
Student: Jan Well uzyskal(a) srednia ocen: 3.21
Student: Ola Jawa uzyskal(a) srednia ocen: 4.65
Student: Oleg Tork uzyskal(a) srednia ocen: 4.01
Student: Ho Si Wan uzyskal(a) srednia ocen: 4.12
Student: Radek Nowak uzyskal(a) srednia ocen: 3.42
Srednia grupy: 3.8820000000000006
Druga przykładowa aplikacja ilustruje wykorzystanie klas FileReader, FileWriter, BufferedReader, PrintWriter, PipedReader, PipedWriter, StreamTokenizer Z pliku dane.txt
Strumień wejściowy FileReaderFileWriterwyniki.txtPrinterWriterPipedWriterPipedReader (dzięki użyciu których zapewniona jest synchronizacja przepływu danych między wątkami).
Metoda obliczPotege jako parametr wejściowy dostaje strumień wejściowy dane typu FileReader, zawierający liczby, dla których zostanie obliczona trzecia potęga.
W metodzie obliczPotege ten strumień wejściowy zostaje skierowany do strumienia BufferedReader (przyspieszającego czytanie). W metodzie obliczPotege tworzony jest PipedWriterPipedReader. Następnie strumień typu PipedWriterPrintWriter, co umożliwia użycie metody println do zapisywania wyników obliczeń do strumienia PipedWriter.
Po wykonaniu tych operacji tworzony jest wątek watekPotegiPrintWriter dołączony do PipedWriterBufferedReader dołączony do źródła danych) i następnie wątek zostaje uruchamiany.
Wątek typu watekPotegi, w metodzie runBufferedReader, oblicza ich potęgę i zapisuje do strumienia wyjściowego PrintWriter. Ponieważ PipedWriterPipedReader, wszystkie dane zapisane do PipedWriterPipedReader. Dane mogą być czytane z PipedReaderobliczPotegePipedReader, dzięki czemu dane mogą być czytane przez inny program lub wątek. W naszej przykładowej aplikacji PIPEstreams dane są czytane przez wątek watekSinusy uruchamiany w metodzie obliczSinus.
Dzięki uruchomieniu dwu wątków, każdy z potoków (ang. pipe) działa niezależnie od drugiego oraz zapobiega to zablokowaniu metody main, w przypadku, gdy jeden z końców potoku (pipe) jest zablokowany w oczekiwaniu na zakończenie operacji I/O.
import java.io.*;
class PIPEstreams {
public static void main(String[] args)
{
try
{
FileReader dane = new FileReader("dane.txt");
FileWriter wyniki = new FileWriter("wyniki.txt");
// obliczenie potęgi i sinusa
Reader liczby = obliczSinus(obliczPotege(dane));
// wyświetlenie danych na ekranie oraz zapisanie ich do pliku
BufferedReader br = new BufferedReader(liczby);
String input;
PrintWriter stdout = new PrintWriter(System.out, true);
while ((input = br.readLine()) != null) {
stdout.println(input);
wyniki.write(input+"\n");
}
br.close();
wyniki.close();
}
catch (Exception e)
{ System.err.println(e); }
}
public static Reader obliczPotege(Reader zrodlo)
{
PipedWriter pw = null;
PipedReader pr = null;
try {
BufferedReader br = new BufferedReader(zrodlo);
pw = new PipedWriter();
pr = new PipedReader(pw);
PrintWriter output = new PrintWriter(pw);
new watekPotegi(output, br).start();
}
catch (Exception e)
{ System.err.println(e); }
return pr;
}
public static Reader obliczSinus(Reader zrodlo)
{
PipedWriter pw = null;
PipedReader pr = null;
try {
BufferedReader br = new BufferedReader(zrodlo);
pw = new PipedWriter();
pr = new PipedReader(pw);
PrintWriter output = new PrintWriter(pw);
new watekSinusy(output, br).start();
}
catch (Exception e)
{ System.err.println(e); }
return pr;
}
}
Poniżej przedstawiono definicję klasy watekPotegiStreamTokenizer. StremTokenizernextTokenStremTokenizer.TT_NUMBER (liczba), StreamTokenizer.TT_WORD (słowo), StreamTokenizer.TT_EOF (koniec pliku), StreamTokenizer.TT_EOL (koniec linii). Wartość pobrana przez metodę przechowywana jest w polu nval (gdy jest to liczba) lub sval (słowo) obiektu typu StreamTokenizer.
import java.io.*;
class watekPotegi extends Thread {
PrintWriter pw;
BufferedReader br;
watekPotegi(PrintWriter pw, BufferedReader br)
{
this.pw = pw;
this.br = br;
}
public void run() {
if (pw != null && br != null) {
try {
StreamTokenizer tokenizer = new StreamTokenizer(br);
int coNastepne;
while((coNastepne = tokenizer.nextToken()) != StreamTokenizer.TT_EOF)
{
double number = 0;
if ((coNastepne == StreamTokenizer.TT_NUMBER) )
{
number = tokenizer.nval;
pw.println(number+" szescian "+Math.pow(number,2));
}
// odblokowanie potoku (PIPE) do odczytu
//przez inne programy lub watki
pw.flush();
}
pw.close();
}
catch (IOException e)
{ System.err.println("blad w metodzie run watekPotegi: "+e);}
}
}
protected void finalize()
{
try
{
if (pw != null)
{ pw.close(); pw = null; }
if (br != null)
{ br.close(); br = null; }
}
catch (IOException e)
{
System.err.println("blad w metodzie finalize watekPotegi: "+ e);
}
}
}
Definicja drugiego z używanych przez nas w aplikacji PIPEstreamswatekPotegi różni się tylko w definicji metody run. Dlatego w poniższym przykładzie przedstawiono tylko tę metodę.
Do strumienia wyjściowego przepisywane są wszystkie słowa i liczby pobrane ze strumienia wejściowego, oprócz ostatniej liczby w linii, dla której obliczany jest jej sinus.
public void run()
{
if (pw != null && br != null)
{
try
{
StreamTokenizer tokenizer = new StreamTokenizer(br);
// ponizsza metoda umożliwia traktowanie jako tokenu znaku
// końca linii
tokenizer.eolIsSignificant(true);
int coNastepne;
coNastepne = tokenizer.nextToken();
while(coNastepne != StreamTokenizer.TT_EOF)
{
double number = 0;
// jesli koniec linii
if (coNastepne == StreamTokenizer.TT_EOL)
{
number = tokenizer.nval;
pw.println("\tsin("+number+")\t= "+Math.sin(number));
}
else
{
if (coNastepne == StreamTokenizer.TT_NUMBER)
pw.print(" "+tokenizer.nval);
if (coNastepne == StreamTokenizer.TT_WORD)
pw.print(" "+tokenizer.sval);
}
coNastepne = tokenizer.nextToken();
pw.flush();
}
pw.close();
}
catch (IOException e)
{
System.err.println("blad w metodzie run watekSinusy: "+e);
}
}
}
Programy pracujące w sieci Internet oparte są na koncepcji klient/serwer. Gdzieś w sieci Internet (lub lokalnie na naszym komputerze) znajduje się program serwera, który dostarcza naszemu programowi klienta potrzebnych danych. Dane te to np. pliki, gdy łączymy się z serwerem FTP, poczta elektroniczna przysłana do nas (serwer POP), strony WWW (serwer HTTP), możemy także wysłać dane do serwera w celu ich przetworzenia lub skorzystania z jakiejś usługi np. połączenie z serwerem SMTP pozwala nam na wysłanie poczty elektronicznej. Aby skorzystać z tych usług na nasz komputer musi obsługiwać protokoły TCP i UDP.
W Javie czasami korzystamy z możliwości sieciowych bez implementacji ich, np. gdy ładujemy rysunek z odległego hosta:
Image image = getImage(new URL("http://cari.put.poznan.pl/cos.gif"));
Java stała się najpopularniejszym językiem programowania w Internecie nie tylko dlatego, że umożliwia pisanie programów działających w heterogenicznych środowiskach ale także dzięki możliwościom sieciowym, jakie dostarcza pakiet java.net
URL jest skrótem od ''Uniform Resource Locator'' oznaczającym referencje (adres) do zasobu w sieci Internet. Wpisanie w przeglądarce adresu URL: "http://www.put.poznan.pl" powoduje połączenie z serwerem WWW Politechniki Poznańskiej (z wykorzystaniem protokołu HTTP), natomiast adres URL: "ftp://ftp.amu.edu.pl" powoduje połączenie z serwerem FTP w Uniwersytecie Adama Mickiewicza w Poznaniu (jeśli przeglądarka obsługuje ten protokół).
Programy napisane w Javie mogą korzystać z URL'a w celu znalezienia zasobów sieci Internet, do których chcemy uzyskać dostęp. Pakiet java.net zawiera klasę o nazwie URL, która może zostać użyta do reprezentacji adresów URL (poprzez obiekt klasy URL) w programach Javy. Należy rozróżnić adres URL, opisujący zasób sieci Internet, od obiektu klasy URL reprezentującego adres URL w Javie.
Przykład użycia klasy URL ilustruje poniższy aplet. Po wpisaniu poprawnego adresu URL w polu tekstowym apletu, w przeglądarce wyświetlana jest strona WWW (lub obsługiwana jest inna usługa, np.: FTP, Telnet, - jeśli przeglądarka obsługuje te protokoły) opisana adresem URL.
Aby stworzyć stronę HTML zawierającą aplet ZaladujURL
<html><head><title>AWTLoadDocument</title></head><body>
<applet
code=ZaladujURL.class
width=320
height=60 >
</applet>
</body></html>
import java.applet.*;
import java.awt.*;
public class ZaladujURL extends Applet
{
Button btnZaladuj = new Button("Zaladuj strone");
TextField txtCo = new TextField("http://friko.onet.pl/po/arturt/index.html");
public void init()
{
setLayout(new BorderLayout());
add("North",txtCo);
add("Center",btnZaladuj);
}
public boolean action(Event e, Object arg)
{
if (e.target instanceof Button)
{
try
{
getAppletContext().showDocument(new java.net.URL(txtCo.getText()),"_self");
/* opis parametrów metody showDocument
"_self" wyświetla dokument w bieżącym oknie
"_parent" wyświetla w bieżącej ramce (ang. frame)
"_top" wyświetla w szczytowej ramce
"_blank" wyświetla w nowym oknie bez nazwy
name wyświetla w nowym oknie o nazwie: name
*/
}
catch (Exception exc)
{
exc.printStackTrace();
txtCo.setText("Zly adres");
}
return true;
}
return false;
}
}
Dzięki użyciu metody getAppletContext uzyskujemy dostęp do przeglądarki, w której uruchamiany jest aplet i możemy skorzystać z metody showDocument, która każe przeglądarce wyświetlić stronę opisaną adresem zawartym w obiekcie URL. W tym celu tworzymy nowy obiekty typu URLString
Ponieważ w chwili pisania tej pracy nie była dostępna przeglądarka obsługująca nowy standard Javy 1.1. Aplet ZaladujURL
Klasa URL
tworzący obiekt URL znakowego,
tworzący obiekt URL
tworzący obiekt URL
tworzący nowy obiekt URLURL
Przykładowo, tworzymy obiekt URL:
URL mojProvider = new URL("http://friko.onet.pl");
i chcemy stworzyć nowy obiekt URL opisujący adres URL mojej strony domowej:
URL mojaStrona = new URL(mojProvider, "/po/arturt/index.html");
Możemy także stworzyć obiekt URL odnoszący się do wybranego miejsca na stronie HTML (ang. anchor):
URL koniecMojejStrony = new URL(mojaStrona, "#KONIEC_STRONY");
Powyższy obiekt można oczywiście stworzyć używając pierwszego konstruktora:
URL koniecMojejStrony = new URL("http://friko.onet.pl/po/arturt/index.html#KONIEC_STRONY");
Użycie konstruktora postaci:
URL mojaStrona = new URL("http","friko.onet.pl","/po/arturt/index.html");
jest równoważne z URL("http://friko.onet.pl/po/arturt/index.html").
Natomiast:
URL mojaStrona = new URL("http","friko.onet.pl",80,"/po/arturt/index.html");
z URL("http://friko.onet.pl:80/po/arturt/index.html"), pojawił się tu nowy argument oznaczający port wykorzystywany podczas połączenia.
Po stworzeniu obiektu URLURLConnection
import java.net.*;
import java.io.*;
class TestPolaczenia
{
public static void main(String[] args)
{
try
{
// Utworzenie obiektu URL
URL friko = new URL("http://friko.onet.pl/");
// otwarcie połączenia z URL, metoda openConection zwraca
// referencję do obiektu typu URLConnection, dzięki któremu
// możemy czytać i zapisywać dane, z utworzonego połączenia
URLConnection polaczenieDoFriko = friko.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(polaczenieDoFriko.getInputStream()));
String inputLine;
// wyprowadzenie odczytanych z serwera HTTP friko.onet.pl danych na ekran
while ((inputLine = br.readLine()) != null)
{ System.out.println(inputLine); }
br.close();
}
catch (MalformedURLException me)
{System.out.println("MalformedURLException: " + me);}
catch (IOException ioe)
{System.out.println("IOException: " + ioe);}
}
}
Aby zapisać (wysłać do serwera) dane do strumienia wyjściowego połączenia z URL, po drugiej stronie musi być np. skrypt CGI potrafiący te dane odebrać i wykorzystać.
Klasy URLURLConnection wykorzystywane są do komunikacji na relatywnie wysokim poziomie i do specyficznych zastosowań (dostęp do zasobów Internetu). Niekiedy programy wymagają niższego poziomu komunikacji w sieci, np. wtedy, gdy chcemy napisać aplikację typu klient/serwer.
W aplikacjach klient-serwer, serwer dostarcza pewnych usług, jak np.: wykonanie zapytań do bazy danych. Klient natomiast wykorzystuje dane dostarczone przez serwer. Komunikacja między serwerem i klientem musi być niezawodna (dane nie mogą być gubione i muszą przybywać do w tej samej kolejności w jakiej zostały wysłane).
Protokół TCP dostarcza niezawodnego, punkt-punkt (ang. point-to-point) kanału komunikacyjnego, który jest używany do komunikacji przez aplikacje klient/serwer w Internecie. Klasy Javy SocketServerSocketjava.net
Przedstawiony w przykładach 2.46SocketServerSocket oraz klas strumieni wejściowych i wyjściowych do komunikacji pomiędzy serwerem i klientem z użyciem protokołu TCP.
Jako pierwszy musi zostać uruchomiony program serwera, aby klient miał się z czym łączyć.
Na innym komputerze w sieci (lub jak na poniższym przykładzie w innej sesji, na tym samym komputerze) uruchamiamy program klienta.
Aplikacja klienta MojKlient łączy się z serwerem MojSerwer
Klient może wysłać do serwera: polecenie "Czesc" - serwer odpowiada wtedy "Co tam?", liczbę - serwer odsyła kwadrat odebranej liczby, polecenie "Koniec" - powodujące zamknięcie połączenia i zakończenie pracy serwera. W przypadku wysłania przez klienta pustej linii serwer odpowiada "Dostałem puste ?!" a dla innych poleceń serwer odpowiada "Nie zinterpretowane". Użycie tak zdefiniowanego sposobu komunikacji po stronie klienta (i odpowiedzi serwera), pokazano na ilustracji 2-28.
Przykład 2.46 Kod serwera MojSerwer
import java.net.*;
import java.io.*;
class MojSerwer
{
public static void main(String[] args)
{
ServerSocket gniazdkoSerwera = null;
int nrPortu = 3535;
try
{
// utworzenie gniazdka na którym nasłuchuje serwer
gniazdkoSerwera = new ServerSocket(nrPortu);
System.out.println("Serwer nasluchuje na porcie: " + nrPortu);
}
catch (IOException e)
{
System.out.println("Nasluchiwanie na porcie: " + nrPortu + " niemozliwe, " + e);
System.exit(1);
}
Socket gniazdkoKlienta = null;
try
{
// serwer czeka na połączenie
gniazdkoKlienta = gniazdkoSerwera.accept();
// połączenie zostało nawiązane (wykonano metode accept)
System.out.println("Operacja accept wykonana! :)");
}
catch (IOException e)
{
System.out.println("Operacja accept nie powiodla sie: " + nrPortu + ", " + e);
System.exit(1);
}
try
{
// ustawienie jako strumienia wejściowego danych przysłanych
// przez klienta do gniazdka
BufferedReader br = new BufferedReader(new InputStreamReader(gniazdkoKlienta.getInputStream()));
// ustawienie jako strumienia wyjściowego danych zapisywanych
// przez serwer do strumienia PrintWriter
// dane te następnie poprzez gniazdko zostają wysłane do
// programu klienta
PrintWriter pw = new PrintWriter(new BufferedOutputStream(
gniazdkoKlienta.getOutputStream(), 1024), false);
pw.println("Serwer wita !");
pw.flush();
int i=0;
String liniaWe, liniaWy;
// pętla while implementuje wymianę komunikatów pomiędzy
// klientem i serwerem
while ((liniaWe = br.readLine()) != null)
{
liniaWy = odpowiedzSerwera(liniaWe);
pw.println(liniaWy);
// wyczyszczenie strumienia (wysłanie danych do serwera)
pw.flush();
if (liniaWy.equals("Koniec pracy serwera")) break;
}
// zamknięcie strumieni i gniazdek, zakończenie połączenia
pw.close();
br.close();
gniazdkoKlienta.close();
gniazdkoSerwera.close();
}
catch (IOException e)
{ e.printStackTrace(); }
}
// metoda odpowiedzSerwera definiuje odpowiedzi serwera
// na polecenia przysłane przez klienta
static String odpowiedzSerwera(String s)
{
try
{
int i = Integer.parseInt(s);
return "Obliczony kwadrat liczby "+ i +" wynosi:"+ i*i;
}
catch (NumberFormatException nfe)
{}
if (s.equals(null)) return "Dostalem puste ?!";
if (s.equals("Czesc")) return "Co tam?";
if (s.equals("Koniec")) return "Koniec pracy serwera";
return s+" Nie zinterpretowane";
}
}
Aplikacja MojKlient, której kod przedstawiono poniżej, łączy się z serwerem, odczytuje polecenia z klawiatury, wysyła je do serwera, odbiera otrzymane dane i wyświetla je na ekranie.
import java.io.*;
import java.net.*;
public class MojKlient
{
public static void main(String[] args)
{
Socket gniazdkoMojSerwer = null;
PrintWriter pw = null;
BufferedReader br = null;
// nazwaHosta określa host na którym uruchomiono
// aplikację serwera; ponieważ program klienta i serwera
// uruchomiono na tym samym komputerze wartością tej
// zmiennej jest 'localhost' deklaracja postaci:
// String nazwaHosta = "coral.put.poznan.pl";
// oznacza, że aplikację serwera uruchomiono na hoście
// coral.put.poznan.pl
String nazwaHosta = "localhost";
int nrPortu = 3535;
try
{
// próba połączenia z serwerem znajdującym się na hoście
// o nazwie "nazwaHosta" nasłuchującym na porcie o numerze
// równym wartości zmiennej "nrPortu"
gniazdkoMojSerwer = new Socket(nazwaHosta, nrPortu);
// ustawienie jako strumienia wyjściowego danych zapisywanych
// przez klienta do strumienia PrintWriter
// dane te następnie poprzez gniazdko zostają wysłane do
// programu serwera
pw = new PrintWriter(gniazdkoMojSerwer.getOutputStream());
// ustawienie jako strumienia wejściowego danych przysłanych
// przez serwer do gniazdka
br = new BufferedReader(new InputStreamReader(gniazdkoMojSerwer.getInputStream()));
}
catch (UnknownHostException e)
{ System.err.println("Host: "+ nazwaHosta +" nieznany"); }
catch (IOException e)
{System.err.println("Blad operacji I/O dla: "+nazwaHosta);}
if (gniazdkoMojSerwer != null && pw != null && br != null)
{
try
{
StringBuffer buf = new StringBuffer(50);
int c;
String odpSerwera;
// w pętli while odczytane z klawiatury polecenia, wysyłane
// są do aplikacji serwera, a następnie wyświetlane są
// odpowiedzi przysłane przez serwer
while ((odpSerwera = br.readLine()) != null)
{
System.out.println("Serwer: " + odpSerwera);
if (odpSerwera.equals("Koniec pracy serwera")) break;
// odczytanie linii danych z klawiatury
while ((c = System.in.read()) != '\n')
{ buf.append((char)c); }
System.out.println("Klient: " + buf);
pw.println(buf.toString().trim());
// wyczyszczenie strumienia (wysłanie danych do serwera)
pw.flush();
buf.setLength(0);
}
pw.close();
br.close();
gniazdkoMojSerwer.close();
}
catch (UnknownHostException e)
{
System.err.println("Proba polaczenia do nieznanego hosta: " + e);
}
catch (IOException e)
{ System.err.println("Blad I/O: " + e);}
}
}
}
Niektóre aplikacje komunikujące się ze sobą poprzez sieć, nie potrzebują stałego, dostarczanego przez TCP, kanału komunikacyjnego. Stosowana jest wtedy komunikacja bezpołączeniowa dostarczana przez protokół UDP. Protokół UDP, definiuje sposób komunikacji, w którym dane wysyłane są w pakietach nazywanych datagramami. Komunikacja za pomocą datagramów z użyciem protokołu UDP implementowana jest w klasach DatagramPacketDatagramSocketjava.net.
Programy klienta i serwera, które komunikują się ze sobą z wykorzystaniem kanału komunikacyjnego (takiego jak URL lub gniazdko), posiadają dedykowany kanał między nimi. Proces wymiany informacji polega na: ustanowieniu połączenia, transmisji danych i zamknięciu połączenia. Wszystkie dane wysyłane przez kanał komunikacyjny zostają odebrane w tej samej kolejności, w jakiej zostały wysłane. Jest to zagwarantowane przez kanał komunikacyjny.
Inaczej jest, gdy programy komunikują się poprzez datagramy, wysyłają i odbierają niezależne pakiety danych. Wtedy programy te nie mają dedykowanego im kanału komunikacyjnego. Dostarczenie datagramu do jego miejsca przeznaczenia nie jest zagwarantowane, tak jak kolejność ich przybycia.
Aby zilustrować użycie datagramów napisano aplikację serwera SerwerPolecenCoMamZrobic. Klient łączy się z serwerem podając nazwę użytkownika, jeśli aplikacja serwera znajdzie plik tekstowy o nazwie nazwa_użytkownika.txt
Na poniższej ilustracji pokazano informacje jakie są wyświetlane przez program serwera w czasie jego działania.
Na innym komputerze w sieci (lub jak tak jak na poniższym przykładzie w innej sesji) uruchamiamy program klienta z następującymi parametrami:
java CoMamZrobic nazwaHosta nrPortu użytkownik
gdzie:
nazwaHostaSerwerPolecen. W przykładzie jest to adres 127.0.0.1 oznaczający bieżący komputer, dzięki czemu można tego typu programy testować lokalnie.
nrPortu
użytkownikzamknij.txt,
Na poniższym listingu przedstawiono kod serwera SerwerPolecenmain utworzony i uruchomiony jest wątek typu WatekSerweraPolecen
class SerwerPolecen
{
public static void main(String[] args)
{
new WatekSerweraPolecen().start();
}
}
Cała komunikacja z użyciem datagramów realizowana jest właśnie przez wątek WatekSerweraPolecen
import java.io.*;
import java.net.*;
import java.util.*;
class WatekSerweraPolecen extends Thread
{
private DatagramSocket m_gniazdkoDat = null;
private BufferedReader m_strumienPolecen = null;
WatekSerweraPolecen()
{
super("Watek Serwera Polecen");
try
{
// utworzenie nowego gniazdka przeznaczonego do komunikacji
// z wykorzystaniem datagramów
m_gniazdkoDat = new DatagramSocket();
// zarezerwowanie portu do komunikacji datagramowej, metoda
// getLocalPort rezerwuje najbliższy wolny port. Dzięki temu
// unikamy sytuacji gdy port, który chcemy zająć jest
// wykorzystywany przez inną usługę.
System.out.println("SerwerPolecen nasluchuje na porcie: " +
m_gniazdkoDat.getLocalPort());
}
catch (java.io.IOException e)
{ System.err.println("Nie moge utworzyc gniazdka I/O datagramow."); }
}
public void run()
{
if (m_gniazdkoDat == null)
return;
while(true)
{
try
{
byte[] bufor = new byte[256];
DatagramPacket pakiet;
InetAddress adres;
int port;
String daneDoWyslania = null;
// odebranie zapytania od klienta
pakiet = new DatagramPacket(bufor, 256);
m_gniazdkoDat.receive(pakiet);
adres = pakiet.getAddress();
port = pakiet.getPort();
String daneOdebrane = new String(pakiet.getData()).trim();
System.out.println("Zapytanie o informacje dla " + daneOdebrane);
// wysłanie odpowiedzi
// pobranie polecenia z pliku
daneDoWyslania = polecenie(daneOdebrane);
// zamienia string w tablice bajtów w celu
// umożliwienia wysłania jej w datagramie
bufor = daneDoWyslania.getBytes();
// utworzenie pakietu przeznaczonego do wysłania
pakiet = new DatagramPacket(bufor, bufor.length, adres, port);
// wysłanie datagramu
m_gniazdkoDat.send(pakiet);
// Jeśli w polu użytkownik zostało podane słowo zamknij
// serwer kończy swoje działanie
if (daneOdebrane.compareTo("zamknij")==0) break;
}
catch (IOException e)
{
System.err.println("IOException: " + e);
e.printStackTrace();
}
}
}
protected void finalize()
{
if (m_gniazdkoDat != null)
{
m_gniazdkoDat.close();
m_gniazdkoDat = null;
System.out.println("Zamknięcie gniazdka datagramow.");
}
}
// metoda polecenie, czyta z pliku polecenia
// przeznaczone dla użytkownika
private String polecenie(String kto)
{
String linia = "Nie ma polecen dla "+ kto;
try
{
StringBuffer info = new StringBuffer("Wiadomosc wyslana " + new Date().toLocaleString()+": \n");
m_strumienPolecen = new BufferedReader(new InputStreamReader(new FileInputStream(kto+".txt")));
while((linia = m_strumienPolecen.readLine())!=null)
info.append(linia+"\n");
return info.toString();
}
catch (FileNotFoundException e)
{
System.err.println("Nie moge otworzyc pliku z poleceniami. " + kto + ".txt");
}
catch (IOException e)
{ linia = "Na serwerze wystapil blad IOException."; }
return linia;
}
}
Aplikacja klienta wysyła na adres i port podane jako parametr wywołania aplikacji, nazwę użytkownika podaną jako ostatni parametr wywołania. Następnie odbierane są dane odesłane przez serwer i wyświetlane na ekranie.
import java.io.*;
import java.net.*;
import java.util.*;
class CoMamZrobic
{
public static void main(String[] args)
{
int port;
InetAddress adres;
DatagramSocket gniazdko = null;
DatagramPacket pakiet;
byte[] buforWys = new byte[256];
if (args.length != 3)
{
System.out.println("Uzycie: java CoMamZrobic <nazwahosta>" + " <port#> <uzytkownik>");
return;
}
try
{
// przyłączenie do gniazdka
gniazdko = new DatagramSocket();
}
catch (java.io.IOException e)
{ System.err.println("Nie moge utworzyc gniazdka I/O datagramow."); }
if (gniazdko != null)
{
try
{
// wysłanie zapytania
adres = InetAddress.getByName(args[0]);
port = Integer.parseInt(args[1]);
buforWys = args[2].getBytes();
// utworzenie datagramu
pakiet = new DatagramPacket(buforWys, buforWys.length, adres, port);
// wysłanie
gniazdko.send(pakiet);
// odbiór odpowiedzi
byte buforOdb[] = new byte[256];
pakiet = new DatagramPacket(buforOdb, 256);
gniazdko.receive(pakiet);
String tekstOdebrany = new String(pakiet.getData()).trim();
System.out.println("Polecenie:\n" + tekstOdebrany);
gniazdko.close();
}
catch (IOException e)
{
System.err.println("Wyjatek IOException: " + e);
e.printStackTrace();
}
}
}
}