Sítě v Javě

Prostředky Javy pro práci se sockety

Na síťové vrstvě, zajištěné typicky protokolem IP, může probíhat přenos buď formou zpráv - User Datagram Protocol (UDP) nebo virtuálním kanálem (beze ztrát, TCP). Při přenosu virtuálním kanálem je zaručeno doručení a to v pořadí odeslání. TCP používá k rozlišení komunikujících aplikací porty (stejně jako UDP) - 16 bitová čísla → 65536 možných portů. Z toho prvních 1024 (0 … 1023) jsou tzv. well-known porty, jsou přiřazeny službám (např. 25 SMTP, 80 HTTP atd.) na základě dohody (IANA). Tyto porty smí typicky používat pouze superuživatel (root). Vyšší porty jsou k dispozici uživatelům.

Aplikace na bázi TCP většinou naplňují představu o architektuře klient-server. Server poslouchá na daném portu a čeká, až se připojí klient. Klient zná jméno a port serveru, naváže spojení - vytvoří se virtuální kanál. Každé spojení je identifikováno dvojicí přístupových míst - tzv. socketů („konce potrubí“). Díky tomuto modelu je možné, aby server zároveň obsluhoval více klientů na stejném portu - jelikož klienti se liší alespoň v portu! Jelikož klientovi typicky nezáleží, jaký port mu bude přidělen, je port typicky vybrán a nabídnut přímo operačním systémem - dva klienti potom budou mít zaručeně přidělené dva různé porty.

Socket je tedy koncové místo obousměrné komunikační linky mezi dvěma programy. Je mu „připojen“ na IP adresu a má přidělen port (multiplex aplikací na jedné IP adrese).

import java.io.*;
import java.net.*;

Čtení a zápis z/do socketu

Po vytvoření socketu můžeme získat vstupní a výstupní proud. Čtením/zápisem poté probíhá komunikace.

Předpokládejme, že máme EchoServer, který běží na localhost a poslouchá na TCP portu 9903.

import java.io.*;
import java.net.*;
import java.util.*;
 
public class EchoClient {
    // cely klient je tak jednoduchy, ze je v metode main
    public static void main(String[] args) {
        // otevirani socketu
        Socket clientSocket = null;
        try {
            clientSocket = new Socket("localhost", 9903);
        }
        catch (UnknownHostException ex) {
            // Nepodarilo se najit (DNS, NIS atp.) hostitele
            System.exit(-1);
        }
        catch (IOException ex) {
            // Nepodarilo se spojit s hostitelem
            System.exit(-1);
        }
        // parametry spojeni - vyprseni (pri cteni ze socketu)
        try {
            clientSocket.setSoTimeout(TIMEOUT_1M);
        } catch (SocketException ex) {
            // Nepodarilo se nastavit timeout
        }
        // otevreni proudu, spojeni
        try {
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in));
 
            String input;
            while ((input = systemIn.readLine()) != null) {
                // takhle se posilaji data serveru
                out.println(input);
                // pokud mame skoncit, tak uzavreme otevrene proudy a socket
                if (input.trim().equals("CLOSE")) {
                    in.close();
                    out.close();
                    systemIn.close();
                    clientSocket.close();
                    break;
                }
                // jinak precteme, co server odpovedel a vypiseme
                else {
                    String response = in.readLine();
                    System.out.println("RESPONSE: " + response);
                }
            }
        }
        catch (IOException ex) {
            EchoLogger.log(ERRO,"Doslo k chybe I/O.");
            System.exit(-1);
        }
    }
}

Všiměte si hlavně vytvoření nového socketu Socket clientSocket = new Socket(„localhost“, 9903); - připojení k počítači localhost na TCP port 9903. Získání proudů - metody getInputStream(), getOutputStream() - tyto pak „obalíme“ potřebnými vlastnostmi - např. Print a Buffer.

Poznámka: Jelikož používáme v aplikaci Readery a Writery, musíme bytové proudy konvertovat na Unicodové proudy - to zajistí speciální proud InputStreamWriter. Toto stačí udělat pro Readery, server také používá pro čtení tento proud, takže si umí s daty zapsanými PrintWriterem poradit.

