Exceptions, I/O Streams, Sockets og Bank Terminal

Disse sidene er en del av dokumentasjonen som Ola Lie utarbeidet da han foreleste faget IP- og webteknologi ved Høgskolen i Østfold i 2005.Websidene ble revidert i 2007.

  • Exceptions (til unntakshåndtering)
  • I/O Streams (til å lese fra og skrive til eksempelvis filer eller sockets)
  • Sockets (til kommunikasjon mellom programmer)
  • Bank Terminal (eksempel på applikasjon som benytter socket forbindelse

Exceptions

Dersom vi skriver inn bokstaver i programmet Dialogboks.java (se Input/Output), når vi blir bedt om fødselsår, kræsjer programmet med en feilmelding lignende:

Exception in thread "main" java.lang.NumberFormatException: For input string: "ola"
at java.lang.NumberFormatException.forInputString(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at Dialogboks.main(Dialogboks.java:11)

Det er Integer.parseInt metoden som genererer et avbrudd når den prøver å konvertere en tekststreng som innholder bokstaver (i stedet for bare tall) til et heltall. I Java kan vi fange opp slike unntak med bruk av try og catch:

while (true) {
    try {
        YearBorn = Integer.parseInt(JOptionPane.
        showInputDialog("I hvilket år ble du født?"));
        break;
    }
    catch (NumberFormatException e) {
        JOptionPane.showMessageDialog(null,
        "Fødselsår må være et heltall.\nPrøv igjen!");
    }
}

Det blir ryddig programkode når vi skriver den normale programflyten i try blokken, og fanger opp og håndterer mulige unntak i én eller flere i påfølgende catch blokker. Kompilatoren sjekker mange av unntakene som kastes av Java API, men ikke alle, som for eksempel runtime exception ”Divisjon med 0”.

Se også Lesson: Exceptions (The Java Tutorials)

I/O Streams

Når vi skal lese noe fra en fil, må vi åpne en stream (strøm), som er en kilde for input. Vi benytter klassebiblioteket java.io og oppretter et objekt av typen FileReader. FileReader objektet inneholder metoden read() som kan lese tegn for tegn. Men det er ofte lettere å ”pakke inn” FileReader objektet i et BufferedReader objekt som inneholder metoden readLine(). Eksempelet nedenfor er hentet fra BankTerminal applikasjonen i dette notatet.

int saldo;
BufferedReader innFil = new BufferedReader(new FileReader("saldo"));
saldo = Integer.parseInt(innFil.readLine());
innFil.close();

Skriving foregår tilsvarende. Finnes ikke fila, blir den opprettet:

BufferedWriter utFil = new BufferedWriter(new FileWriter("saldo"));
utFil.write(Integer.toString(saldo));
utFil.close();

I eksempelet ovenfor er ”saldo” både en heltalsvariabel og navnet på fila. Dersom vi ønsker å legge informasjon til på slutten av filen (append) i stedet for å overskrive fila benytter vi:

new FileWriter("saldo",true)

En enkel måte å skrive til en socket (kontakt mellom to programmer) er å pakke inn output strømmen i et PrintWriter objekt som inneholder metoden println():

PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);

Når vi skal lese fra en socket, blir det ganske likt som å lese fra en fil, men vi oppretter en InputStreamReader i stedet for en FileReader. Deretter pakkes objektet inn i en BufferedReader, og vi kan fortsette som før:

BufferedReader in = null;
in = new BufferedReader(new InputStreamReader(bSocket.getInputStream()));

Se også Lesson: Basic I/O (The Java Tutorials)

Sockets

Socket er ett endepunkt på en toveis kommunikasjonsforbindelse mellom to programmer og består av en IP adresse og et portnummer, som identifiserer tjenesten. Først oppretter vi en socket på tjener siden, som kobles til en bestemt port, for eksempel 4444:

ServerSocket serverSocket = null;
serverSocket = new ServerSocket(4444);

Deretter settes tjeneren til å vente på en forespørsel om en forbindelse fra en klient:

Socket clientSocket = null;
clientSocket = serverSocket.accept();

Når forbindelsen er etablert, returnerer accept() metoden et nytt Socket objekt som bindes til en annen port. Ved hjelp av tråder (threads) kan vi la tjeneren kommunisere med klienten via denne nye Socket’en samtidig som den fortsetter å lytte etter klient forespørsler på den opprinnelige porten 4444 (ServerSocket). BankTerminal eksempelet benytter ikke tråder.

Tjener programmet må startes før klient programmet. Klienten oppretter en forbindelse med tjeneren ved å opprette en Socket som krever at både hostname/IP adresse og portnummer oppgis.

Socket bSocket = null;
bSocket = new Socket("192.168.1.10", 4444);

Lesing til og skriving fra Socket objektene foregår som beskrevet ovenfor i avsnittet om I/O Streams.

Se også Lesson: All About Sockets (The Java Tutorials)

BankTerminal

minibankDenne applikasjonen er et eksempel på en klient - tjener modell som benytter seg av Socket programmering. Eksempelet består av fire java filer. To på tjeneren og to på klienten. Alle filene kompileres hver for seg, men de to tjener filene (BankServer.class og BankProtocolTINI.class) konverteres til én .tini fil (BankServer.tini) når vi kjører build.xml. BankProtocolTINI.class filen på tjeneren er ulik BankProtocolPC.class filen på klienten som benytter klassebiblioteket javax.swing til et grafisk brukergrensesnitt (GUI). Det er ikke mulig på TINI. Men det er andre forskjeller også.

Både BankTerminal og BankServer leser informasjon fra Socket’en, sender det videre til BankProtocolPC/TINI metoden processInput() som returnerer det som skal sendes tilbake.

BankTerminal kan sende fem forskjellige ”kommandoer” til BankServer:

1 = Vis saldo
2 = Forespørsel om kr 100
3 = Forespørsel om kr 200
4 = Forespørsel om kr 500
5 = Avslutt

BankServer kan sende fem forskjellige ”svar” til BankTerminal:

Et heltall som viser saldo.
2 = OK med kr 100
3 = OK med kr 200
4 = OK med kr 500
5 = Ikke dekning på konto.

Dersom det står 3 kroner på konto, hvordan kan klienten vite at det er saldo og ikke beskjed om det er trukket kr 200 på kontoen? Jo, klienten befinner seg i tre ulike tilstander (states): HOVEDMENY, SALDO og UTTAK. Har klienten bedt om saldo, står den i tilstand SALDO. Etter at saldoen er vist på terminalen, settes tilstanden tilbake til HOVEDMENY og metoden hovedMeny() kalles. Programmet begynner med at BankTerminal kaller processInput med parameteren 0. Parameteren har ingen betydning i det første kallet fordi tilstanden initieres til HOVEDMENY. processInput() returnerer valget som gjøres i metoden hovedMeny().

På tjener siden er logikken enklere. BankServer lar oss legge inn saldo fra kommandolinja når vi starter programmet på TINI, for eksempel java BankServer.tini 5000 &. Dersom ingen saldo oppgis, brukes den sist lagrede saldo. Dersom BankProtocolTINI kalles med parameteren 1, returneres saldoen som ligger lagret i fila saldo. Hvis det er forespørsel om uttak, sjekker vi først om det er dekning på konto. Er alt i orden, reduseres saldoen, den nye saldoen skrives til saldo fila og en bekreftelse sendes tilbake (2, 3 eller 4). Hvis det ikke er nok penger på kontoen, returneres verdien 5 som tekststreng.

Den vedlagte build.xml filen automatiserer enda flere oppgaver enn i tidligere eksempler:

  1. i init opprettes mappene \bin (for .class og .tini filer) og \lib (for .jar filier)
  2. i compile kompileres .java filene til .class filer
  3. i convert konverteres .class filene i pakken no.tip.minibank.tini til én .tini fil (BankServer.tini).
  4. i jar pakkes .class filene i pakken no.tip.minibank.pc inn i én kjørbar .jar fil (BankTerminal.jar)
  5. i deploy overføres BankServer.tini til TINI og BankTerminal.jar til mappen utrulling (c:\tip\utrulling)
  6. i clean slettes mappene \bin og \lib
  7. i run brukes telnet til å starte BankServer.tini på TINI'en og java til å starte BankTerminal på PC'en.

Programmet skal dokumenteres med UML og koden forbedres....