Kommunikation via sockets

En introduktion till hur Javaprogram kan kommunicera över ett nätverk med hjälp av TCP-sockets samt en titt på några övriga klasser i paketet java.net.

Litteratur

Mycket kort om adressering på internet

En IP-adress består av fyra byte och anger till vilken dator kommunikationen ska ske. I stället för dessa fyra bytes kan ett alias anges. Vi skriver exempelvis www.kth.se för att titta på KTH:s sajt, inte 130.237.32.50.  Aliaset översätts automatiskt till siffernotation av en DNS (Domain Name Service).

Det räcker dock inte att peka ut vilken dator som ska adresseras, det gäller också att hitta rätt process på datorn.  Därför går det att ange vilken av dess portar som ska adresseras. En port anges som ett 16-bitars tal, dvs ett tal mellan 0 och 65535. När en process kommunicerar på nätet använder den en av dessa portar. Vissa typer av server använder alltid samma portnummer för att de ska vara lätta att hitta. Exempelvis förväntar vi oss att hitta en HTTP-server på port 80 och en FTP-server på port 21. Portnummer under 1024 kallas välkända och bör inte användas eftersom de kan vara bokade för någon sorts tjänst.

Klassen InetAddress

Klassen java.net.InetAddress symboliserar en IP-adress. Den har inga publika konstruktorer, en instans kan skapas med någon av nedanstående tre statiska metoder: Här kommer ett litet program som skriver ut IP-adresser med hjälp av InetAddress, NetworkInfo.java.

TCP

Endpunkten i en TCP-förbindelse utgörs av en socket, det som skickas från en socket i den ena änden av förbindelsen kan läsas från socketen i den andra änden. I Java representeras socketar av klasserna java.net.Socket och java.net.ServerSocket.

Vi ska nu steg för steg skriva ett Javaprogram som kommunicerar via TCP med hjälp av sockets. Programmet består av en klient och en server. Det enda som händer i programmet är att klienten skickar en sträng till servern som skriver ut strängen.

  1. Vi börjar med serversidan. Det första steget är att skapa en socket vilket görs med raden

  2. ServerSocket ss = new ServerSocket(4711);
    Detta skapar en socket som binds till port nummer 4711 på local host.
  3. Därefter anropas metoden accept() vilken lyssnar efter anrop till socketens port. När den returnerar har ett anrop skett. accept() returnerar en ny Socket över vilken kommunikationen kommer att ske. Den ServerSocket som skapades först finns kvar och vi kan anropa accept() på den igen för att ta emot fler anrop från klienter som vill koppla upp sig. Anropet av accept()ser ut så här: Socket s = ss.accept();
  4. Nästa steg är att få tag i en inström från socketen så att vi kan läsa det som klienten sänder till den. Det görs med metoden getInputStream() vilken returnerar en InputStream. Eftersom vi ska läsa tecken vill vi hellre ha en Reader, det får vi genom att koppla den nyss erhållna InputStreamen till en InputStreamReader som översätter en bytebaserad InputStream till en teckenbaserad Reader. Detta skulle resultera i följande rad, new InputStreamReader(s.getInputStream()). Men det vore smidigt att i stället använda strömmen BufferedReader vilken innehåller den bekväma metoden readLine() som läser in en rad i form av en String. Det åstadkoms på följande sätt:

  5. sockIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
  6. Det var servern, nu är det dags att skriva klienten. Steg ett är att skapa en socket, vilket görs så här:

  7. Socket s = new Socket("localhost", 4711);
    När en Socket skapas anropar den automatiskt angiven port (4711 i det här fallet) på angiven host (local host i det här fallet).
  8. Sedan ska vi ha tag i en utström till socketen vilket vi får genom att anropa metoden getOutputStream(). Den returnerar en OutputStream men det vore enklare att använda en PrintWriter. Writer eftersom det handlar om teckenbaserad I/O och PrintWriter eftersom den innehåller de välkända och lättanvända metoderna print() och println(). Detta resulterar i följande rad:

  9. PrintWriter sockOut = new PrintWriter(s.getOutputStream());
Då var det klart. Det vi skriver till klientens utström går att läsa från serverns inström. Här kommer hela programmet, det består av en serverdel, ReadAndPrint.java, och en klientdel, Send.java.

Flertrådad server

Ovanstående server har oacceptabelt lång svarstid (förklaras mer i avsnittet om meddelandesändning som kommer senare). Lösningen är att göra den flertrådad. Vi bryr oss inte om att implementera en trådpool utan nöjer oss med den flertrådning som skissas i stycket "En tråd per meddelande". Detta resulterar i följande variant av servern, MTReadAndPrint.java. Varje gång accept() returnerar har en ny Socket skapats till vilken en ny förbindelse har kopplats upp. Då startas nu en ny tråd som läser från den nyss returnerade Socketen. Detta innebär att alla förbindelser hanteras samtidigt.

URL-hantering

En URL (Uniform Resource Locator) identifierar en resurs på nätet. Det kan vara till exempel en html-fil, en bild eller en databas. URL:er hanteras i Java av klassen URL. Ett exempel på en URL är http://www.ncsa.uiuc.edu:8080/demoweb/url-primer.html, där "http" anger protokoll, "www.ncsa.uiuc.edu" anger host (dator), "8080" anger port och "/demoweb/url-primer.html" pekar ut resursen på datorn. Denna sista del kallas ofta path.

Klassen URL har dels konstruktorer där en hel URL kan ges i form av en sträng, dels sådana där de olika delarna kan anges var för sig. Det finns också konstruktorer med vars hjälp en URL kan anges relativt en annan URL. Klassen innehåller bland annat metoder som ger information om den representerade URL:en.