Remoting

Z FI WIKI
Verze z 18. 11. 2013, 18:32; 3988@muni.cz (diskuse | příspěvky)

(rozdíl) ← Starší verze | zobrazit aktuální verzi (rozdíl) | Novější verze → (rozdíl)
Přejít na: navigace, hledání


Často je třeba komunikovat mezi systémy umístěnými na různých počítačích. Některé způsoby umožňují komunikovat pouze mezi systémy vytvořenými ve stejném programovacím jazyku, jiné umožňují komunikovat mezi libovolnými systémy.

Druhy komunikace

  • z hlediska synchronnosti
    • synchronní – volající strana zastaví a čeká, dokud nedostane odpověď
    • asynchronní – volající strana pokračuje v práci, na příchod odpovědi je upozorněna
  • z hlediska zajištění doručení zprávy
    • transientní (pomíjivá)
    • persistentní (vytrvalá)

Na Internetu obvyklá komunikace protokolem HTTP je transientní - pokud cílový server nefunguje nebo je přerušeno sítové spojení, komunikace bude prostě neúspěšná. Pro některé, zejména tzv. enterprise aplikace je to nepřijatelné. Pro persistentní komunikaci se používají specializované systémy vytvářející fronty zpráv, např. IBM MQSeries, ke kterým existuje v JavaEE univerzální rozhraní zvané JMS - Java Message Service.

Záleží na typu aplikace, jaký druh komunikace je pro ni vhodný.

Síťová komunikace z hlediska vývojáře aplikací

V dnešní době se používají pro síťovou komunikaci zejména protokoly Internetu, ostatní protokoly (např. Infiniband) jsou jen pro specializované aplikace. Protokoly Internetu neodpovídají přesně vrstvám ISO/OSI modelu.

Dva síťařské vtipy:

  • "I'd tell you a UDP joke, but you may not get it." ("Řekl bych vám vtip o UDP, ale nemusel by vám dojít.")
  • "I could tell you a joke about TCP, but I’d have to keep repeating it until you got it." ("Řekl bych vám vtip o TCP, ale musel bych ho opakovat dokud vám nedojde.")


IP 

Datová komunikace po Internetu probíhá prostřednictvím protokolu IP (Internet Protocol) zasíláním datagramů z jedné IP adresy na druhou.

V současné době se používají dvě verze IP protokolu, IPv4 používající 32-bitové adresy zapisované ve tvaru čtyř desítkových čísel oddělených tečkami, např. 147.251.9.183, a IPv6 používající 128-bitové adresy zapisované jako 8 čtveřic hexadecimálních cifer oddělených dvojtečkami, např. 2001:718:801:130:216:3eff:fe02:403. IPv4 adresy byly vyčerpány 3. února 2011. IP adresa je v Javě reprezentována třídou java.net.InetAddress.

IP adresy jsou špatně zapamatovatelné, proto služba DNS (Domain Name System) zajišťuje převod zapamatovatelných jmen na adresy, např. výše uvedené adresy patří jménu www.metacentrum.cz.

Komunikace IP protokolem je nespolehlivá, datagramy se mohou ztrácet nebo přicházet v jiném pořadí, než byly odeslány.

TCP

Spolehlivé doručení dat ve správném pořadí zajišťuje protokol TCP (Transmission Control Protocol), který doručuje tzv. packety mezi dvojicemi bodů určených IP adresou a číslem portu, např. z 147.251.3.64:12345 na 147.251.9.183:80.

TCP používá potvrzování packetů po určitém počtu odeslaných packetů. Proto TCP nedokáže využít plnou šířku pásma sítě, a nezaručuje horní mez doby doručení. Proto TCP není vhodný pro doručování dat v reálném čase, např. hlasových dat.

Programové rozhraní protokolu TCP se nazývá socket a využívá model klient-server. TCP server poslouchá na jedné nebo více IP adresách na určitém portu. Spojení je iniciováno klientem, který se připojí na server. Server obvykle reaguje přenesením spojení na jiný port a na původním portu dál čeká na příchozí spojení.

