{ "title": "Det grundlæggende", "exercisesLink": "opgaver/opgaver.htm" }
  Når to applikationer, der afvikles på hver deres computer, er forbundet med hinanden via et netværk, er der overordnet to forskellige måder at anskue forbindelser, der kan oprettes mellem dem.
Stream eller ej Enten kan man se på det som en konstant opretholdt stream-agtig forbindelse; hvor de to applikationer sender data til hinanden i en ubrudt strøm. Alternativt kan man se forbindelsen som et postsystem; hvor igennem man kan sende stykker af data, der som enkelte postkort sendes ud på nettet og (forhåbentlig) når deres modtager.
Virtuel forbindelse Den første type kalder man: forbindelsesbaseret, og den sidste: forbindelsesløs. I denne sammenhæng skal man naturligvis forstå forbindelse som virtuel forbindelse, idet der (forhåbentlig) altid er tale om en eller anden form for fysisk forbindelse.
  Vi skal i det følgende se hvordan man i Java kan lave applikationer, der arbejder med disse typer af forbindelser.
 

 

1. Forbindelsesbaseret

Ikke vores ansvar Som nævnt, er idéen i den forbindelsesbaserede løsning, at der konstant opretholdes en virtuel forbindelse mellem de to applikationer. Det betyder, at vi i vores programmer arbejder med streams - vi læser fra dem og skriver til dem. Vores applikation har ikke noget ansvar for, at data når frem til den anden applikation, det påhviler den virtuelle maskine og det underliggende operativsystem.
Manglende eller langsom Den store fordel ved den forbindelsesbaserede løsning er, at vi kun i minimalt omfang skal forholde os til, at den anden applikation befinder sig på en anden computer. Vi mærker det i hovedsagen kun ved to ting: At forbindelsen kan være afbrudt og at kommunikationen kan være langsom. Det er to forhold, som vi skal tage i betragtning, hvis vores applikationer skal kunne blive robuste (dvs. kunne håndtere "enhver situation", der måtte opstå).
  Lad os se hvordan en simpel server kan implementeres:
Kildetekst 1:
Simpel server
            import java.net.*;
            import java.io.*;
            
            public class SimpleServer extends Thread {
            
              @Override
              public void run() {
                try {
                  ServerSocket server = new ServerSocket( 6010 );
                  System.out.println( "[Server] Online" );
                  
                  Socket connection = server.accept();
                
                  BufferedReader reader = new BufferedReader(
                                            new InputStreamReader(
                                              connection.getInputStream() ) );
                  
                  String line = "";
                  while ( !line.equals( "<end>" ) ) {
                    line = reader.readLine();
                    
                    System.out.println( "[Server] received line: " + line );
                  }
                  
                  reader.close();
            
                  System.out.println( "[Server] Offline" );
                }
                catch ( IOException e ) {
                  System.out.println( "[Server] I/O error" );
                }
              }
            }            
          
Lytte på port Som server skal man tilknyttes en port som man kan kontaktes på - vi vælger her port: 6010. Til det formål laver man en ServerSocket. Dernæst skal man lytte på porten - dvs. afvente en udefra kommende forbindelse fra en klient, der ønsker at kommunikere med serveren. Dette gøres ved at kalde accept-metoden. Denne metode blokkerer indtil en klient opretter en forbindelse til porten. Denne blokkering er ikke interruptable, men vi skal i kapitlet: "Flertrådede servere" se hvordan man kan undgå en længerevarende blokkering.
  Når en klient opretter en forbindelse til serveren returnerer accept en instans af Socket, der repræsenterer forbindelsen. Socket har bla. følgende metoder:
InputStream getInputStream()
OutputStream getOutputStream()
  Disse streams er byte-orienterede og vi vælger derfor at indkapsle dem i en BufferedReader, da vi i eksemplet vil arbejde med tekst.
  Bemærk, at close-kaldet på reader'en også lukker netværks-forbindelsen til klienten.
   
  En simpel klient kan implementeres som:
