Aplet DwaZegary ilustruje niektóre możliwości Javy takie, jak: wielowątkowość, współpraca z siecią Internet oraz z przeglądarką, w której wyświetlana jest strona WWW zawierająca aplet. Wykorzystano także pojęcie strumieni oraz klasy i komponenty AWT, do których należą: panele, przyciski, zdarzenia, zarządcy widoków.
W aplecie tym łączymy się z serwerem, z którego została załadowana strona go zawierająca. Z tego serwera odczytujemy datę i czas (usługa 'dtime'), następnie odczytujemy datę i czas na naszym komputerze. Te informacje są wykorzystane przy tworzeniu dwu wątków, odpowiadających za: rysowanie odpowiednio: zegara wyświetlającego czas odczytany z serwera i zegara wyświetlającego czas lokalny. Dodatkowo u dołu okna znajduje się przycisk, po naciśnięciu którego mamy możliwość wysłania poczty email, dzięki wykorzystaniu właściwości przeglądarki. Gdy w użytej tu metodzie showDocument (opis, patrz: Przykład 2.44):
showDocument(new URL("mailto:arturt@friko.onet.pl"),"_self");
zmienimy pierwszy parametr na adres strony WWW, naciśnięcie przycisku spowoduje jej wyświetlenie w przeglądarce.
Apletu w trakcie działania przedstawia poniższa ilustracja:
Ilustracja 4-1Wygląd apletu DwaZegary
Aplet korzysta z usługi 'dtime', usługa ta umożliwia sprawdzenie aktualnego czasu i daty na komputerze, z którym sie połączyliśmy.
Dokładny opis usługi można znaleźć w dokumencie RFC867. Ogólnie, korzysta ona z portu nr 13. Komunikacja polega na nawiązaniu połączenia z serwerem i przesłaniu przez niego informacji. Potem następuje przerwanie połączenia.
Tekst apletu zamieszczono poniżej:
import java.util.Date;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
public class DwaZegary extends java.applet.Applet implements Runnable
{
// program napisano tak, aby łatwo można było zwiększyć liczbę
// rysowanych zegarów
static int m_nLiczbaZegarow = 2;
Date daty[];
WatekZegara zegary[];
Thread watki[];
Thread watekGlowny;
int m_nLiczbaWatkowNaStarcie;
//adresy, źródła daty i czasu
public String zrodloDanych[] = {"friko.onet.pl", "czas lokalny"};
public void init()
{
// użyjemy tej zmiennej później aby sprawdzić czy wszystkie
// wątki zostały zabite
m_nLiczbaWatkowNaStarcie = Thread.activeCount();
// ustawienie zarządcy rozmieszczenia; zegary będą dodawane
// na prawo od ostatniego dodanego
setLayout(new BorderLayout());
// utworzenie panelu zawierającego zegary
Panel panel = zegaryGridLayoutPanel();
// utworzenie tablic zawierających:
// 1 wątki rysujące zegary,
zegary = new WatekZegara[m_nLiczbaZegarow];
// 2 wątki kontrolujące wykonanie wątków typu WatekZegara,
watki = new Thread[m_nLiczbaZegarow];
// 3 informację o czasie i dacie
daty = new Date[m_nLiczbaZegarow];
// w tym bloku, czytane są dane z serwerów (oraz czas lokalny)
for (int numerSerwera = 0; numerSerwera < m_nLiczbaZegarow; numerSerwera++)
{
if(zrodloDanych[numerSerwera].compareTo("czas lokalny") != 0)
{
try
{
Socket gniazdko = new Socket(zrodloDanych[numerSerwera] ,13 );
DataInputStream dis = new DataInputStream(gniazdko.getInputStream());
daty[numerSerwera]= new Date(dis.readLine());
gniazdko.close();
}
catch(IOException ioe)
{
zrodloDanych[numerSerwera] =
"blad przy polaczeniu z "+zrodloDanych[numerSerwera];
daty[numerSerwera] = new Date(97,01,02,11,59,59);
}
catch (Exception e)
{
zrodloDanych[numerSerwera] =
"blad dla "+zrodloDanych[numerSerwera];
daty[numerSerwera] = new Date(97,01,02,11,59,59);
}
finally
{
// inicjalizacja wątków rysujących zegary i dodanie
// zegara do panelu
inicjujZegar(numerSerwera, panel);
}
}
else
{ // inicjalizacja pozostałych zegarów
daty[numerSerwera] = new Date();
inicjujZegar(numerSerwera, panel);
}
}
add("North",panel);
Button wyslanieWiadomosci =
new Button("Autor: Artur Tyloch (arturt@friko.onet.pl)");
add("Center",wyslanieWiadomosci);
wyslanieWiadomosci.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt)
{
try
{
getAppletContext().showDocument(
new URL("mailto:arturt@friko.onet.pl"),"_self");
}
catch(Exception e){}
}
}
);
}
Panel zegaryGridLayoutPanel()
{
Panel tmpPanel = new Panel();
tmpPanel.setLayout(new GridLayout(1,m_nLiczbaZegarow));
return tmpPanel;
}
// w metodzie tej tworzymy nowy wątek dla każdego zegara
// i dodajemy go do panelu
void inicjujZegar(int x, Panel zegarPanel)
{
zegary[x]=new WatekZegara(daty[x], true, zrodloDanych[x]);
zegary[x].resize(size().width,(int)(size().height/1.2));
zegarPanel.add(zegary[x]);
watki[x]=new Thread(zegary[x]);
}
public void start()
{
// uruchomienie wszystkich wątków zegarów
for (int x = 0; x < m_nLiczbaZegarow; x++)
watki[x].start();
// utworzenie wątku głównego, używanego do monitorowania
// stanu zegarów
watekGlowny= new Thread (this);
watekGlowny.start();
}
public void stop()
{
watekGlowny.stop();
}
public void run()
{
// wykonanie pętli do czasu aż wszystkie zegary zakończą
// swoje działanie
while(Thread.activeCount() > m_nLiczbaWatkowNaStarcie+2)
{
try
{
watekGlowny.sleep(10);
repaint();
}
catch (InterruptedException e)
{
System.out.println("watekGlowny was interrupted");
}
}
stop();
destroy();
}
}
W klasie WatekZegara zdefiniowano metody odpowiedzialne za rysowanie pojedynczego zegara:
import java.util.*;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Color;
public class WatekZegara extends java.awt.Canvas implements Runnable
{
// współrzedne rysowanych wskazówek
private int m_nOstatnieXS=0, m_nOstatnieYS=0, m_nOstatnieXM=0, m_nOstatnieYM=0, m_nOstatnieXH=0, m_nOstatnieYH=0;
// ostatnio wyświetlany czas i data
private String m_OstatniaData = null;
// różnica pomiędzy czasem lokalnym a czasem na serwerze
private long m_lRoznica = 0;
// pole danych określające czy wyświetlamy czas lokalny,
// czy czas serwera
private boolean m_bZdalne;
private String m_informacja;
WatekZegara(Date d, boolean Zdalne, String info)
{
Date localD = new Date();
if ( localD != d)
m_lRoznica = localD.getTime() - d.getTime();
m_OstatniaData = d.toLocaleString();
m_bZdalne = Zdalne;
m_informacja = info;
m_nOstatnieXS=70;
m_nOstatnieYS=m_nOstatnieXS;
m_nOstatnieXM=m_nOstatnieXS;
m_nOstatnieYM=m_nOstatnieXS;
m_nOstatnieXH=m_nOstatnieXS;
m_nOstatnieYH=m_nOstatnieXS;
}
public void run()
{
while (true)
{
repaint();
// uśpienie wątku, aby umożliwić przerysowania apletu
try
{
Thread.currentThread().sleep(1000);
}
catch (InterruptedException e){ }
}
}
public void update(Graphics g)
{
// nie ma efektu mrugania (domniemana metoda update
// czyści pulpit kolorem tła, a następnie rysuje aplet),
// tu w metodzie paint stare wskazówki są zamazywane i
// nie ma kłopotu z mruganiem rysowanego obrazu zegarów
paint(g);
}
public synchronized void paint(Graphics g)
{
Date dat = new Date();
if (m_bZdalne)
dat = new Date(dat.getTime()-m_lRoznica);
rysujZegar(dat.getSeconds(), dat.getMinutes(), dat.getHours(), size().width/2, 70, dat.toLocaleString(), g);
g.setFont(new Font("Helvetica", Font.BOLD, 12));
g.drawString("Czas:",20 , 165);
g.drawString(m_informacja,20 , 185);
g.setFont(new Font("Courier", Font.ITALIC, 10));
g.drawString("autor: Artur Tyloch",20 , 195);
g.draw3DRect(2,2,size().width - 4,size().height - 4,true);
g.drawRoundRect(5,5,size().width - 10,size().height - 10, 45, 50);
}
synchronized private void rysujZegar(int s, int m, int h,
int xcenter, int ycenter, String dzis, Graphics g)
{
int xh, yh, xm, ym, xs, ys;
xs = (int)(Math.cos(s * 3.14f/30 - 3.14f/2) * 45 + xcenter);
ys = (int)(Math.sin(s * 3.14f/30 - 3.14f/2) * 45 + ycenter);
xm = (int)(Math.cos(m * 3.14f/30 - 3.14f/2) * 40 + xcenter);
ym = (int)(Math.sin(m * 3.14f/30 - 3.14f/2) * 40 + ycenter);
xh = (int)(Math.cos((h*30 + m/2) * 3.14f/180 - 3.14f/2) * 30 + xcenter);
yh = (int)(Math.sin((h*30 + m/2) * 3.14f/180 - 3.14f/2) * 30 + ycenter);
// Rysowanie koła i liczb
g.setFont(new Font("Courier", Font.BOLD, 14));
g.setColor(Color.blue);
g.drawOval(xcenter - 50,ycenter -50 ,100,100);
g.setColor(Color.yellow);
g.fillOval(xcenter - 45,ycenter -45 ,90,90);
g.setColor(Color.darkGray);
g.drawString("9",xcenter-45,ycenter+3);
g.drawString("3",xcenter+40,ycenter+3);
g.drawString("12",xcenter-5,ycenter-37);
g.drawString("6",xcenter-3,ycenter+45);
// wymazanie, gdy zachodzi taka potrzeba i przerysowanie
g.setColor(Color.yellow/*getBackground()*/);
if (xs != m_nOstatnieXS || ys != m_nOstatnieYS)
{
g.drawLine(xcenter, ycenter, m_nOstatnieXS, m_nOstatnieYS);
g.setColor(getBackground());
g.drawString(m_OstatniaData, 20 , ycenter + 75);
g.setColor(Color.yellow/*getBackground()*/);
}
if (xm != m_nOstatnieXM || ym != m_nOstatnieYM)
{
g.drawLine(xcenter, ycenter-1, m_nOstatnieXM, m_nOstatnieYM);
g.drawLine(xcenter-1, ycenter, m_nOstatnieXM, m_nOstatnieYM);
}
if (xh != m_nOstatnieXH || yh != m_nOstatnieYH)
{
g.drawLine(xcenter, ycenter-1, m_nOstatnieXH, m_nOstatnieYH);
g.drawLine(xcenter-1, ycenter, m_nOstatnieXH, m_nOstatnieYH);
}
g.setColor(Color.darkGray);
g.drawString(dzis, 20, ycenter + 75);
g.drawLine(xcenter, ycenter, xs, ys);
g.setColor(Color.blue);
g.drawLine(xcenter, ycenter-1, xm, ym);
g.drawLine(xcenter-1, ycenter, xm, ym);
g.drawLine(xcenter, ycenter-1, xh, yh);
g.drawLine(xcenter-1, ycenter, xh, yh);
m_nOstatnieXS=xs; m_nOstatnieYS=ys;
m_nOstatnieXM=xm; m_nOstatnieYM=ym;
m_nOstatnieXH=xh; m_nOstatnieYH=yh;
m_OstatniaData = dzis;
}
}
W aplecie MailAplet pokazano wykorzystanie protokołu SMTP (ang. Simple Mail Transfer Protocol) do wysłania pocztę e-mail z apletu pod zadany adres.
Po wczytaniu przez przeglądarkę strony WWW zawierającej ten aplet, wysyłany jest list pod adres określony przez pole danych o nazwie adresat. W polu 'Subject' e-mail'a wysyłana jest informacja o adresie IP osoby przeglądającej stronę WWW. Do pobrania adresu IP użyto metody statycznej getLocalHostInetAddress.
Dokładny opis protokołu można znaleźć w dokumencie RFC821. Ogólnie, korzysta on z portu nr 25.
Opis komunikacji:
Klient: MAIL <SPACJA> FROM:<adres nadawcy> <CRLF>
Serwer: 250 <komunikat>
K: RCPT <SPACJA> TO:<adres odbiorcy> <CRLF>
S: 250 <komunikat>
K: DATA <CRLF>
S: 354 <komunikat2>
K: Jakas przykladowa tresc listu ....
K: ... itd. itd. itd.
K: <CRLF>.<CRLF>
S: 250 <komunikat>
W programie nie interesuje się tym, co wysyła serwer, bo zakładam, ze podaje prawdziwe dane. Poniżej przedstawiono kod apletu:
import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.*;
public class MailAplet extends Applet
{
String m_mojGosc = null;
String adresat = "arturt@priv.onet.pl";
public void init()
{
resize(320, 40);
try
{
System.out.println("Przygotowanie listu\n");
Socket socket = new Socket(getCodeBase().getHost(),25);
//odpowiedzi serwera można sprawdzić, gdy zadeklarujemy:
//DataInputStream dis =
// new DataInputStream(socket.getInputStream());
// i będziemy je interpretować
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeBytes("MAIL FROM:<zieloni@uop.warszawa.gov.pl>\n");
dos.writeBytes("RCPT TO:<"+adresat+">\n");
dos.writeBytes("DATA\r\n");
try
{
m_mojGosc=InetAddress.getLocalHost().toString();
}
catch (UnknownHostException e)
{
m_mojGosc=e.toString();
}
dos.writeBytes("Subject: GOSC: "+m_mojGosc+"\n\n");
dos.writeBytes("Dzis odwiedzil:\r\n");
dos.writeBytes(m_mojGosc+"\r\n");
dos.writeBytes(".\r\n.\n\n.\nQUIT");
dos.flush();
System.out.println("List zostal wyslany");
socket.close();
}
catch(IOException e)
{
System.out.println("BLAD !!!");
}
}
// wyświetlenie w oknie apletu informacji o wysłaniu e-mail'a
public void paint(Graphics g)
{
g.drawString("Czesc: "+m_mojGosc, 10, 20);
g.drawString("Mail z informacja o twojej wizycie zostal" + " wyslany do: "+adresat, 10, 30);
}
}
Zauważmy, ze można w ten sposób wysłać list jako "ktokolwiek". Wystarczy tylko w "MAIL FROM:<adres>" podać dowolny (nawet nieistniejący) adres. Tym sposobem można podszyć się pod każdego !
A oto, kilka nagłówków (pole Subject) listów jakie otrzymałem, po umieszczeniu apletu na stronie WWW "http://friko.onet.pl/po/arturt/":
GOSC: robert/195.116.127.24
GOSC: pcjura.comarch.pl/195.116.125.114
GOSC: fornax/150.254.25.38
GOSC: wks620.inform.pucp.edu.pe/200.16.7.180
GOSC: w014-3.ppcor.org.pl/190.59.76.23
GOSC: lala.waw.ids.edu.pl/195.117.3.20
GOSC: imul-043.uni.lodz.pl/193.59.60.56
GOSC: ds3pc94.pol.lublin.pl/194.92.19.94
GOSC: quake.tx.iex.com/192.153.191.251
GOSC: ppp.sylaba.poznan.pl/150.254.152.163
GOSC: Dolar.it.com.pl/195.116.134.101
GOSC: parkds.hscom.co.kr/150.30.50.32
Na podobnych zasadach można napisać w Javie każdą usługę sieciową.
Pisanie sieciowych aplikacji w Javie jest bardzo proste. Java dostarcza pakiet java.net, w którym są zdefiniowane wszystkie niezbędne klasy do obsługi komunikacji połączeniowej (TCP) i bezpołączeniowej (UDP), zarówno dla aplikacji klienckich jak i serwerowych.