Jak naprogramovat internetovou komunikaci?
Co nám nabízejí některé programátorské nástroje? • „nižší úroveň programování“ – programování pomocí tzv. „socketů“ • UNIX, Linux, Windows, Python, JAVA
• „vyšší úroveň“ – zapouzdření služeb do tříd v rámci objektově orientovaného programování • JAVA, .NET (Microsoft), Python
Rozhraní socketů Je sice nejstarší, ale stále se používá…
Rozhraní socket BSD (Berkley Socket Distribution) • jde o standardní softwarové rozhraní pro zasílání dat pomocí rodiny IP protokolů • vymyšleno a poprvé implementováno na univerzitě v Berkley do OS UNIX • umožňuje programátorům používat TCP/IP, resp. UDP, obdobným způsobem, jako se pracuje se soubory
Charakteristika • poprvé implementováno v jazyce C • implementace do operačních systémů – UNIX (Linux) - knihovna socket.h – MS Windows – knihovna winsock.h
• programátorské funkce jsou navrženy tak, aby bylo snadné naprogramovat aplikaci klient -server
Princip práce se soubory v jazyce C • nejprve se soubor otevře; informace o otevřeném souboru se uloží do speciální datové struktury OS (file descriptor) – zde proměnná f (ukazatel na typ FILE*) f = fopen(″text.doc″, ″rb″)
jméno souboru
mód otevření r – čtení, w – zápis b – binární mód
• ze souboru čteme data pomocí funkce fread(…), zapisujeme data pomocí funkce fwrite(…) – zde v ukázce např. do pole data přečteme 10 bloků po 1 bytu ze souboru f, který jsme předtím otevřeli fread(data,1,10,f)
• po skončení práce „uzavřeme“ soubor voláním funkce fclose(…) fclose(f)
• návrháři přenesli tento jednoduchý princip do programování komunikace sítí Je to ale přece o trochu složitější … • programujeme aplikaci klient/server – server čeká na příchod požadavku klienta
• TCP je orientované na spojení – funkce navazující spojení
• musíme znát mnoho údajů – IP adresu vlastní a partnera, porty
Co je to socket? • učeně – koncový komunikační uzel (endpoint)
• méně učeně – datová struktura (data), nesoucí informaci o stavu síťového spojení; • musíme jej vytvořit voláním speciální funkce podobně jako fopen pro otevření souboru • pak komunikujeme pomocí funkcí write() a read(), jejichž parametr je otevřený socket (kam se posílají data)
Základní funkce rozhraní • socket() – vytvoří strukturu socket – používá jej klient i server – argumentem je rodina a typ protokolu (TCP,UDP), typ služby (spojovaná/nespojovaná) – vrací identifikaci vytvořeného socketu
• close() – dealokuje (zavírá) socket
Funkce používané serverem • bind() – připojí do socketu identifikaci lokálního uzlu, tj. „moji“ IP adresu a port
• listen() – spojovaná služba – „pasivuje“ socket, tj. nastaví jej do stavu čekání na příchod požadavků klienta (přes rezervovaný port)
• accept() - spojovaná služba – přijde-li požadavek, accept vytvoří nový socket, pomocí něhož server komunikuje s klientem; původní socket je nadále v pasivním stavu a očekává nový požadavek (na rezerv. portu)
Funkce používané klientem • connect() - spojovaná služba – vytvoří spojení ke vzdálenému serveru • v případě TCP – 3-fázový potvrzovací protokol
– parametrem je adresa a port vzdáleného serveru – po vytvoření spojení lze zahájit výměnu dat se serverem
Funkce přenosu dat • read(), write() - spojovaná služba • recvfrom(), recvmsg • sendto(), sendmsg() – čtení a zápis dat „do socketu – recvfrom zaznamená i příjemce
Spojované služby Klient
Server
socket
socket bind
connect listen write
accept
read
read write
close close
Nespojované služby Klient
Server
socket
socket bind
connect write read
recvfrom recvmsg sendto sendmsg
close close
Konkurentní TCP server Aplikace
Server (proces) Vlákno (syn)
rezervovaný port
Vlákno (syn)
individuální sockety a porty
Operační systém
Ukázky, jak se to programuje … 1. Vytvoření socketu
s=socket(AF_INET,SOCK_STREAM,TCP)
rodina protokolů
spojovaná služba
protokol TCP
Ukázky, jak se to programuje … 2. Bind – server: vložení vlastní IP adresy a portu k socketu • naplním datovou strukturu pro adresu a port port, kde addr.sin_family = AF_INET; poslouchám addr.sin_port = htons(3434); addr.sin_addr.S_un.S_addr = INADDR_ANY; bind(s,(sockaddr*)&addr,sizeof(addr)) moje adresa
Ukázky, jak se to programuje … 3. listen – server: čeká na příchod požadavku
listen (s,SOMAXCON);
socket
max. délka fronty
Ukázky, jak se to programuje … 4. listen – server: po příchodu požadavku vytvoří nový socket pomocí accept a vytvoří nový proces, který dále komunikuje
//vytvoření nového procesu sx=accept (s, adresa,délka); read(sx,data,vel); write(sx,data,vel); nový socket
• algoritmičtěji listen (s,SOMAXCON); while (není_konec) { SOCKET sx = accept(s,adr,delka); fork() //vytvoření syn. procesu { read(s,data,vel); write(s,data,vel); } }
• k dispozici je mnoho pomocných funkcí – pro získání informací o počítači: • gethostbyname() • GetAddressByName - ve winsock2.h
– čtení a změna parametrů socketu • getsockopt(), setsockopt()
– převodní funkce • getservbyport() – zjistí informace o serveru, např. voláme z řetězcem FTP, vrátí port 21
Jednoduchý příklad • klient, který pošle serveru v TCP paketu jméno souboru • server pošle obsah souboru v TCP paketu – první slabika v paketu odpovědi signalizuje hodnotou -1, že soubor se nepodařilo otevřít
• příklad je značně zjednodušen – předpokládá se, že soubor se vejde do jediného paketu o max. 100 bytech
Klient specialita WIN – nutnost int Init_DLL(WORD version) inicializovat winsock.dll { WSADATA wsaData; return WSAStartup(version, &wsaData); }
SOCKET Create_tcp_socket(void) { SOCKET s; s = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP); return s; } int Set_sock_rcv_timeout(SOCKET s, int milisec) { return setsockopt(s, SOL_SOCKET,SO_RCVTIMEO,(char *)&milisec,sizeof(milisec)); }
if ((s=Create_tcp_socket())==INVALID_SOCKET) { printf("Nepodarilo se vytvorit socket."); getchar(); return 2; } err=Set_sock_rcv_timeout(s,10000); ZeroMemory((void*)&addr,sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(4242); addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); printf("Spojuji se se serverem...\n"); if (connect(s,(sockaddr*)&addr,sizeof(addr))!= 0) { printf("Spojeni se nepodarilo navazat...\n"); closesocket(s); return 2; }
send(s,"text.txt",9,0); printf("Cekam na data\n"); int prijato; addr_len = sizeof(addr); if ((prijato=recv(s,paket,100,0))>0) { if (paket[0]==-1) { printf("Server soubor nenalezl"); break; } else { for(int i=1;i<prijato;i++) putchar(paket[i]); } } closesocket(s); WSACleanup();
Server ZeroMemory((void*)&addr,sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(4242); addr.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(s,(sockaddr FAR *)&addr,sizeof(addr))!=0) { printf("Nepodarilo se provest bind"); closesocket(s); WSACleanup(); return 2; }
printf("Pasivuji socket...\n"); if(listen(s,10)==SOCKET_ERROR) { printf("Nepodarilo se vytvorit frontu"); closesocket(s); WSACleanup(); return 2; } addr_len=sizeof(addr_from); novy_s = accept(s,(sockaddr*)&addr_from,&addr_len); printf("Prisel pozadavek...\n"); recv(novy_s,jmeno,256,0); printf("Pozadavek na soubor %s\n",jmeno); // načtení souboru send(novy_s,packet,celkem_precteno+1,0);
Princip socketů je stále využíván i v nových programátorských prostředcích!
Programování pomocí OOP (objektově orientovaného programování)
JAVA • sockety, třídy pro práci s IP adresou atd.
Microsoft • MFC – objektové prostředí pro tvorbu aplikací ve Windows • pro síťové poskytuje pouze zapouzdřený socket jako CSocket
• .NET – definovány třídy na vyšší úrovni (TCPClient)
Co je OOP? • počátky programování – strukturované programování – posloupnost operací (příkazů) nad relativně jednoduchými daty (matematické výpočty)
• svět se skládá z objektů, které mají určité vlastnosti; objekty mají mezi sebou nějaké vztahy, „něco se s nimi děje“
• v OOP – základní datový prvek objekt – sdružuje (zapouzdřuje) data (tzv. atributy) atributy a funkcionalitu (operace nad daty – metody) metody • definice podoby objektu se nazývá třída (class) – termínem třída se také někdy označuje souhrn objektů stejných vlastností
• některé objektové programovací jazyky: C++, Java, SmallTalk, Delphi (objektový Pascal)
Příklad (v jazyce C++): // definice struktury adresa typedef struct { char Ulice[20]; //typ retezec int Cislo; char Mesto[20]; } Adresa;
// definice třídy osoba class Osoba { Atribut char Jmeno[20]; //atributy strukturovaného char Prijmeni[30]; datového typu char RC[12]; Adresa adresa; Odkaz (ukazatel) na int Pocet_deti; jiný objekt Osoba* deti[5]; (zde pole 5 ukazatelů) int Ma_narozeniny(); void Pridej_dite(Osoba *dite); Osoba(); Metody }; Konstruktor
• konstruktor – inicializuje objekt Osoba::Osoba { Pocet_deti = 0; }
• příklad definice metody: void Osoba::Pridej_dite(Osoba* dite) { deti[Pocet_deti] = dite; Pocet_deti = Pocet_deti + 1; }
• program obsahuje – deklaraci objektů // zde deklarace dvou objektů typu osoba, // konstruktor je automaticky vyvolán Osoba zamest, dite; // staticky Osoba *sef = new Osoba(); //dynamicky
– kód - operace s atributy, volání metod //POZOR, následující operace přiřazení řetězců nejsou v jazyku C správně, zápis je jen pro ilustraci
zamest.Jmeno = "Josef"; zamest.Prijmeni = "Novák"; zamest.adresa.Ulice = "Hradební"; dite.Jmeno = "Markéta"; dite.Prijmeni = "Nováková"; zamest.Pridej_dite(&dite); zamest.Ma_narozeniny(); atd.
V OOP se využívají některé další techniky: • dědičnost – nově definovaná třída dědí atributy a metody po předcích; metoda může být předefinována
• řízení přístupu k atributům a metodám – private – proměnnou nebo metodu nelze použít mimo objekt
• přetěžování – např. více metod se stejným jménem a různým kódem lišící se parametry
• strukturované ošetření chyb – výjimky – při chybě se „vyhodí“ výjimka; výjimky se ošetřují v kódu na jednom místě (na dané úrovni)
Princip používání výjimek try { kód if (chyba) throw Vyjimka_sit } catch Vyjimka_sit { reakce na chyby obsloužení } vyjímky
vznik výjimky
JAVA
Síťová komunikace v JAVě • všechny třídy týkající se sítě jsou soustředěny v knihovně (balíčku) java.net • tříd a „přidružených definicí“ je mnoho: – asi 36 tříd týkajících se komunikace – 12 souvisejících výjimek – 6 rozhraní (interface)
• většina „původní složité“ komunikace pomocí socketů je zjednodušena díky proudům
Balík java.net obsahuje především: • třídu pro adresaci – InetAddress, URL
• třídy pro TCP komunikaci – URLConnection, Socket, ServerSocket
• třídy pro UDP komunikaci – DatagramPacket, DatagramSocket
• výjimky – ProtocolException, SocketException, ConnectException, PortUnreachableException, UnknownHostException,
Třída InetAddress • objekty nesou IP adresu, tj. 32 resp. 128 bitové číslo bez znaménka • implementuje IPv4 i IPv6 – má potomky Inet4Address a Inet6Address
• adresa může být typu unicast nebo multicast – tj. paket se má dodat na jedno či více míst
• nemá přímo přístupné konstruktory, objekty jsou vraceny statickými funkcemi
Metody • InetAddress getByName( String host ) – adresa hostitele dle DNS.
• InetAddress[ ] getAllByName( String host ) – adresy hostitele dle DNS.
• InetAddress getByAddress ( byte[ ] addr ) – vytvoří objekt podle adresy zapsané v normálním poli
• InetAddress getLocalHost( ) – adresa lokálního hostitele, tj. moje
• byte [ ] getAddress( ) – adresa po bytech.
• String getHostAddress( ) – textová reprezentace adresy hostitele.
• String getHostName( ) – jméno hostitele
Příklad: InetAddress a=InetAddress.getByName("www.seznam.cz"); byte[ ] b=a.getAddress( ); // rozklad na slabiky System.out.println( b[0] + "." +b[1] +"."+ b[2] +"."+ b[3] );
Třída URL • nese informace o lokalitě zdroje • přetížené konstruktory umožňují vytvořit URL jednak pomocí plné specifikace řetězem, jednak pomocí jednotlivých částí URL • při nesprávném zadání vyhodí výjimku MalformedURLException.
Metody • getProtocol( ), getHost( ), getFile( ), ... – umožňují vybírat části URL.
• URLConnection openConnection( ) – vrací objekt typu URLConnection, který reprezentuje spojení (např. komunikaci pomocí http) se vzdáleným objektem, identifikovaným daným URL
Třída URLConnection • abstraktní třída reprezentující komunikační kanál mezi aplikací a URL – třídy (potomci) HttpURLConnection a HttpsURLConnection podporují přenos HTML stránek protokolem http
• parametrem konstruktoru je URL vzdáleného počítače • spojení se navazuje metodou connect • vlastní přenos dat se realizuje pomocí proudů (Stream)
Ukázka:
URL url = new URL("http://www.seznam.cz"); URLConnection connection = url.openConnection(); connection.connect(); InputStream is = connection.getInputStream();
Třída Socket • implementuje klientský socket
Příklady konstruktorů • Socket() – vytvoří nespojený socket
Socket(InetAdress host, int port) – vytvoří socket a spojí se (!) se vzdáleným počítačem
Socket(String host, int port)
• bind(), connect() – metody známé z rozhraní BSD socket, (použití při vytváření nespojeného socketu)
• InputStream getInputStream() OutputStream getOutputStream() – vrací vstupní a výstupní proudy, které slouží pro zasílání dat v paketech
Příklad dalších metod: • getLocalAddress(), isConnected()
Příklad klienta – jednoduchý www klient try { Socket sock2 = new Socket("www.linuxsoft.cz", 80); BufferedReader br = new BufferedReader( new InputStreamReader(sock2.getInputStream())); BufferedWriter bw = new BufferedWriter( new OutputStreamWriter(sock2.getOutputStream())); bw.write(request); // zapíšeme http paket bw.flush(); String line = ""; while (line != null) { line = br.readLine(); if (line != null) System.out.println(line); } sock2.close(); }
Třída ServerSocket • implementuje funkce koncového místa TCP spojení na straně serveru – funkce souvisí jen s navazováním spojení
• vlastní přenos dat pomocí TCP je realizován třídou (Socket)
Metody • přetížené konstruktory ServerSocket – povinným parametrem je port, na který je socket vázán – dále je možné specifikovat délku fronty požadavků a omezit lokální adresu IP (má-li počítač více IP rozhraní)
• InetAddress getInetAddress() – vrací lokální IP adresu, na kterou je socket vázán
• int getLocalPort() – vrací lokální port
• Socket accept() throws IOException – čeká na příchozí volání a vrací instanci třídy Socket
• implAccept(Socket s) throw IOException – umožňuje v potomcích implementovat vlastní navázání spojení, např. pomocí SSL
• setSoTimeout(int tout), getSoTimeout() – nastavení a zjištění časových limitů
• close() – uzavření socketu
Příklad klienta - ping String s = args[1] + "\n"; byte [] message = s.getBytes(); int msglen = args[1].length(); Socket data = null; try { data = new Socket(atgs[0],port); } catch (IOException e) { System.out.println(e);System.exit(3);
}
try { OutputStream out = data.getOutputStream(); BufferedReader in = new BufferedReader( new InputStreamReader(data.getOutputStream())); out.write(message); out.flush(); String response = in.readLine(); System.out.println("Prislo: " + response); in.close(); out.close(); } catch (IOException e) { System.out.println(e); } finaly { data.close(); }
Příklad serveru - ping Socket control = null; try { control = new ServerSocket(port); } catch (IOException e) { System.out.println(e);System.exit(1); } Socket data = null; System.out.println("Cekam na prichod pozadavku\n");
try { data = control.accept(); } catch (IOException e) { System.out.println(e); break; } try { OutputStream out = data.getOutputStream(); BufferedReader in = new BufferedReader( new InputStreamReader(data.getOutputStream())); String inputLine = in.readLine(); inputLine = inputLine + "\n"; out.write(inputLine.getBytes()); out.flush(); in.close(); out.close(); } catch { … } finaly { data.close(); }
Třída DatagramPacket • třída připravuje datagramy UDP k odeslání a příjmu • DatagramPacket(byte [] ibuf, int ilen) – vytvoří paket určený pro příjem (prázdný paket)
• DatagramPacket(byte [] ibuf, int ilen, InetAddress iaddr, int port) – připraví paket určený k vysílání
• getLength(), getData(), getPort(), getAddress() – podporují analýzu paketu – význam funkcí je zřejmý
Třída DatagramSocket • představuje zapouzdřený socket pro odeslání a příjem UDP datagramů
Konstruktory: • DatagramSocket() DatagramSocket(int port) DatagramSocket(int port, InetAddress a) – socket spojený s danou lokální adresou
• send (DatagramPacket p) receive (DatagramPacket p) – odeslání a příjem UDP paketu
• close() – uzavření paketu
Poznámka: • třída MulticastSocket – umožňuje posílat přes sockety IGMP pakety
Microsoft .NET
Prostředí .NET • generace systému vývoje aplikací pro operační systémy Windows založeném na řízeném běhovém prostředí • založeno na OOP – je realizováno pomocí velkého počtu speciálních tříd
• podporuje více programovacích jazyků – C++, C#, J++, Visual Basic
TCP/UDP komunikace Třídy • IPEndPoint – práce s IP adresou a portem
• TcpClient, TcpListener – spojovaná služba
• UdpClient – nespojovaná služba
IPEndPoint • nese informaci o adrese a portu • atributy – Address, AdressFamily, Port
• zajímavé metody – ToString • převede adresu na řetězec
– Create • vytvoří objekt podle socketu
TCPClient • zapouzdřuje kompletní TCP komunikaci pro stranu klienta • zajímavé atributy – ReceiveBufferSize, ReceiveTimeout, SendBufferSize
• zajímavé metody – Connect(IPEndpoint endpoint) • spojí se se serverem na adrese dané parametrem endpoint
– NetworkStream GetStream() • vrátí objekt typu Stream, pomocí jehož metod zasíláme/přijímáme data; zasílání dat se neprovádí přímo pomocí metod této třídy
– Close() • uzavření spojení
TCPListener • zapouzdřuje kompletní TCP komunikaci pro stranu serveru • zajímavé atributy – LocalEndpoint • informace o „mé“ adrese a portu
• zajímavé metody – Start() • začíná poslouchat na portu, ekvivalentní funkci listen()
– Socket AcceptSocket(), – TcpClient AcceptTcpClient() • vrací nový socket nebo objekt typu klient, pomocí kterého se zajišťuje další komunikace
– NetworkStream GetStream() – bool Pending() • dotaz, zda není ve frontě nevyřízený požadavek na spojení
Ukázka v C# //vytvorime instanci posluchace pro urcity //TCP port TcpListener listener = new TcpListener(IPAddress.Loopback, 2000); TcpClient client = null; try { //zacneme naslouchani na urcenem portu listener.Start(); //pockame na pripojeni nejakeho klienta client = listener.AcceptTcpClient();
//po pripojeni si vyzvedneme proud a nacteme z nej data Stream clientStream = client.GetStream(); StreamReader reader = new StreamReader(clientStream); string content = reader.ReadToEnd(); } catch (Exception ex) { Console.WriteLine(ex.ToString()); }
UDPClient • zapouzdřuje kompletní UDP komunikaci; využívají jej obě strany • zajímavé metody – Send(byte[ ] data, int velikost) • zašle druhému počítači UDP paket
– byte[ ] Receive(ref IPEndPoint remoteEP ) • čeká na příchod a přijme UDP paket • data vrátí v poli bytů • vedlejší efekt je adresa, odkud paket přišel
– Connect(IPEndPoind remoteEP) • do vlastních datových struktur uloží adresu a port cíle (spojení se ale nerealizuje, je to UDP!)
Přístup k www Třída • WebClient – implementuje kompletní www klient s možností stahování dat, souborů – umožňuje • download a upload ve formě souborů z/na server • download a upload dat (tj. do pole) • přistupovat k datům na serveru přímo jako k souboru
WebClient • zajímavé atributy – BaseAddress • URI adresa serveru
• zajímavé metody – DownloadData, DownloadFile – UploadData,UploadFile – OpenRead, OpenWrite • otevře stream ke vzdálenému souboru
Ukázka v C# WebClient client = new WebClient(); Console.Write("Zadejte URI (napr. ) : "); //nacteme URI string uri = Console.ReadLine(); Console.Write("Zadejte nazev stazeneho souboru (napr. C:/new.txt) :"); //nacteme kam se ma soubor ulozit string fileName = Console.ReadLine();
try { //stahneme soubor client.DownloadFile(uri, fileName); Console.WriteLine("Soubor byl stazen."); } catch(WebException ex) { Console.WriteLine("Pri stahovani souboru doslo k vyjimce : {0}", ex.ToString()); }
Další třídy pro práci v síti • Uri – práce s řetězcem URI (porovnání, parsing)
• IPAddress • Dns – GetHostEntry, Resolve
• WebRequest, WebResponse – podle URI umí posílat/přijímat protokoly HTTP, HTTPS, FILE, FTP
Co na závěr • podporu IP sítí mají i jiné jazyky, např. Python – sockety – HTTP, FTP, SMTP