Kildetekst 2:
Simpel klient
            import java.net.*;
            import java.io.*;
            
            public class SimpleClient extends Thread {
              
              @Override
              public void run() {
                try {
                  Socket connection = new Socket( "127.0.0.1", 6010 );
                  System.out.println( "[Client] Connection opened" );
                
                  BufferedWriter writer = new BufferedWriter(
                                            new OutputStreamWriter(
                                              connection.getOutputStream() ) );
                  
                  sendLine( writer, "Hello" );
                  sendLine( writer, "I'm a client" );
                  sendLine( writer, "<end>" );
            
                  writer.close();
                  System.out.println( "[Client] Connection closed" );
                }
                catch ( IOException e ) {
                  System.out.println( "[Client] I/O error" );
                }
              }
              
              private void sendLine( BufferedWriter writer, String line )
                throws IOException
              {
                System.out.println( "[Client] sending line: " + line );
                
                writer.write( line );
                writer.newLine();
                writer.flush();
                
                Sleeper.nap();
              }
            }
          
Adresse En server kan stå og vente på, at klienter fra alle mulige steder, opretter en forbindelse til den. For klienten er det anderledes, den må videre hvilken server den ønsker at oprette en forbindelse til - den må kende dens adresse. Vi har her valgt IP: 127.0.0.1, der er localhost - dvs. maskinen selv, idet vi har tænkt os at lave en testanvendelse, hvor server og klient kører på samme maskine.
DNS Man bemærker, at vi her, i modsætning til i serveren, selv instantierer en Socket. I konstruktoren angiver vi adresse og port. Ovenfor har vi anvendt et IP-nummer, men konstruktoren kan også tage en DNS-adresse, f.eks.: solaris.ikasths.dk (er pt. et fiktivt navn).
  Ligesom for serverens vedkommende, vil kaldet af closewriter'en, også lukke forbindelsen til serveren
  Der er indsat pauser i både server og klient, for at data ikke blot bliver sendt i én pakke over nettet. I så fald ville man få et dårligere indtryk af hvad der sker rent netværksmæssigt, i forbindelse med følgende testanvendelse:
  Da både server og klient er implementeret som tråde, er det let at teste dem på samme maskine med følgende testanvendelse:
Kildetekst 3:
Testanvedelse
            public class Main {
  
              public static void main( String[] argv ) {
                new SimpleServer().start();
                Sleeper.nap(); // være sikker på server er online
            
                new SimpleClient().start();
              }
            }
          
            [Client] Connection opened
            [Server] Online
            [Client] sending line: Hello
            [Server] received line: Hello
            [Client] sending line: I'm a client
            [Server] received line: I'm a client
            [Client] sending line: <end>
            [Server] received line: <end>
            [Server] Offline
            [Client] Connection closed
          
  Tekststrengen: "<end>" bruges til at markere afslutningen på de linier som klienten sender til serveren, og man bemærker, at serveren går offline efter at have modtaget denne linie.
 

 

2. Forbindelsesløs

Ingen saks Som nævnt i starten af dette kapitel betyder forbindelsesløs ikke, at vi klipper netkablet over! Der er blot tale om, at vi ikke opretholder en virtuel forbindelse mellem to applikationer, men i stedet sender enkelte pakker af data fra den ene til den anden, efterhånden som det er påkrævet.
  At vi arbejder med enkelte pakker betyder samtidig, at vi ikke længere arbejder med streams, og den typemæssige bekvemmelighed der ligger i Java's stream-klasser. Vi skal ned og arbejde med bytes!