V Javě stranu TCP serveru představuje třída java.net.ServerSocket a stranu klienta třída java.net.Socket.

Nad protokolem TCP se budují aplikace a aplikační protokoly. Aplikační protol má obvykle tzv. well known port, např. 80 pro HTTP, 443 pro SSL, 25 pro SMTP, atd.

TCP server v Javě

 
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
 
/**
 * Simple one-thread TCP server in Java.
 */
public class TcpServer {
 
    public static final int SERVER_PORT = 65432;
 
    public static void main(String[] args) throws IOException {
 
        ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
 
        while (true) { //démon nikdy neumírá
            System.out.println("=======");
            System.out.println("Čekám na spojení na "+serverSocket.getInetAddress().getHostAddress()+":"+serverSocket.getLocalPort());
            //čeká na příchozí spojení
            Socket socket = serverSocket.accept();
            System.out.println("Připojil se klient z "+socket.getInetAddress()+":"+socket.getPort());
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(),"utf-8"));
            PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"utf-8"),true);
            out.println("Server Vás vítá !");
            String line = null;
            //obsluha dokud klient neuzavře spojení, nebo nepožádá o uzavření slovem "exit"
            while((line=in.readLine())!=null) {
                System.out.println("Přišel řádek '"+line+"'");
                if("konec".equals(line)) {
                    out.println("Děkujeme, přijďte zas.");
                    break;
                }
                out.println("Zvětšeno: " + line.toUpperCase());
            }
            System.out.println("Ukončuji spojení s "+socket.getInetAddress()+":"+socket.getPort());
            in.close();
            out.close();
            socket.close();
        }
    }
 
}

TCP klient v Javě

 
import java.io.*;
import java.net.Socket;
 
/**
 * Simple TCP client. Uses HTTP protocol.
 */
public class TcpClient {
 
    public static void main(String[] args) throws IOException {
 
        Socket socket = new Socket("www.seznam.cz", 80);
 
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
        PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "utf-8"), false);
 
        out.println("GET / HTTP/1.0");
        out.println("Host: www.seznam.cz");
        out.println();
        out.flush();
        String line = null;
        while ((line = in.readLine()) != null) {
            System.out.println(line);
        }
        in.close();
        out.close();
        socket.close();
    }
 
}

Aplikační vrstva

Nad TCP (nebo UDP) je aplikační vrstva. Tu už obvykle implementuje naše aplikace v Javě, buď přímo, nebo pomocí existujících knihoven pro různé protokoly (HTTP, FTP, SSL, atd.)

Je rozdíl mezi formátem zpráv a protokolem. Protokol je systém pravidel pro výměnu zpráv. Např. si můžeme vyměňovat zprávy ve formátu XML nebo JSON, ale jak k výměně dochází popisuje komunikační protokol, např. HTTP nebo XMPP.

NAT a firewally

Komunikace pomocí TCP/IP mezi dvěma stroji nemusí být vždy možná.

Jeden problém představují tzv. firewally (protipožární zdi), které z bezpečnostních důvodů blokují provoz, pokud není explicitně povolen. Povolen bývá provoz ven z místní sítě, a do místní sítě jen komunikace aplikačními protokoly HTTP a SMTP kvůli webovým a poštovním serverům.

Druhý problém je tzv. NAT (Network Address Translation), kdy počítač nemá veřejně dostupnou IP adresu, ale privátní adresu v rozsazích 192.168.x.x, 172.16.x.x172.31.x.x, a 10.x.x.x. NAT je prováděn na zařízení připojujícím místní síť k Internetu, obvykle je to domácí WiFi router nebo firemní firewall. NAT vyměňuje v procházejících datagramech privátní IP adresy za veřejné a naopak.

