12. Programowanie sieciowe
12.1. Sterowanie przeglądarką
Najprostszym i najczęściej stosowanym zabiegiem jest kojarzenie w apletach przycisków z adresami w Internecie. Pozwala to wyświetlać strony WWW za pomocą naciśnięcia przycisku.
Do przechowywania adresów służy klasa URL dostępna w pakiecie java.net. Obiekty tej klasy można tworzyć na różne sposoby:
URL adres = new URL ( String < protokół >,
String < nazwaSerwera >,
int < numerPortu >,
String < ścieżkaDostępu > );
URL adres = new URL ( String < protokół >,
String < nazwaSerwera >,
String < ścieżkaDostępu > );
URL adres = new URL ( String < ścieżkaBazowa >,
String < ścieżkaWzględna > );
URL adres = new URL ( String < adresSieciowy > );
Na przykład, obiekt zawierający adres strony IPPT PAN można utworzyć w taki sposób:
String url = ”http://www.ippt.gov.pl”;
try { URL adres = new URL ( url ); }
catch ( MalformedURLException e ) {
System.out.println ( ”Błędny adres URL” );
}
Po utworzeniu obiektu można go wyświetlić w przeglądarce. Służą do tego dwie metody klasy URL:
getAppletContext ( ) oraz showDocument ( ).
Obiekt klasy AppletContext zawiera informacje o środowisku, w którym działa aplet, czyli o przeglądarce. Po utworzeniu takiego obiektu zleceniem
AppletContext ac = getAppletContext ( );
możemy zarówno sterować przeglądarką, jak i komunikować się z innymi apletami. Na przykład, możemy wyświetlić stronę WWW o adresie adresURL za pomocą zlecenia
ac.showDocument ( adresURL );
Przykładowy aplet Ulubione tworzy 3 przyciski, które wywołują wybrane strony WWW:
1) informację o trasach zjazdowych w Szczyrku;
2) serwer pocztówek;
3) stronę domową Javy.
Przykład
import java.awt.*;
import java.awt.event.*;
import java.net.URL;
import java.net.MalformedURLException;
public class Ulubione extends java.applet.Applet
implements ActionListener {
final int MAX = 4; // liczba przycisków
final int H = 30; // odstęp w poziomie
final int V = 0; // odstęp w pionie
final int D = 10; // margines
final String [ ] [ ] zakładki = {
{ "Szczyrk", "http://www.petex.bielsko.pl/wyciag/szczyrk/" },
{ "Pocztówki", "http://www.rava.com.pl/kartki/" },
{ "Java", "http://java.sun.com" },
{ "Koniec", " " }
};
Button przyciski [ ] = new Button [ MAX ];
public Insets getInsets ( ) {
return new Insets ( D, D, D, D );
}
Button dodajPrzycisk ( String nazwa ) {
Button przycisk = new Button ( nazwa );
add( przycisk );
przycisk.addActionListener ( this );
return przycisk;
}
public void actionPerformed ( ActionEvent e ) {
Button przycisk = ( Button ) e.getSource ( );
String nazwa = przycisk.getLabel ( );
if ( nazwa != zakładki [ MAX ] [ 0 ] ) {
wyświetl ( nazwa );
}
else
this.setVisible ( false );
}
public void init ( ) {
setBackground ( Color.blue );
setLayout ( new FlowLayout ( FlowLayout.CENTER,
H, V ) );
for ( int i = 0; i < MAX; i++ ) {
przyciski [ i ] = dodajPrzycisk ( zakładki [ i ] [ 0 ] );
}
}
void wyświetl ( String name ) {
URL adr = null;
for ( int i = 0; i < 3; i++ ) {
if ( name.equals ( zakładki [ i ] [ 0 ] )) {
try { adr = new URL ( zakładki [ i ] [ 1 ] ); }
catch ( MalformedURLException e ) {
System.out.println ( "Zły adres: " + adr );
}
}
}
if ( adr != null )
getAppletContext ( ).showDocument ( adr );
}
}
12.2. Zdalny dostęp do plików
Klasy pakietu java.net pozwalają nawiązywać połączenia ze zdalnym komputerem i pobierać z niego pliki. Należy przy tym pamiętać, że aplet ma dostęp do lokalnego dysku jedynie na tym serwerze, na którym sam jest zainstalowany.
Metoda openStream ( ) klasy URL otwiera połączenie sieciowe z plikiem o podanym adresie URL. Protokół połączenia (np. ftp lub http) określony jest w adresie URL. Metoda ta zwraca obiekt klasy InputStream, który można skonwertować do postaci
BufferedInputStream lub
DataInputStream.
Oto jak można zdalnie przeczytać plik o adresie mojURL:
try {
InputStream we = mójURL.openStream ( );
DataInputStream dane = new DataInputStream
( new BufferedInputSteam ( we ) );
String line;
while ( ( line = dane.readLine ( ) ) != null ) {
System.out.println ( line );
}
}
catch ( IOException e ) {
System.out.println ( ”Błąd wejścia/wyjścia: ” +
e.getMessage ( ) );
}
Przykładowy aplet CzytajZdalnie łączy się z podanym serwerem, otwiera na nim plik tekstowy i wyświetla zawartość tego pliku na ekranie. Występuje w nim pomocnicza klasa URLConnection, której metoda connect ( ) powoduje otwarcie połączenia sieciowego.
Przykład
import java.awt.*;
import java.io.*;
import java.net.*;
public class CzytajZdalnie extends java.applet.Applet
implements Runnable {
URL adres;
Thread wątek;
TextArea tekst = new TextArea ( "Pobieranie tekstu ..." );
public void init ( ) {
setLayout ( new GridLayout ( 1, 1 ) );
String url =
"http://www.ippt.gov.pl/~abork/testowy.txt";
try { this.adres = new URL ( url ); }
catch ( MalformedURLException e ) {
System.out.println ( "Błędny adres: " + adres );
}
add ( tekst );
}
…
// pominięte metody start ( ), stop ( )
…
public void run ( ) {
URLConnection połączenie = null;
BufferedReader dane = null;
String wiersz;
StringBuffer buf = new StringBuffer ( );
try {
połączenie = this.adres.openConnection ( );
połączenie.connect ( );
tekst.setText ( "Otwieram połączenie ..." );
dane = new BufferedReader ( new
InputStreamReader ( połączenie.getInputStream ( ) ) );
tekst.setText ( "Czytam dane ..." );
while ( ( wiersz = dane.readLine ( ) ) != null ) {
buf.append ( wiersz + "\n" );
}
tekst.setText ( buf.toString ( ) );
}
catch ( IOException e ) {
System.out.println ( "Błąd wejścia/wyjścia: " +
e.getMessage ( ) );
}
}
}
12.3. Architektura klient-serwer
Wiele programów działających w środowisku sieciowym działa na zasadzie współpracy par klient-serwer:
W takiej parze serwer czeka na zgłoszenie pewnego zapotrzebowania przez klienta (przykładem może być prośba o przesłanie zawartości skrzynki pocztowej). Po otrzymaniu zgłoszenia serwer wykonuje odpowiednią czynność i wraca do stanu oczekiwania.
Zwykle komunikacja pomiędzy serwerem i klientem odbywa się za pośrednictwem protokołu sieciowego TCP/IP z wykorzystaniem pojęcia gniazd (sockets). Pakiet java.net zawiera szereg klas służących do tworzenia i obsługi gniazd.
Gniazdo po stronie klienta tworzymy jako obiekt klasy Socket:
Socket gniazdo = new Socket ( < nazwaSerwera >,
< numerPortu > );
Po utworzeniu gniazda możemy przypisać mu strumienie wejścia/wyjścia:
DataInputStream in = new DataInputStream
( new BufferedInputStream
( gniazdo.getInputStream ( ) ) );
DataOutputStream in = new DataOutputStream
( new BufferedOutputStream
( gniazdo.getOutputStream ( ) ) );
Po zakończeniu transmisji wypada zamknąć połączenie metodą gniazdo.close ( ).
Gniazdo po stronie serwera otwieramy zleceniem
ServerSocket gniazdo = new ServerSocket
( < numerPortu > );
Zgłoszenie klienta akceptowane jest za pośrednictwem metody
accept ( ).
Para aplikacji Klient-Serwer ilustruje implementowanie takiej architektury w Javie.
Program Serwer wykonuje następujące czynności:
czeka na zgłoszenie klienta;
akceptuje zgłoszenie;
przesyła klientowi losowo wybrane pytanie;
czeka na odpowiedz;
sprawdza jej poprawność i przesyła odpowiedni komunikat do klienta;
pyta klienta, czy ma zadać następne pytanie;
czeka na odpowiedz;
jeżeli odpowiedź jest pozytywna, to wraca do kroku 3;
w przeciwnym razie wraca do kroku 1.
Pytania zadawane przez serwer i odpowiedzi uznawane za prawidłowe zapisane są w pomocniczym pliku Pytania.txt:
Co spowodowało powstanie kraterów na Księżycu?
meteoryty
W jakiej odległości od Ziemi jest księżyc (w milach)?
239000
W jakiej odległości od Ziemi jest Słońce(w milionach mil)?
93
Czy Ziemia jest idealną sferą?
nie
Jaka jest wewnętrzna temperatura Ziemi (w stopniach F)?
9000
Jakijest wiek Ziemi (w milionach lat)?
4600
Z jaką prędkością porusza się światło (w milach na sekundę)?
186300
Czy Słońce się porusza?
tak
Jaka jest nazwa świetlnej smugi, którą czasami widać na niebie?
Droga Mleczna
Czy Galaktyka się obraca?
tak
Przykład
import java.io.*;
import java.net.*;
import java.util.Random;
public class Serwer extends Thread {
private static final int PORTNUM = 1234;
private static final int CZEKAJ_NA_KLIENTA = 0;
private static final int CZEKAJ_NA_ODPOWIEDŹ = 1;
private static final int
CZEKAJ_NA_POTWIERDZENIE = 2;
private String [ ] pytania;
private String [ ] odpowiedzi;
private ServerSocket gniazdoSerwera;
private int liczbaPytań;
private int num = 0;
private int stan = CZEKAJ_NA_KLIENTA;
private Random rand = new
Random ( System.currentTimeMillis ( ) );
public Serwer ( ) {
super( "Serwer" );
try {
gniazdoSerwera = new ServerSocket ( PORTNUM );
System.out.println ( "Serwer uruchomiony ..." );
}
catch ( IOException e ) {
System.err.println ( "Nie mozna utworzyc gniazda" );
System.exit ( 1 );
}
}
public static void main ( String [ ] args ) {
Serwer serwer = new Serwer ( );
serwer.start ( );
}
public void run ( ) {
Socket gniazdoKlienta;
// Inicjalizacja tablic pytań i odpowiedzi
if ( !initPytania ( ) ) {
System.err.println ( "Nie udalo sie zainicjalizowac tablic
pytan i odpowiedzi");
return;
}
// Oczekiwanie na połączenie i zadanie pytania
while ( true ) {
// Czekaj an klienta
if ( gniazdoSerwera == null )
return;
try {
gniazdoKlienta = gniazdoSerwera.accept ( );
// Zadanie pytania i pobranie odpowiedzi
BufferedReader we = new BufferedReader ( new InputStreamReader ( gniazdoKlienta.getInputStream ( ) ) );
PrintStream wy = new PrintStream
( new BufferedOutputStream
( gniazdoKlienta.getOutputStream ( ) ), false );
String wierszWe, wierszWy;
// Przesłanie pytania
wierszWy = wczytaj ( null );
wy.println( wierszWy );
wy.flush ( );
// Przetworzenie odpowiedzi
while ( ( wierszWe = is.readLine ( ) ) != null ) {
wierszWy = wczytaj ( wierszWe );
wy.println ( wierszWy );
wy.flush ( );
if ( wierszWy.equals ( "koniec" ) )
break;
}
// Sprzątanie
wy.close ( );
we.close ( );
gniazdoKlienta.close ( );
}
catch (Exception e) {
System.err.println ( "Blad: " + e );
e.printStackTrace ( );
}
}
}
private boolean initPytania ( ) {
try {
File plikWe = new File ( "Pytania.txt" );
FileInputStream strumieńWe = new
FileInputStream ( plikWe );
byte [ ] dane = new byte [ ( int ) plikWe.length ( ) ];
// Wczytaj pytania i odpowiedzi do tablicy bajtów
if ( strumieńWe.read ( dane ) <= 0 ) {
System.err.println ( "Nie mozna odczytac pytan i
odpowiedzi");
return false;
}
// Zobacz ile par pytań i odpowiedzi przeczytano
for ( int i = 0; i < dane.length; i++ )
if ( dane [ i ] == ( byte ) '\n' )
liczbaPytań ++;
liczbaPytań /= 2;
pytania = new String [ liczbaPytań ];
odpowiedzi = new String [ liczbaPytań ];
// Załaduj pytania i odpowiedzi od tablic napisów
int start = 0, index = 0;
boolean jestP = true;
for ( int i = 0; i < dane.length; i++ )
if ( dane [ i ] == ( byte ) '\n' ) {
if ( jestP ) {
pytania [ index ] =
new String ( dane, 0, start, i - start - 1 );
jestP = false;
}
else {
odpowiedzi [ index ] =
new String ( dane, 0, start, i - start - 1 );
jestP = true;
index++;
}
start = i + 1;
}
}
catch ( FileNotFoundException e ) {
System.err.println ( "Nie mozna odnalezc pliku" );
return false;
}
catch ( IOException e ) {
System.err.println ( "Blad przy probie odczytu pliku" );
return false;
}
return true;
}
String wczytaj ( String inStr ) {
String outStr = "";
switch ( stan ) {
case CZEKAJ_NA_KLIENTA:
// Zadaj pytanie
outStr = pytania [ num ];
stan = CZEKAJ_NA_ODPOWIEDŹ;
break;
case CZEKAJ_NA_ODPOWIEDŹ:
// Sprawdź odpowiedź
if ( inStr.equalsIgnoreCase ( odpowiedzi [ num ] ) )
outStr = "Odpowiedz poprawna!
Nastepne pytanie? ( t/n )";
else
outStr = "Zle, poprawna odpowiedz to " +
odpowiedzi [ num ] +
". Nastepne pytanie? ( t/n )";
stan = CZEKAJ_NA_POTWIERDZENIE;
break;
case CZEKAJ_NA_POTWIERDZENIE:
// Czy użytkownik chce odpowiadać na następne pytanie?
if ( inStr.equalsIgnoreCase ( "t" ) ) {
num = Math.abs ( rand.nextInt ( ) ) % pytania.length;
outStr = pytania [ num ];
stan = CZEKAJ_NA_ODPOWIEDŹ;
}
else {
outStr = "koniec";
stan = CZEKAJ_NA_KLIENTA;
}
break;
}
return outStr;
}
}
Schemat działania programu Klient jest następujący:
nawiązanie połączenia z serwerem;
oczekiwanie na pytanie;
wyświetlenie pytania i pobranie odpowiedzi użytkownika;
przesłanie odpowiedzi do serwera;
oczekiwanie na odpowiedz serwera;
wyświetlenie tej odpowiedzi i spytanie użytkownika, czy oczekuje dalszych pytań;
odesłanie odpowiedzi użytkownika do serwera;
jeżeli odpowiedz była pozytywna, powrót do kroku 2;
w przeciwnym razie powrót do kroku 1.
Przykład
import java.io.*;
import java.net.*;
public class Klient {
private static final int PORTNUM = 1234;
public static void main ( String [ ] args ) {
Socket gniazdo;
BufferedReader we;
PrintStream wy;
String serwer;
// Sprawdź argumenty wywołania by określić nazwę serwera
if ( args.length != 1 ) {
System.out.println ( "Wywołanie:
java Klient < nazwa serwera > " );
return;
}
else
serwer = args [ 0 ];
// Inicjalizuj gniazda i strumienie
try {
gniazdo = new Socket ( serwer, PORTNUM );
we = new BufferedReader (new InputStreamReader
( gniazdo.getInputStream ( ) ) );
wy = new PrintStream ( gniazdo.getOutputStream ( ) );
// Obsługa danych wprowadzanych przez użytkownika
StringBuffer str = new StringBuffer ( 128 );
String inStr;
int c;
while ( ( inStr = we.readLine ( ) ) != null ) {
System.out.println ( "Serwer: " + inStr );
if ( inStr.equals ( "koniec" ) )
break;
while ( ( c = System.in.read ( ) ) != '\n' )
str.append ( ( char ) c );
System.out.println ( "Klient: " + str );
wy.println( str.toString ( ) );
wy.flush ( );
str.setLength ( 0 );
}
// Zamykanie strumieni i gniazd
wy.close ( );
we.close ( );
gniazdo.close ( );
}
catch ( IOException e ) {
System.err.println ( "Błąd tworzenia gniazda lub błąd
we-wy" );
}
}
}
Adam Borkowski Język programowania „Java” 12−20
Dysk
serwera
Serwer
sieciowy
Internet
Dysk
lokalny
Komputer
zgłoszenie
Klient
Serwer
usługa