UDP Lad os se hvordan en server svarende til den vi så overnfor, laves forbindelsesløst, eller med UDP (User Datagram Protocol), som er den protokol der anvendes i Java:
Kildetekst 4:
Simpel server
            import java.net.*;
            import java.io.*;
            
            public class SimpleServer extends Thread {
            
              @Override
              public void run() {
                try {
                  byte[] buffer = new byte[100];
                  
                  DatagramSocket server = new DatagramSocket( 6010 );
                  System.out.println( "[Server] Online" );
                  
                  DatagramPacket receivePacket =
                    new DatagramPacket( buffer, buffer.length );
            
                  String line="";
                  while ( !line.equals( "<end>" ) ) {
                    server.receive( receivePacket );
                    
                    System.out.println( "[Server] received " +
                                        receivePacket.getLength() +
                                        " bytes" );
                    
                    line = getLine( receivePacket );
                  }
                  
                  System.out.println( "[Server] Offline" );
                }
                catch ( IOException e ) {
                  System.out.println( "[Server] I/O error" );
                }
              }
              
              private String getLine( DatagramPacket packet ) {
                String s = new String( packet.getData(),
                                       0, packet.getLength() );
                
                System.out.println( "[Server] received line: " + s );
                return s;
              }
            }
          
  På samme måde som den forbindelsesbaserede serveren ovenfor knyttede sig til en bestemt port, gør den forbindelsesløse det også. Her sker det ved, at man laver en instans af DatagramSocket. Svarende til accept-kaldet, kalder man her receive-metoden, der ligeledes blokkerer indtil der modtages en pakke.
  Det nye er, at vi skal lave en pakke - en instans af DatagramPacket. Til denne instans skal vi allokere et byte-array, der skal kunne indholde de data vi modtager. receive-kaldet vil returnere den modtagne pakkes data-indhold i dette array, men er der ikke plads, vil vi kun få den forreste del - rester vil kræve gentagne kald af receive.
  Når vi har modtaget en pakke, kan vi anvende en af flere nyttige metoder i DatagramPacket-klassen. Vi har her anvendt metoderne:
int getLength()
byte[] getData()
  Man kunne måske få den tanke, at anvende length på det array, der returneres af getData, men det ville blot give os længden af array'et - ikke hvor meget af det, der indeholder data fra pakken!
Low-level Da vi i dette eksempel udelukkende arbejder med tekststrenge er arbejdet med at afkode de modtagne data meget enkelt, idet String-klassen har en konstruktor, der netop passer til formålet. Man må dog normalt regne med, at UDP kræver en del low-level kode, når man skal håndtere pakkers indhold.
   
  Den tilhørende klient har følgende implementation:
Kildetekst 5:
Simpel klient
            import java.net.*;
            import java.io.*;
            
            public class SimpleClient extends Thread {
              private DatagramSocket socket;
              
              @Override
              public void run() {
                try {
                  socket = new DatagramSocket();
                  
                  System.out.println( "[Client] started" );
                  
                  sendLine( "Hello" );
                  sendLine( "I'm a client" );
                  sendLine( "<end>" );
                  
                  System.out.println( "[Client] ended" );
                }
                catch ( IOException e ) {
                  System.out.println( "[Client] I/O error" );
                }
              }
              
              private void sendLine( String line ) throws IOException {
                byte[] buffer = line.getBytes();
            
                DatagramPacket packet =
                  new DatagramPacket(
                    buffer, buffer.length,
                    InetAddress.getByName( "127.0.0.1" ), 6010 );
                
                System.out.println( "[Client] sending: " + line );
                socket.send( packet );
                
                Sleeper.nap();
              }
            }
          
Bemærk, at vi her, hverken i forbindelse med instantieringen af DatagramSocket, eller i send-kaldet, angiver adresse eller port - disse oplysninger tilknyttes selve pakken, når vi laver en instans af DatagramPacket.
 
Da både server og klient igen er implementeret som tråde, er det ligeledes enkelt at lave en testanvendelse; hvor de kører i samme program:
Kildetekst 6:
Testanvedelse
            public class Main {
  
              public static void main( String[] argv ) {
                new SimpleServer().start();
                Sleeper.nap(); // være sikker på server er online
                  
                new SimpleClient().start();
              }
            }
          
            [Server] Online
            [Client] started
            [Client] sending: Hello
            [Server] received 5 bytes
            [Server] received line: Hello
            [Client] sending: I'm a client
            [Server] received 12 bytes
            [Server] received line: I'm a client
            [Client] sending: <end>
            [Server] received 5 bytes
            [Server] received line: <end>
            [Server] Offline
            [Client] ended
          

 

3. HTTP klienter