Práce se socketem by se tedy dala popsat následovně:

  1. otevření socketu,
  2. otevření vstupního a výstupního proudu do socket,
  3. čtení/zápis z/do proudu, ale pozor, je třeba dodržet protokol serveru (porozumění),
  4. uzavření proudů,
  5. uzavření socketu.

ServerSocket

ServerSocket slouží ke přidělení portu serveru. Tento socket je pouze poslouchací, z něj získáváme metodou accept() instance třídy Socket, se kterými pracujeme tak, jak bylo popsáno výše.

Socket clientSocket = null;
ServerSocket serverSocket = null;
 
// ziskani portu nebo chyba
try {
  serverSocket = new ServerSocket(9903);
} catch (IOException e) {
    System.out.println("Port je uz obsazen.");
    System.exit(-1);
}
 
// ziskani bezneho socketu - bude se cekat do pripojeni klienta!
try {
    clientSocket = serverSocket.accept();
} catch (IOException e) {
    System.out.println("Pripojeni selhalo.");
    System.exit(-1);
}

Obsluha ServerSocketu se typicky provádí v cyklu, aby server byl schopen akceptovat předem nedefinovaný počet spojení. Abychom zajistili možnost současné obsluhy, je nutné využít vláken. Protokol serveru (obsluha požadavků klienta) implementujeme jako potomka třídy Thread, instanci Socketmu předáme konstruktorem a vlákno spustíme. Tak se jedno vlákno vždy stará o jedno spojení. Pokud tak neučiníme, spojení budou obsluhována sekvenčně (za sebou).

Řešený příklad

URL v Javě

URL - Uniform Resource Locator - unikátní adresa v internetu. Skládá se z identifikátoru protokolu (http), oddělovače (:) a vlastní adresy zdroje

Prostředky Javy pro práci s URL

import java.net.*;

Třída URL umožňuje vytvořit URL adresu - absolutní, relativní atp. (různé konstruktory). Pokud by URL adresa měla obsahovat jiné než ASCII znaky, je třeba použít třídu URI, vytvořit její instanci (obdobné konstruktory jako URL) a pak metodou toURL() transformovat na URL adresu. Pokud URL adresa nebude platná, bude vyhozena výjimka MalformedURLException.

Pokud máme URL adresu, můžeme získat vstupní/výstupní proud a z/do URL adresy číst/zapisovat tak, jak jsme zvyklí s běžnými proudy.

import java.net.*;
import java.io.*;
 
public class URLReader {
    public static void main(String[] args) throws Exception {
        URL pjv = new URL("http://x36pjv.tomaskadlec.net/");
        BufferedReader in = new BufferedReader(
                                new InputStreamReader(
                                pjv.openStream()));
 
        String inputLine;
 
        while ((inputLine = in.readLine()) != null)
            System.out.println(inputLine);
 
        in.close();
    }
}

Čtení přímo z URL není nejvhodnější, lepší je vyrobit instanci URLConnection resp. některého potomka jako HttpURLConnection. Tyto třídy umožňují nastavit parametry spojení.

import java.net.*;
import java.io.*;
 
public class URLConnectionReader {
    public static void main(String[] args) throws Exception {
        URL pjv = new URL("http://x36pjv.tomaskadlec.net/");
        URLConnection pjvConnection = pjv.openConnection();
        BufferedReader in = new BufferedReader(
                                new InputStreamReader(
                                pjvConnection.getInputStream()));
        String inputLine;
 
        while ((inputLine = in.readLine()) != null)
            System.out.println(inputLine);
        in.close();
    }
}

Obdobně lze do URL zapisovat, ale pozor, jedná se o přenos dat metodou POST. Pokud bychom chtěli data přenášet v URL - metodou GET - musíme vždy vytvořit novou instanci URL - instance třídy URL nelze měnit!

courses/b0b36pjv/tutorials/10/site.txt · Last modified: 2021/04/21 17:17 by mudromar