Graafilise liidesega võrgurakendused Võrguprotokoll, lõimed, klient, server, kuularid, dokumenteerimine, vektorgraafika Trips-traps-trull Kirjeldus Võrguprogrammi näiteks on siin kokku pandud Trips-traps-trulli mäng. Serveripoole ülesanne on kaks mängijat kohale oodata, siis kordamööda teineteisele käiguõigus anda ning saadetud käsud edasi saata. Samuti on serveripoole otsustada, kumba mängijat tähistatakse nulli ja kumba ristiga. Otsus on tehtud lihtne: esimene saabunud mängija saab märgiks X, teine 0. Server ise on koostatud nii, et kui üks paar on omaette lõime abil mängima saadetud, siis asub serveri peaprogramm ise järgmist kasutajat ootama ning paari kokku saamisel paneb need taas omavahel mängima. Edasine klientide vaheline vestlus on juba nende otsustada. Server seda otseselt ei määra ega kontrolli. Vaid kirjutamise järg antakse mängijatele kordamööda. Kui keegi mängijatest saadab lõputunnuse, siis lõpetatakse neid mängijaid teenindava lõime töö. Kliendi pool ühendab end serveriga ning asub sealt tulevaid teateid ootama. Kui öeldakse, millise sümboliga klient mängib, siis jäetakse see meelde. Kui teatatakse toimunud käigust, siis joonistatakse selle tulemus nuppudest moodustatud mängulauale. Kui oli tegemist mängu alguse teatega või vastase käiguga, siis muudetakse lubatud käigunupud aktiivseks, et kasutaja saaks sobiva valida. Vajutuse peale muudetakse nupud taas külmunuks, saadetakse nupule vastav käik serverisse ning edasi tegutsetakse vastavalt saabuvatele teadetele. Lõpetusnupule vajutades saadetakse lõpetusteade, mille peale server teab selle edasi saata mängupartnerile ning samas ka nende mängijatega suhtleva lõime sulgeda. Saadetavad käsud näevad välja järgnevad: Kuju Tähendus .margiksX Teate saanud klient teab edaspidi, et tema mängib ristidega. Kui olnuks .margiks0, tuleks nullidega mängida .kaikX5 Mängija X käis nupu 5. .kaik03 tähendanuks, et mängija 0 vajutas nuppu 3. .kaikVaba Teate saanud klient võib oma käiguga alustada. .ots Mäng on lõppenud Tutvustavad pildid Järgnevalt on mõned pildid ühest konkreetsest mängust. Masinas on käivitatud serverprogramm, üks klient käsurealt ning teine veebiseilurist. Serverisse on jõudnud mõlemad kliendid ühenduda (sellest teatavad serveri ekraanil sõnad Esimene ja Teine). Klient on serverist teateid kuulava lõime tööle saanud. Seiluriaknas olev klient jõudis ühenduda esimesena. Temale tuli kõigepealt teade .margiksX Oota paarilist, mille peale inimene teab, et tal tuleb partnerit oodata. Partneri saabumisel antakse sellele teada .margiks0 ning seejärel esimesele ühendunule .kaikVaba. Selle peale vabastatakse esimese kliendi nupud lukust ning tal on vaba voli käia. Mängijal on vaba voli valida üheksa mängunupu ning Stopp-nupu vahel. Nagu serveri konsoolilt näha, käis X-iga mängija kõigepealt nupule nr. 0 ning seejärel 0-ga mängija nupule 1. D:\Kasutajad\jaagup\java>java Tripsuserver Esimene Teine .kaikX0 .kaik01 Sama tulemus paistab ka mängulaudu vaadates. Saabuvad teated on lihtsalt kontrolli mõttes ülal asuvasse tekstivälja paigutatud. Nupud on aga samuti õigesti märgitud. Nii võib mäng jätkuda kuniks mängijad seda soovivad. Praeguse näite puhul veel võite ei loeta ning tulemusi ei kontrollita. Kes leiab, et ta enam midagi arukat käia ei taha ega oska, võib vajutada stopp. Selle peale katkestatakse serveris selle mängu lõim ning ka kaaslasele saadetakse teade lõpetamisest. Nupule ilmub kiri Start ning kui sellele vajutada, algab mängijate kohtumine otsast peale. Serveripoole lähtekood seletustega import java.io.*; import java.net.*; Võrguprogrammide puhul alati vajalikud paketid public class Tripsuserver{ Programmi töö käigus vajalikud konstandid. Kui nende väärtused ühte kohta kirjutada, siis on vajadusel nende väärtusi kergesti võimalik muuta. Näiteks kui juhtub vastava värati peal juba mõni programm jooksma, siis tuleb siia mõni vaba number määrata. Kui tegemist on käsurealt käivitatava kliendiga ning server jookseb kohalikust masinast väljaspool, siis tuleb vastava serveri nimi siia kirjutada, et klient teaks sinna ühenduda. Rakendi puhul pole siia märgitud masina nimi tähtis, sest rakend saab alati ühenduda vaid serverisse kust ta ise pärit ning selle aadress küsitakse töö käigus. static final int pordinr=3001; static final String masin="localhost"; static final String lopp=".ots"; Serveri main-meetod ei tegele lihtsuse mõttes eriolukordade töötlemisega vaid laseb veateated lihtsalt konsoolile trükkida. Selleks on kiri throws Exception meetodi päises. public static void main(String argumendid[]) throws Exception{ Asutakse väratit kuulama ServerSocket ss=new ServerSocket(Tripsuserver.pordinr); Jätkatakse igaveses tsüklis, st. senikaua kuni serverirakendus CTRL+C-ga kinni pannakse. Viisakam, kuid keerulisem võimalus oleks luua eraldi protokoll serveri juhtimiseks ning selle abil saata serverile teateid sulgemise või muude toimingute kohta. while(true){ Oodatakse esimese kliendi saabumist ning teatatakse talle, et tema märgiks on X. Serveri administraatorile antakse teada, et paarist esimene klient on saabunud. Socket sc1=ss.accept(); new PrintWriter(sc1.getOutputStream(), true).println(".margiksX "+ "Oota paarilist"); System.out.println("Esimene"); Sama lugu teise kliendiga. Socket sc2=ss.accept(); new PrintWriter(sc2.getOutputStream(), true).println(".margiks0"); System.out.println("Teine"); Luuakse Tripsuloim'e nimelisest klassist uus eksemplar ning antakse sellele kaasa juurdepääsuvõimalus mõlema kliendi ühendusele, et loodud isendil oleks võimalus hakata nende omavahelise suhtluse üle hoolt kandma. new Tripsuloim(sc1, sc2); } } Ning serveri peaklassil rohkem tööd polegi. } Tripsuloim'e ülesandeks on siis talle etteantud ühendustega suhtlema hakata ning hoolitseda, et nad omavahel saaksid mängu ilusti peetud. class Tripsuloim extends Thread{ Klassi sisse luuakse koht ühenduste andmete hoidmiseks. Lühiduse mõttes on tehtud Socket tüüpi massiiv. Sellisel juhul pole vaja kahte eraldi muutujat ning ühised operatsioonid (näiteks sulgemine lõpus) on võimalik tsükli abil läbi viia nii, et pole tarvilik kummagi pistiku jaoks eraldi käske välja kirjutada. Eeldatakse, et esimese mängija ühendus paigutatakse elemendiks sc[0] ning teise oma sc[1]. Socket[] sc=new Socket[2]; Konstruktor saab kahe pistiku andmed, talletab need kohalikku pistikühenduste massiivi ning start- meetodi käivitamise abil palub virtuaalmasinal käivitada run-nimeline meetod eraldi lõimena, mis peaks hoolt kahe saabunud ühenduse vahelise suhtlemise eest. Kuni siiani ootab veel klassis Tripsuserver olev peaprogramm iga täidetava käsu taga enne kui oma järjega edasi läheb. Kui aga konstruktor on läbitud, siis võib peaprogramm käsu new Tripsuloim(sc1, sc2); edukalt lõppenuks lugeda ning järgmise juurde asuda. Järgmiseks tuleb aga peaprogrammis hüpe tsükli algusse ning siis asutakse juba uut klienti ootama. Loodud Tripsuloim'e isend aga toimetab tasapisi run-meetodis edasi. public Tripsuloim(Socket usc1, Socket usc2){ sc[0]=usc1; sc[1]=usc2; start(); } Omaette lõimes töötav mängijapaarivahelist suhtlust korraldav meetod. public void run(){ Veapüünis, kuna üle kaetud run-meetodist pole võimalik erindeid throws käsuga välja lasta, samas aga mitmed võrguga seotud käsud nõuavad erindite töötlemist. Samuti on viisakas tekkivatele probleemidele reageerida. try{ Nii sisend- kui väljundvoogude tarbeks luuakse massiivid, et pärastpoole oleks nende voogude poole mugavam pöörduda. BufferedReader sisse[]=new BufferedReader[2]; PrintWriter valja[]=new PrintWriter[2]; for(int i=0; i<2; i++){ sisse[i]=new BufferedReader( new InputStreamReader(sc[i].getInputStream()) ); valja[i]=new PrintWriter(sc[i].getOutputStream(), true); } Esimesele kliendile teatatakse, et too võib oma käiguga alustada. valja[0].println(".kaikVaba"); Muutuja veel näitab, kas vastav lõim peab oma tööd jätkama. Kui klient saadab lõpetamisteate, siis muutuja väärtus läheb vääraks ning enam uut ringi teateid kuulama ei minda. boolean veel=true; while(veel){ Esimeselt kliendilt saabunud käik või lõpetamisteade saadetakse mõlemale edasi. Klient on koostatud nii, et korrektseks toimimiseks peab ta ka ise serverilt oma saadetud teated kätte saama. See on justkui kontroll, et ühendus serveriga töötab. String vastus=sisse[0].readLine(); kirjuta(valja, vastus); Kui esimeselt kliendilt saadi lõpetamisteade, siis teise kliendi teadet enam ei oodata. if(!vastus.equals(Tripsuserver.lopp)){ Muul juhul kirjutatakse ka teiselt saabunud teade mõlemale. vastus=sisse[1].readLine(); kirjuta(valja, vastus); } if(vastus.equals(Tripsuserver.lopp))veel=false; } Jõudnud tsüklist välja, suletakse ühendused mõlema kliendiga. for(int i=0; i<2; i++){ sc[i].close(); } }catch(Exception e){ System.out.println("Probleem:"+e); } } Abimeetod teate välja saatmiseks. Teade jõuab nii mõlemale kliendile kui serveri konsoolile. Piiritleja private meetodi sees teatab, et seda võib kasutada ainult sama klassi (Tripsuloim) isendi seest. private void kirjuta(PrintWriter[] pw, String teade){ for(int i=0; i0)serverinimi=argumendid[0]; Frame f=new Frame("Tripsuraam"); f.add(new Tripsupaneel(), BorderLayout.CENTER); f.setSize(300, 300); f.setVisible(true); } } Kujundus on Tripsupaneeli nimelisse klassi kokku pandud. Klass nimega Tripsuklient on vaid käivitamiseks. class Tripsupaneel extends Panel{ Kõik lehel nähtavad nupud on paigutatud ühisesse massiivi. Sellisena on neid kergem tsükli abil külmutada, sulatada või puhastada. Button nupp[]=new Button[10]; String omanimi; //X või 0, mille alt klient mängib TextArea suhtlus=new TextArea("",3, 60,TextArea.SCROLLBARS_VERTICAL_ONLY); public Tripsupaneel(){ setLayout(new BorderLayout()); add(suhtlus, BorderLayout.NORTH); Nupud omaette paneeli, kus 3*3 elementi. Panel nupupaneel=new Panel(); nupupaneel.setLayout(new GridLayout(3, 3)); Tsükli abil luuakse üheksa mängunuppu, igaüks neist lisatakse nii paneeli näitamiseks kui massiivi pärastiseks kergemaks ligipääsuks. for(int i=0; i<9; i++){ nupp[i]=new Button(" "); nupupaneel.add(nupp[i]); } add(nupupaneel, BorderLayout.CENTER); Massiivi viimane nupp mängu peatamiseks. Kui nupupaneel paigutati Tripsupaneeli keskele (kogu vabale alale), siis seiskamis/käivitusnupp pannakse Tripsupaneeli allserva, nupupaneeli alla. nupp[9]=new Button("Stopp"); add(nupp[9], BorderLayout.SOUTH); Kõik nupud külmutatakse. seaNupud(false); Käivitatakse serveri teadete kuulamiseks ning nende töötlemiseks omaette lõim. Lõimele antakse ette osuti loodud Tripsupaneelile, mille kaudu on omakorda võimalik ligi pääseda seal paiknevatele nuppudele ning tekstialale. TripsukliendiLoim vahendaja=new TripsukliendiLoim(this); } Alamprogramm kliendi nuppude külmutamiseks ning lahti sulatamiseks. Külmutamise korral külmutatakse kõik nupud. See toimub juhul, kui käigujärg läheb vastasele ning sel ajal pole server võimeline ühtki teadet vastu võtma. Lahti sulatamisel aga tehakse toimivaks vaid nupud, millel pole mingit kirja ehk mängunuppude puhul need, mis on veel vabad. void seaNupud(boolean kasutatav){ if(!kasutatav)for(int i=0; i0)serverinimi=argumendid[0]; Frame f=new Frame("Tripsuraam"); f.add(new Tripsupaneel(), BorderLayout.CENTER); f.setSize(300, 300); f.setVisible(true); } } class Tripsupaneel extends Panel{ Button nupp[]=new Button[10]; String omanimi; TextArea suhtlus=new TextArea("",3, 60,TextArea.SCROLLBARS_VERTICAL_ONLY); public Tripsupaneel(){ setLayout(new BorderLayout()); add(suhtlus, BorderLayout.NORTH); Panel nupupaneel=new Panel(); nupupaneel.setLayout(new GridLayout(3, 3)); for(int i=0; i<9; i++){ nupp[i]=new Button(" "); nupupaneel.add(nupp[i]); } add(nupupaneel, BorderLayout.CENTER); nupp[9]=new Button("Stopp"); add(nupp[9], BorderLayout.SOUTH); seaNupud(false); TripsukliendiLoim vahendaja=new TripsukliendiLoim(this); } void seaNupud(boolean kasutatav){ if(!kasutatav)for(int i=0; ijava ParooligaJututuba .pr 94 62 33 21 .pr 113 48 8 14 .pr 112 70 12 13 .pr 99 70 7 7 Joonistasin sulle maja pildi Väljund serveriaknas Kolmas arendusring. Järgnevalt on tahvli koodi täiendatud javadoc-ile sobivate kommentaaridega. Nii õnnestub ülevaatlikkuse tarbeks genereerida koodi kohta mõningane dokumentatsioon ilma selle jaoks eraldiseisvat juttu kirjutamata. Joonistussündmustega ümber käimiseks on lihtne JooniseKuular-liidese tüüpi muutuja asemele paigutatud nimistu jooniseKuularid, mis võib eneses hoida kuulajaid nõnda palju kui parajasti tarvilik on. LinkedList jooniseKuularid=new LinkedList(); Et kannataks tahvlile väljapoolt kuulajaid külge panna, selleks alljärgnev meetod. Töötab teine samal põhimõttel, kui näiteks nupu või tekstivälja puhul addActionListener. Ehk sündmuse allikaks olev objekt jätab enese juurde meelde, kuhu tuleb sündmuse toimumise puhul teated välja saata. public void lisaJooniseKuular(JooniseKuular j){ jooniseKuularid.add(j); } Saatmise puhul tuleb hoolitseda, et teade kõikide sooviavaldanuteni jõuaks. Kui ennist oli valida kas nulli või ühe kuulaja vahel, siis nüüd alampiiriks endiselt null, ülempiiri pole aga seatud. Tõsi küll, mõni klass võib liiga suure kuulajaks registreerunute arvu puhul anda TooManyListenersException'i. Nagu ikka, on korduvate tegevuste puhul abiks tsükkel. Nimistust soovitud järjekorranumbriga elemendi aitab kätte saada get. Et oleme nimistusse paigutanud vaid JooniseKuulareid, siis võime ka kindlad olla, et sama tüüpi elemente sealt kätte saame. void saada(){ if(jooniseKuularid.size()==0){return;} int laius=hyx-hax; int korgus=hyy-hay; for(int i=0; i