Java har en række klasser, der gør det enkelt at lave klienter til HTTP-protokollen (HyperText Transfer Protocol) - populært sagt: klienter der kan hente sider fra web-servere.
Man kan selv programmere en HTTP-klient fra bunden ved at oprette en forbindelse på port 80, og kommunikere med web-server vha. HTTP-protokollen, men vi skal i det følgende se; hvordan man med en række klasser fra java.net kan gøre dette meget enkelt.
URL Først og fremmest skal man kende URL'en til den side man ønsker at læse. En URL (Uniform Resource Locator) har den velkendte form:
http:// <sti> [/ <side>]
F.eks. "http://www.dr.dk/nyheder"
Da HTML-sider er tekst, kan det være nyttigt at bruge en BufferedReader til at læse fra dem. En sådan fås med:
Kildetekst 7:
Åbning af HTML-side til læsning
            URL url = new URL( "http://www.dr.dk/nyheder" );
  
            BufferedReader reader =
              new BufferedReader(
                new InputStreamReader( url.openStream() ) );
          
Her har vi lavet en instans af URL-klassen (fra java.net pakken). En instans af URL, har følgende metode, der returnerer en InputStream, svarende til den vi har set ovenfor, ved forbindelsebaseret kommunikation:
InputStream openStream()
At læse HTML-sider kan bruges til mange sjove eksperimenter. I det følgende skal vi se et program der læser nyheder fra www.dr.dk (Danmarks Radio's web-server). Det udnytter at siden:
 

http://www.dr.dk/nyheder

  bruger et specielt tag til at markere nyheds-overskifter, nemlig:
 

<span class=nyhederleadcontentbig>

  Vi læser linie efter linie fra siden, og leder efter forekomster af dette tag. Når vi finder et, "klipper" vi nyheden ud af HTML-teksten.
  Programmet er som følger:
Kildetekst 8:
Program der læser nyheder fra www.dr.dk
            import java.net.*;
            import java.io.*;
            
            public class Main {
              private static final String startTag = 
                "<span class=\"nyhederleadcontentbig\">";
            
              public static void main( String[] argv ) {
                try {
                  URL url = new URL( "http://www.dr.dk/nyheder" );
                  
                  BufferedReader reader =
                    new BufferedReader(
                      new InputStreamReader( url.openStream() ) );
                  
                  while ( reader.ready() ) {
                    String line = reader.readLine();
                    
                    if ( line.indexOf( startTag ) >= 0 ) {
                      String nyhed = readNyhed( reader, line );
                      if ( nyhed.length() > 0 )
                        System.out.println( nyhed.substring( 0, 70 ).trim() + "..." );
                    }
                  }
                }
                catch ( IOException e ) {
                }
              }
              
              private static String readNyhed( BufferedReader reader, String firstLine ) {
                int start = firstLine.indexOf( startTag ) + startTag.length();
                int end = firstLine.indexOf( "<", start );
                
                if ( end < 0 )
                  return firstLine.substring( start ).trim();
                else
                  return firstLine.substring( start, end ).trim();
              }
            }
          
            Årets første storm kommer i aften ind over hele den sydlige del af lan...
            Mindst tre mennesker blev dræbt, og omkring 30 såret, da en selvmordsb...
            Midt i debatten om kristne friskoler vinder Indre Mission frem blandt...
            Paradentose eller tandkødsbetændelse giver ikke kun dårligere tænder....
          
Her ses resultatet af at køre programmet søndag d. 27. oktober 2002.
Kompliceret at kode Man skal være opmærksom på at et sådant program er meget følsomt overfor ændringer. Hvis f.eks. DR begynder at skriver deres HTML-sider på en anden måde kan det ophøre med at virke. Samtidig har vi her begrænset os i retning af, hvor stor umage vi gøre os i forbindelse med at parse teksten - det er trods alt ikke det primære i dette kapitel - det kunne være lavet mere avanceret.
Læser som en browser Skulle man være blevet inspireret til at lege videre med sådanne programmer, er der ikke nogen problemer i forhold til serveren, da man blot læser sider som enhver browser ville gøre det. Ønsker man at viderebringe informationer fra en sådan side til andre, skal man dog være opmærksom på den ophavsretslige side af sagen.