NAT šetří počet používaných veřejných IPv4 adres. Počítač s privátní IP adresou nelze zvenku kontaktovat, komunikace musí být zahájena z privátní adresy ven. Proto nelze přímo komunikovat mezi dvěma počítači za různými NATy. Odhaduje se, že až polovina uživatelů Internetu je za NATem. NAT se dá obejít na aplikační úrovni, dělá to např. program Skype, který provádí komunikaci mezi dvěma stroji za NATem pomocí třetího stroje s veřejnou adresou.

Bezpečnost

Komunikace přes TCP/IP není šifrovaná, lze ji snadno odposlouchávat nebo podvrhnout. Bezpečnost je nutné řešit na aplikační vrstvě.

Bezpečnost je nutné řešit u všech aplikací, které komunikují přes veřejnou síť, protože stav Internetu dnes připomíná středověk - cesty jsou plné lapků a kolem měst jsou hradby (firewally).

Obvyklé řešení je SSL/TLS (Secure Sockets Layer/Transport Layer Security), při které je komunikace šifrována symetrickou šifrou, a při ustavení spojení se server nebo oba konce spojení identifikují svými X509 certifikáty podepsanými důvěryhodnými certifikačními autoritami.

V Javě je SSL/TLS reprezentováno v balíku javax.net.ssl, zejména třídou javax.net.ssl.SSLSocket, a certifikáty v balíku java.security.cert, zejména třídou java.security.cert.X509Certificate pro certifikát a třídou java.security.cert.TrustAnchor pro důvěryhodnou certifikační autoritu.

Další užitečné třídy jsou javax.crypto.Mac pro MAC (Message Authentication Code) a java.security.MessageDigest pro hashovací funkce typu MD5 a SHA.

Vzdálené volání - RPC a RMI

RPC (Remote Procedure Call) a RMI (Remote Method Invocation) jsou pro programátory snadno pochopitelné postupy, jak volat kód na jiném počítači. Rozdíl je v objektové orientovanosti - RPC předpokládá pouze existenci volatelných procedur, kdežto RMI předpokládá, že existují vzdálené objekty se vzdáleně volatelnými metodami. V obou případech programátor volá vzdálený kód stejně, jako kdyby byl lokální.

Příkladů implementací RPC v Javě je více, např. SOAP, XML-RPC, Hessian. Tato řešení umožňují komunikaci se systémy vytvořenými v libovolném jazyce.

Příklady implementací systémů distribuovaných objektů jsou: CORBA,DCOM, Java RMI. Tato řešení v praxi neumožňují komunikaci se systémy vytvořenými v libovolném jazyce.

Systémy s ambicí umožnit komunikaci mezi systémy v různých programovacích jazycích pak mají nějaký jazyk pro popis toho, co lze volat, např.

  • CORBA má tzv. IDL - Interface Definition Language
  • SOAP má tzv. WSDL - Web Service Definition Language.

Existují pak nástroje, kterými lze z popisu rozhraní vygenerovat tzv. client stub, což je kód ve zvoleném programovacím jazyku volatelný lokálně. Na pozadí pak tento kód zajišťuje

  • marshalling/serializaci jména a parametrů volané funkce nebo metody z paměti klientského programu do tvaru přenositelného přes síť
  • komunikaci se vzdáleným serverem
  • demarshaling/deserializaci návratových hodnot zpět do paměťového prostoru klienta

Java RMI

Přímo v základních knihovnách Java SE od verze 1.3 je integrováno řešení zvané Java RMI. Viz Java Remote Method Invocation.

Je mixem dvou technologií. Původního RMI, které je pevně svázáno s jazykem Java, a pro přenos používá speciální binární formát, do kterého mohou být zapsány všechny objekty označené rozhraním java.io.Serializable. A CORBA (Common Object Request Broker Architecture), které - alespoň teoreticky - umožňuje vzdálené volání objektů i mezi různými programovacími jazyky.

Hlavní výhodou Java RMI je jeho rychlost. Hlavní nevýhodou jeho závislost na formátu serializace a formátu .class souborů, který se liší podle verzí Javy, a proto spolu fungují pouze klienti a servery používající stejnou verzi JVM.

