javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen

16 Netværkskommunikation

Indhold:

Kapitlet forudsættes ikke i resten af bogen.

Forudsætter 15, Datastrømme og filhåndtering.

Alle maskiner på et TCP-IP-netværk, f.eks. internettet, har et IP-nummer. Det er en talrække på fire byte1, der unikt identificerer en maskine på nettet, f.eks. 195.215.15.20.

Normalt bruger man en navnetjeneste (eng.: Domain Name Service - DNS), der sammenholder alle numre med navne, der er nemmere at huske, f.eks. www.cv.ihk.dk eller www.esperanto.dk. Adressen localhost (IP-nummer 127.0.0.1) er speciel ved altid at pege på den maskine, man selv sidder ved.

Kommunikation mellem to maskiner sker ved, at værtsmaskinen (eng.: host) gør en tjeneste (eng.: service) tilgængelig på en bestemt port, hvorefter klienter kan åbne en forbindelse til tjenesten.

Hjemmesider (HTTP-tjenesten) er tilgængelige på port 80. Filoverførsel (FTP-tjenesten) er på port 21, og hvis man vil logge ind på maskinen (telnet-tjenesten), er det port 23.

I det følgende vil vi vise, hvordan man bruger og udbyder HTTP-tjenesten til hjemmesider, men andre former for netværkskommunikation foregår på lignende måder.

16.1 At forbinde til en port

Man opretter en forbindelse ved at oprette et Socket-objekt2 og angive værtsmaskinen og porten i konstruktøren. Vil man f.eks. kontakte webserveren på www.esperanto.dk, skriver man:

  Socket forbindelse = new Socket("www.esperanto.dk",80);

Hvis alt gik godt, har Socket-objektet (forbindelsen eller "soklen") nu kontakt med værtsmaskinen (ellers har den kastet en undtagelse).

Nu skal vi have fat i datastrømmene fra os til værten og fra værten til os:

  OutputStream binærUd  = forbindelse.getOutputStream();
  InputStream  binærInd = forbindelse.getInputStream();

Hvis vi vil sende/modtage binære data, kan vi nu bare gå i gang: binærUd.write() sender en byte eller en række af byte til værten, og binærInd.read() modtager en eller flere byte.

Hvis det er tekstkommunikation, er PrintWriter og BufferedReader (der arbejder med tegn og strenge som beskrevet i kapitel 15) dog nemmere at bruge:

  PrintWriter    ud  = new PrintWriter(binærUd);
  BufferedReader ind = new BufferedReader(new InputStreamReader(binærInd));

