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 getLocalHost klasy InetAddress.
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.