Java RMI využívá toho, že bajt kód tříd v souborech .class je nezávislý na operačním systému či typu procesoru, a umožňuje něco, co ostatní systémy nemohou nabídnout - lze předat vzdálené metodě jako parametr instanci třídy, kterou vzdálený systém nezná (dynamic code loading). Vzdálený systém si sám stáhne příslušný bajt kód a může ho provést. Je tedy možné udělat něco takového:

Část na serveru:

 
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
 
/**
 * Rozhraní pro objekt na serveru.
 */
public interface Compute extends Remote {
    <T> T executeTask(Task<T> t) throws RemoteException;
}
 
/**
 * Rozhraní pro parametr metody na serveru.
 */
public interface Task<T> {
    T execute();
}
 
/**
 * Implementace na serveru.
 */
public class ComputeEngine implements Compute {
 
    public <T> T executeTask(Task<T> t) {
        return t.execute();
    }
 
    public static void main(String[] args) {
        Compute engine = new ComputeEngine();
        Compute stub = (Compute) UnicastRemoteObject.exportObject(engine, 0);
        LocateRegistry.getRegistry().rebind("Compute", stub);      
    }
}

Část na klientovi:

 
/**
 * Implementace úlohy poskytnutá klientem.
 */
public class Pi implements Task<BigDecimal>, Serializable {
 
    public BigDecimal execute() {
      // implementace výpočtu čísla Pí
      //...
    } 
}
 
/**
 * Klient.
 */
public class ComputePi {
    public static void main(String args[]) {
        Pi task = new Pi();
        Compute comp = (Compute) LocateRegistry.getRegistry("stroj.nekde.cz").lookup("Compute");
        BigDecimal pi = comp.executeTask(task);
    }
}

Formáty komunikačních zpráv

XML

XML (eXtended Markup Language) je standardizovaný značkovací jazyk. Jeho hlavní výhodou je existence mnoha přidružených standardů, např. XML Schema, které umožňuje předepsat povolené formáty dat, nebo XML Signature pro digitální podpis.

JSON

JSON (JavaScript Object Notation) je formát dat, který je zapisuje jako JavaScriptové objekty pomocí skládání polí, map a skalárních hodnot. Např.:

{
 "jméno" : "Jan",
 "příjmení": "Novák",
 "adresa": {
     "ulice": "Severní 1",
     "obec":   "Horní Dolní"
 },
 "věk": 110,
 "mrtev": true,
 "odpracované roky": [1950,1951,1952,1953,1954],
 "oblíbené knihy": [ {"název":"Vinnetou","autor":"Karel May"}, {"název":"Babička","autor":"Božena Němcová"} ]  
}

Jeho výhodou je, že ve WWW prohlížečích vybavených JavaScriptem (což jsou všechny) není data třeba nijak parsovat. Zároveň je kompaktnější než XML, které bylo původně navrženo pro dokumenty, nikoliv data. Implementace parserů a generátorů existují pro všechny jazyky.

binární

Binární formáty mají výhodu v rychlé parsovatelnosti, nevýhodu ve špatné rozšiřitelnosti v novějších verzích.

Java RMI používá binární formát.

Použití HTTP protokolu pro komunikaci

Z bezpečnostních důvodů je na většině firewallů ve směru dovnitř zakázán jiný provoz než na porty 80 a 443 protokolem HTTP resp. HTTP nad SSL.

Ve standardních knihovnách v JRE je základní HTTP klient, číst URL metodou GET se dá takto:

 
        InputStream inputStream = new URL("http://www.seznam.cz").openStream();
        BufferedReader in = new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
        String line;
        while((line=in.readLine())!=null) {
            System.out.println(line);
        }

V případě potřeby se dá udělat i POST, případně pracovat s hesly a certifikáty.

Ale pro složitější práci s HTTP se doporučuje knihovna Apache Http Components Client.

XML-RPC