Nu kan vi f.eks. bede om startsiden (svarende til http://www.esperanto.dk/index.html) ved at sende "GET /index.html HTTP/0.9", "Host: www.esperanto.dk" og en blank linie:

  ud.println("GET /index.html HTTP/0.9");
  ud.println("Host: www.esperanto.dk");
  ud.println();
  ud.flush();

Kaldet til flush() sikrer, at alle data er sendt til værten, ved at tømme eventuelle buffere.

Nu sender værten svaret, der kan læses fra inddatastrømmen3:

  String s = ind.readLine();
  while (s != null)
  {
    System.out.println("modt: "+s);
    s = ind.readLine();
  }

While-løkken bliver ved med at læse linier. Når der ikke er flere data (værten har sendt alle data og lukket forbindelsen), returnerer ind.readLine() null, og løkken afbrydes.

Her er hele programmet:

import java.io.*;
import java.net.*;
public class HentHjemmeside
{
  public static void main(String[] arg)
  {
    try {
      Socket forbindelse = new Socket("www.esperanto.dk",80);
      OutputStream binærUd  = forbindelse.getOutputStream();
      InputStream  binærInd = forbindelse.getInputStream();
      PrintWriter    ud  = new PrintWriter(binærUd);
      BufferedReader ind = new BufferedReader(new InputStreamReader(binærInd));
      ud.println("GET /index.html HTTP/0.9");
      ud.println("Host: www.esperanto.dk");
      ud.println();
      ud.flush();               // send anmodning afsted til værten
      String s = ind.readLine();
      while (s != null)         // readLine() giver null når datastrømmen lukkes
      {
        System.out.println("modt: "+s);
        s = ind.readLine();
      }
      forbindelse.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

modt: HTTP/1.1 200 OK
modt: Date: Tue, 17 Apr 2001 13:06:06 GMT
modt: Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/4.0.2 mod_perl/1.24
modt: Last-Modified: Thu, 05 Mar 1998 17:28:16 GMT
modt: Accept-Ranges: bytes
modt: Content-Length: 896
modt: Content-Type: text/html
modt:
modt: <HTML><HEAD><TITLE>Esperanto.dk</TITLE>
modt: <META name="description" content="Den officielle danske hjemmeside om plansproget esperanto. Oficiala dana hejmpagxo pri la planlingvo Esperanto.">
modt: <META name="keywords" content="Esperanto, Danmark, Danio, Esperanto-nyt, Zamenhof, bogsalg, plansprog, lingvistik, libroservo, planlingvo, lingvistiko">
modt: </HEAD>
modt: <FRAMESET cols="22%,*">
modt: <FRAME NAME="menu"    SRC="da/menu.htm"     MARGINWIDTH=0>
modt: <FRAME NAME="indhold" SRC="da/velkomst.htm">
modt: <NOFRAMES>
modt: Velkommen til Esperanto.dk!<p>
modt: Gå til <a href="da/velkomst.htm">velkomst-siden</a>,
modt: eller til <a href="da/menu.htm">indholdsfortegnelsen</a>.
modt: </NOFRAMES>
modt: </FRAMESET>
modt: </HTML>

Det ses, at svaret starter med et hoved med metadata, der beskriver indholdet (dato, værtens styresystem, hvornår dokumentet sidst blev ændret, længde, type).

Derefter kommer en blank linie og så selve indholdet (HTML-kode).

Dette er i overensstemmelse med måden, som data skal sendes på ifølge HTTP-protokollen. Protokollen er løbende blevet udbygget. En af de tidligste (og dermed simpleste) var HTTP/0.9, mens de fleste moderne programmer bruger HTTP/1.1.

16.2 At lytte på en port

For at lave et program, der fungerer som vært (dvs. som andre maskiner/programmer kan forbinde sig til), opretter man et ServerSocket-objekt, der accepterer anmodninger på en bestemt port:

      ServerSocket værtssokkel = new ServerSocket(8001);

Nu lytter vi på port 8001. Så er det bare at vente på, at der kommer en anmodning:

      Socket forbindelse = værtssokkel.accept();

Kaldet af accept() venter på, at en klient forbinder sig, og når det sker, returnerer kaldet med en forbindelse til klienten i et Socket-objekt.

Derefter kan vi arbejde med forbindelsen ligesom før. Ligesom når to mennesker snakker sammen, har det ikke den store betydning, hvem der startede samtalen, når den først er kommet i gang.

I tilfældet med HTTP-protokollen er det defineret, at klienten først skal spørge og værten (webserveren) derefter svare, så vi læser først en anmodning

      String anmodning = ind.readLine();
      System.out.println("Anmodning: "+anmodning);

... og sender derefter et svar, tømmer databufferen og lukker forbindelsen:

        ud.println("HTTP/0.9 200 OK");
        ud.println();
        ud.println("<html><head><title>Svar</title></head>");
        ud.println("<body><h1>Kære bruger</h1>");
        ud.println("Du har spurgt om "+anmodning+", men der er intet her.");
        ud.println("</body></html>");
        ud.flush();
        forbindelse.close();

Bemærk, at ud.flush() skal ske, før vi lukker forbindelsen, ellers går svaret helt eller delvist tabt; Socket-objektet ved ikke, om datastrømmen har nogle data liggende, der ikke er blevet sendt endnu.

Herunder ses det fulde program.

import java.io.*;
import java.net.*;
public class Hjemmesidevaert
{
  public static void main(String[] arg)
  {
    try {
      ServerSocket værtssokkel = new ServerSocket(8001);
      while (true)
      {
        Socket forbindelse = værtssokkel.accept();
        PrintWriter ud = new PrintWriter(forbindelse.getOutputStream());

        BufferedReader ind = new BufferedReader(
          new InputStreamReader(forbindelse.getInputStream()));

        String anmodning = ind.readLine();
        System.out.println("Anmodning: "+anmodning);

        ud.println("HTTP/0.9 200 OK");
        ud.println();
        ud.println("<html><head><title>Svar</title></head>");
        ud.println("<body><h1>Kære bruger</h1>");
        ud.println("Du har spurgt om "+anmodning+", men der er intet her.");
        ud.println("</body></html>");
        ud.flush();
        forbindelse.close();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Anmodning: GET / HTTP/0.9
Anmodning: GET / HTTP/1.1
Anmodning: GET /xx.html HTTP/1.1

Du kan afprøve programmet ved at ændre på HentHjemmeside.java til at spørge 'localhost' på port 8001 eller ved i en netlæser at åbne adressen http://localhost:8001/xx.html

16.3 URL-klassen

HTTP-forespørgsler kan egentligt klares nemmere ved, at man bruger URL-klassen, der er indrettet til netop dette (og som tager højde for eventuelle brandmure og proxyer), og som tillader at arbejde på et mere overordnet niveau uden at kende til detaljerne i HTTP-protokollen.

Herunder er HentHjemmeside igen, men hvor URL-klassen bruges i stedet.

import java.io.*;
import java.net.*;
public class HentHjemmesideMedURL
{
  public static void main(String[] arg)
  {
    try {
      URL url = new URL("http://www.esperanto.dk");
      InputStream binærInd = url.openStream();
      BufferedReader ind = new BufferedReader(new InputStreamReader(binærInd));
      String s = ind.readLine();
      while (s != null)
      {
        System.out.println("modt: "+s);
        s = ind.readLine();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

modt: <HTML><HEAD><TITLE>Esperanto.dk</TITLE>
modt: <META name="description" content="Den officielle danske hjemmeside om plansproget esperanto. Oficiala dana hejmpagxo pri la planlingvo Esperanto.">
...

Faktisk er URL-klassen meget kapabel og understøtter mere end HTTP-protokollen.

Man kan åbne en fil på filsystemet med f.eks.:

new URL("file:sti/filnavn.txt")

Man kan endda åbne et jar- eller zip-arkiv (her arkiv.jar) og læse en fil fra det:

new URL("jar:file:arkiv.jar!/fil_i_arkivet.txt")

Man kan også bruge anonym FTP til at hente filer og liste kataloger:

new URL("ftp://sunsite.dk/")

... eller logge ind med brugernavn og adgangskode:

new URL("ftp://brugernavn:adgangskode@ftp.vært.dk/fil.txt")

URL-klassen understøtter ikke at gemme filer (hverken på disk eller over FTP/HTTP).

16.4 Opgaver

  1. Lav Hjemmesidevaert om, så den, afhængig af anmodningen, kan give tre forskellige svar.

  2. Skriv HentHjemmeside om, så den spørger den lokale maskine ('localhost') port 8001, og brug den til at teste Hjemmesidevaert (der køres i en separat proces).

  3. Lav en virtuel opslagstavle. Den skal bestå af klasserne Opslagstavletjeneste, som udbyder tjenesten (brugport 8002), og Opslagstavleklient, som forbinder sig til tjenesten. Opslagstavletjeneste skal understøtte to former for anmodninger: 1) TILFØJ, der føjer en besked til opslagstavlen og 2) HENTALLE, der sender alle opslag til klienten. Afprøv begge slags anmodninger fra Opslagstavleklient.

  4. Lav din egen mellemvært (eng.: proxy - betyder egentlig 'stråmand'), der modtager en HTTP-forespørgsel og spørger videre for klienten.

  5. Prøv hver af de andre eksempler på URL-adresser i HentHjemmesideMedURL.

16.5 Avanceret

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

16.5.1 FTP-kommunikation

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

16.5.2 Brug af FTP fra en applet

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

1For den nye version af IP-protokollen, IPv6, som man regner med vil slå igennem omkring år 2005, er det 16 byte. Den gamle vil dog blive understøttet mange år frem.

2Netop HTTP kan egentligt klares nemmere med URL-klassen (se senere). Vi bruger Socket-klassen i det følgende, da den kan anvendes til alle former for netværkskommunikation.

3I virkeligheden tager det noget tid, før data når frem til klienten, men så vil read-metoderne blokere, dvs. vente på, at der er data.

4Meddelelser kunne også hentes med URL-klassen, sådan at de kunne ses uden adgangskode. Husk da, at protokollerne FTP og HTTP er ret forskellige, blandt andet går FTP ud fra brugerens hjemkatalog, mens hjemmeside-adresser ofte ligger i et underkatalog, der hedder public_html. F.eks. svarer "PUT public_html/opslag/opslag.txt" fra FTP-protokollen i HTTP-protokollen til URL'en "http://pingo.cv.ihk.dk/~jano/opslag/opslag.txt" på min maskine.

javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen
http://javabog.dk/ - af Jacob Nordfalk.
Licens og kopiering under Åben Dokumentlicens (ÅDL) hvor intet andet er nævnt (80% af værket).

Ønsker du at se de sidste 20% af dette værk (205249 tegn) skal du købe bogen. Så får du pæne figurer og layout, stikordsregister og en trykt bog med i købet.