XML-RPC pomocí XML zapisuje jednoduchá volání, např:

 
<?xml version="1.0"?>
<methodCall>
  <methodName>examples.getStateName</methodName>
  <params>
    <param>
        <value><i4>40</i4></value>
    </param>
  </params>
</methodCall>

existují implementace pro mnoho jazyků, viz XML-RPC Home Page. V Javě např. Apache XML-RPC, použití např.:

 
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
 
  XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
  config.setServerURL(new URL("http://127.0.0.1:8080/xmlrpc"));
 
  XmlRpcClient client = new XmlRpcClient();
  client.setConfig(config);
  Object[] params = new Object[]{new Integer(33), new Integer(9)};
  Integer result = (Integer) client.execute("Calculator.add", params);

JSON RPC

Ekvivalent XML-RPC pomocí zpráv ve formátu JSON.


in: {"id":"-7784003634935858885","jsonrpc":"2.0","method":"mojeMetoda","params":[1 , "řetězec", true, {"i":99,"s":"kůň","b":false,"f":0.2098023224,"list":["D","E","F"],"map":{"březen":"March"}}] }
out: {"jsonrpc":"2.0","id":"-7784003634935858885","result":{"i":10,"s":"příliš žlutoučký kůň","b":true}}

SOAP/WSDL

Velké firmy nabízející tzv. enterprise řešení mají pro RPC v oblibě tzv. Web Services, čímž se myslí komunikace protokolem SOAP (kdysi znamenalo Simple Object Access Protocol, ale není ani simple, ani object, takže dneska to není zkratka) se službou s rozhraním popsaným v jazyce WSDL (Web Service Description Language).

Oproti XML-RPC mají několik výhod:

  • rozhraní služby je popsáno ve strojově zpracovatelném formátu WSDL, takže existují nástroje pro generování Java tříd z WSDL
  • umožňují vracet tzv. faults, něco jako výjimky, při nestandardních stavech
  • existují navazující standardy, např. WS-Addressing nebo WS-Security pro speciální požadavky

Detaily jsou na dlouhé povídání, např. tutoriál Web Services

V Javě existuje kromě komerčních i dost free implementací:

REST

Volání vzdálených procedur či metod je sice programátorský pohodlné, ale ukázalo se, že takto vytvořená rozhraní k systémům vyžadují přesnou shodu verzí klientů a serverů, a že špatně škálují. Systémy, které chtějí mít rozhraní použitelné v Internetovém měřítku (např. Google Calendar, Amazon , atd.) volí tzv. REST rozhraní, což je zkratka z Representional State Transfer (viz [1]).

REST je architektonický styl aplikací, v kostce jde o to, že využívá protokol HTTP s jeho metodami GET, PUT, POST a DELETE pro úpravu dat umístěných na určitých URL.

Pro přenos dat lze použít libovolný formát, populární jsou XML a JSON.

Pro implementaci v jazyce Java lze použít libovolnou knihovnu pro HTTP. Ale vcelku výhodné je použít Spring 3.x RestTemplate (javadoc pro RestTemplate). Tato knihovna umí konverzi nejběžnějších typů odpovědí (XML, JSON, plain text, ...) na příslušné objekty.

 
   RestTemplate rt = new RestTemplate();
   JsonNode l = rt.getForObject("http://nekde.cz/neco/{a}/{b}", JsonNode.class,"hodnotaA","hodnotaB");

AJAX

Rozhraní postavené na REST lze s výhodou využít při tvorbě aplikací běžících ve WWW browseru, tzv. RIA - Rich Internet Applications. Pro jeden jejich typ se ujal název AJAX (Asynchronous JavaScript And XML), i když pro přenos dat se obvykle nepoužívá XML, ale právě JSON.

OAuth

Hlavní článek OpenID a OAuth.

OAuth umožňuje bezpečnou spolupráci nad uživatelovými daty mezi více webovými servery, případně mezi mobilní aplikací a webovým serverem. Je to nejpoužívanější řešení pro autorizaci aplikací třetích stran na daty u Google, Facebooku, Twitteru a podobně.