luni, 12 ianuarie 2015

SCD

Un protocol de rețea este, într-o rețea de telecomunicații, o descriere formală a regulilor și convențiilor care stau la baza comunicării între dispozitivele atașate la rețea. Protocolul determină formatul sau structura mesajului, metodele prin care dispozitivele din rețea schimbă informații privitoare la căile către alte rețele, temporizarea, ordinea și controlul erorilor în comunicațiile de date, inițierea și finalizarea sesiunii pentru transferul de date și altele.
Fără protocoale calculatoarele nu ar putea construi sau reconstrui în formatul original șirul de biți (mesajul) transmis de la un alt calculator.
Protocoalele controlează toate aspectele comunicațiilor de date, incluzând:
·        Cum e construită fizic rețeaua?
·        Cum sunt conectate între ele calculatoarele din rețea?
·        Cum sunt formatate datele pentru transmitere?
·        Cum sunt trimise datele?
·        Ce se întâmplă când apar erori și cum se pot corecta erorile?


      Diferenţe între UDP şi TCP

UDP reprezintă un protocol mai relaxat decât TCP iar o comparaţie între caracteristicile de bază ale celor două protocoale va scoate în evidenţă acest aspect:

TCP
UDP
Fiabil: TCP gestionează confirmări de mesaje, retransmisii şi timeout-uri. În cazul în care nu mai este posibilă livrarea unui mesaj va fi resetată întreaga conexiune.
Nefiabil: la trimiterea unui mesaj nu se oferă nici o garanţie în ceea ce priveşte livrarea acestuia cu succes. Nu există conceptele de confirmări de mesaje, retransmisii şi timeout-uri.
Ordonat: dacă două mesaje sunt trimise pe o conexiune într-o anumită ordine atunci aplicaţie de la celălalt capăt le va primi în aceiaşi ordine. În cazul în care mesajele ajung în altă ordine, TCP aplică mesanisme de resecvenţiere.
Neordonat: dacă două mesaje sunt trimise către acelaşi destinatar, nu există nici o garanţie privind ordinea în care sunt recepţionate şi nici mecanisme de resecvenţiere în cazul în care sosesc în altă ordine.
Încărcare mare: există o încărcare mare cu informaţii de control – sunt necesare trei mesaje iniţiale numai pentru stabilirea conexiunii, sunt de asemenea implementaţi algoritmi de control al fluxului şi al congestiei.
Încărcare mică: nu există nici o încărcare cu informaţii de control – nici un fel de gestiune a secvenţierii, nici un fel de infprmaţie de control al conexiunii.
Flux de date: datele sunt văzute ca un flux continuu, fără a exista informaţii implicite cu privire la locul unde începe sau se sfârşeşte un pachet. Pachetele pot fi fragmentate în bucăţi mai mari sau mai mici în mod arbitrar.
Datagrame: pachetele sunt trimise ca entităţi individuale şi se garantează că vor fi livrate nefragmentat. Pachetele au graniţe bine determinate şi nu există posibilitatea de a fi fragmentate şi recombinate în fluxuri de date.

Clienţii care comunică prin intermediul TCP, utilizând socket-uri, au un canal dedicat, iar transmisia datelor este sigură. Datele sunt recepţionate în ordinea în care acestea au fost trimise.

In contrast, când datele sunt transmise prin UDP, ajungerea acestora la destinaţie nu este garantată, de asemenea, ordinea de sosire la destinaţie a datagramelor poate să difere de ordinea în care acestea au fost transmise.

Avantajul lucrului cu datagrame este creşterea vitezei cu care pachetele (datagramele) ajung la destinaţie. Există cazuri în care viteza de transmisie a datelor este mai importantă decât garantarea 100% a ajungerii acestora la destinaţie. De exemplu în cazul transmiterii unui semnal audio în timp real, viteza de transmitere a acestuia este mai importantă decât garantarea ajungerii la destinaţie.

In java pentru implementarea protocolului UDP sunt utilizate clasele: DatagramPacket şi DatagramSocket. Spre deosebire de programarea TCP, în cazul UDP nu există conceptul de ServerSocket. Atât serverul cât şi clientul utilizează DatagramSocket pentru realizarea conexiunii. Pentru transmiterea şi recepţionarea datelor se utilizează clasaDatagramPacket.
URL este acronimul de la Uniform Resource Locator (de asemenea este şi numele unei clase java). Un URL este unpointer către o resursă din Internet.

O bază de date reprezintă o modalitate de stocare persistentă a informaţiilor pe un suport fizic cu posibilitate de regăsire a acestora ulterior. Cel mai cunoscut model de baze de date este cel relaţional în care datele sunt memorate sub formă de tabele. Bazele de date relaţionale mai conţin pe lângă tabele funţii şi proceduri, mecanisme de gestionare a utilizatorilor, tipuri de date, etc.

Printre cei mai cunoscuţi producători de baze de date se numără: Oracle, Microsoft, Sybase, IBM.
Accesarea unei baze de date folosind JDBC este simplă şi implică următorii paşi:
  1. Obţinerea unui obiect de tip Connection ce încapsulează conexiunea la baza de date (în acest pas se realizează deci conexiunea la baza de date).
  2. Obţinerea unui obiect Statement dintr-un obiect de tip Connection. Acest obiect este folosit pentru a transmite spre execuţie comenzi SQL către baza de date. Prin intermediul acestui obiect sunt efectuate operaţii de interogare şi modificare a bazei de date.
  3. Obţinerea unui obiect ResultSet dintr-un obiect Statement. Obiectul ResultSet încapsuleaz rezultatele operaţiilor de interogare.
  4. Procesarea rezultatelor încapsulate în obiectul ResultSet.


2. Noţiuni preliminare(Socket TCP)

Calculatoarele conectate in reţea comunică  între ele utilizând protocoalele TCP (Transport Control Protocol) şi UDP (User Datagram Protocol) conform diagramei:

Description: http://control.aut.utcluj.ro/scd/lab2/laborator2_1_files/image001.gif
Figura 1. Nivelele de omunicare în reţea
Pentru realizarea unor programe care comunică in reţea în java, se utilizează clasele din pachetul java.net . Acest pachet oferă clasele necesare pentru realizarea unor programe de reţea independente de sistemul de operare.

In tabelul următor sunt prezentate principalele clase care sunt utilizate pentru construirea unor programe de reţea.

Class
Scop
URL
Reprezintă un URL
URLConnection
Returnează continutul adresat de obiectele URL
Socket
Crează un socket TCP
ServerSocket
Crează un socket server TCP
DatagramSocket
Crează un socket UDP
DatagramPacket
Reprezintă o datagrama trimisă printr-un obiect DatagramSocket
InetAddress
Reprezintă numele unui pc din reţea, respectiv IP-ul corespunzător

Java oferă două abordări diferite pentru realizarea de programe de reţea. Cele două abordări sunt asociate cu clasele:
-          Socket, DatagramSocket şi ServerSocket
-          URL, URLEncoder şi URLConnection

Programarea prin socket-uri reprezintă o abordare de nivel jos, prin care, două calculatoare pot fi conectate pentru a realiza schimb de date. Ca principiu de baza, programarea prin socketuri face posibilă comunicarea în mod full-duplex între client şi server. Comunicarea se face prin fluxuri de octeţi.

Pentru ca comunicarea  se desfăşoare corespunzător, programatorul va trebui să implementeze un protocol de comunicaţie (reguli de dialog), pe care clientul şi serverul îl vor urma.

Identificare unui calculator în reţea

Orice calculator conectat la Internet este identificat in mod unic de adresa sa IP (IP este acronimul de la Internet Protocol). Aceasta reprezinta un numar reprezentat pe 32 de biti, uzual sub forma a 4 octeti, cum ar fi de exemplu:193.226.5.33 si este numit adresa IP numerică. Corespunzătoare unei adrese numerice exista si o adresa IP simbolica, cum ar fi utcluj.ro. 
De asemenea fiecare calculator aflat într-o reţea locala are un nume unic ce poate fi folosit la identificarea locala a acestuia.

Clasa Java care reprezinta notiunea de adresa IP este InetAddress. Pentru a construi un obiect se foloseşte comanda:

InetAddress address =InetAddress.getByName("121.3.1.2");

Pentru a vedea toate modurile în care pot fi construite obiecte de tip InetAddress studiaţi documentaţia acestei clase.

Un calculator are în general o singura legătura fizica la reţea. Orice informaţie destinata unei anumite maşini trebuie deci sa specifice obligatoriu adresa IP a acelei maşini. Insa pe un calculator pot exista concurent mai multe procese care au stabilite conexiuni în reţea, asteptând diverse informaţii. Prin urmare datele trimise către o destinaţie trebuie sa specifice pe lângă adresa IP a calculatorului si procesul catre care se îndreaptă informaţiile respective. Identificarea proceselor se realizează prin intermediul porturilor.

Orice aplicaţie care comunică în reţea este identificată în mod unic printr-un port, astfel încât pachetele sosite pe calculatorul gazdă să poată fi corect rutate către aplicaţia destinaţie.

Valorile pe care le poate lua un număr de port sunt cuprinse între 0 si 65535 (deoarece sunt numere reprezentate pe 16 biţi), numerele cuprinse între 0 si 1023 fiind însă rezervate unor servicii sistem, si, din acest motiv, nu se recomandă folosirea acestora.
           
Definitia socket-ului: Un socket reprezintă un punct de conexiune într-o reţea TCP\IP. Când două programe aflate pe două calculatoare în reţea doresc să comunice, fiecare dintre ele utilizează un socket. Unul dintre programe (serverul) va deschide un socket si va aştepta conexiuni, iar celălalt program (clientul), se va conecta la server şi astfel schimbul de informaţii poate începe. Pentru a stabili o conexiune, clientul va trebui să cunoască adresa destinaţiei ( a pc-ului pe care este deschis socket-ul) şi portul pe care socketul este deschis.
           
Principalele operaţii care sunt facute de socket-uri sunt:
- conectare la un alt socket
- trimitere date
- recepţionare date
- inchidere conexiune
- acceptare conexiuni

3. Aplicaţie client-server cu server monofir

             
Pentru realizarea unui program client-server se utilizează clasele ServerSocket şi Socket.

Programul server va trebui să deschidă un port şi să aştepte conexiuni. In acest scop este utilizată clasă ServerSocket. In momentul în care se crează un obiect ServerSocket se specifică portul pe care se va iniţia aşteptarea. Inceperea ascultării portuli se face apelând metoda accept(). In momentul în care un client s-a conectat, metoda accept() va returna un obiect Socket.

La rândul său clientul pentru a se conecta la un server, va trebui să creeze un obiect de tip Socket, care va primi ca parametri adresa serverului şi portul pe care acesta aşteaptă conexiuni.

Atât la nivelul serverului cât şi la nivelul clientului, odată create obiectele de tip Socket, se vor obţine fluxurile de citire şi de scriere. In acest scop se utilizeaza metodele getInputStream() şi getOuptuStream().

In listingul următor este prezentat programul server.

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

public class ServerSimplu {
  public static void main(String[] args) throws IOException{

    ServerSocket ss=null;
    Socket socket=null;
    try{
      String line="";
      ss = new ServerSocket(1900); //creaza obiectul serversocket
      socket = ss.accept(); //incepe asteptarea peportul 1900
      //in momentul in care un client s-a  conectat ss.accept() returneaza
      //un socket care identifica conexiunea

      //creaza fluxurile de intrare iesire
      BufferedReader in = new BufferedReader(
            new InputStreamReader(socket.getInputStream()));

      PrintWriter out = new PrintWriter(
            new BufferedWriter(new OutputStreamWriter(
              socket.getOutputStream())),true);

      while(!line.equals("END")){
        line = in.readLine(); //citeste datele de la client
        out.println("ECHO "+line); //trimite date la client
      }

    }catch(Exception e){e.printStackTrace();}
     finally{
      ss.close();
      if(socket!=null) socket.close();
     }
  }
}


Programul client este prezentat în listingul următor:

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

public class ClientSimplu {

  public static void main(String[] args)throws Exception{
    Socket socket=null;
    try {
    //creare obiect address care identifica adresa serverului
      InetAddress address =InetAddress.getByName("localhost");
    //se putea utiliza varianta alternativa: InetAddress.getByName("127.0.0.1")
   socket = new Socket(address,1900);

      BufferedReader in =
        new BufferedReader(
          new InputStreamReader(
            socket.getInputStream()));
      // Output is automatically flushed
      // by PrintWriter:
      PrintWriter out =
        new PrintWriter(
          new BufferedWriter(
            new OutputStreamWriter(
              socket.getOutputStream())),true);

      for(int i = 0; i < 10; i ++) {
        out.println("mesaj " + i);
        String str = in.readLine(); //trimite mesaj
        System.out.println(str); //asteapta raspuns
      }
      out.println("END"); //trimite mesaj care determina serverul sa inchida conexiunea

    }
    catch (Exception ex) {ex.printStackTrace();}
    finally{
      socket.close();
    }
  }
}


Pentru verificare se va starta serverul, după care se va starta clientul. 

 6. Trimiterea obiectelor prin socket-uri


Mecanismul de serializare pune la dispoziţia programatorului o metodă prin care un obiect poate fi salvat pe disc şi restaurat atunci cand este nevoie. Tot prin acelaşi mecanism un obiect poate fi transmis la distanta catre o altă maşină utilizând socketurile.    

Pentru a putea serializa un obiect acesta va trebui să implementeze interfaţa Serializable.

Pentru scrierea şi citirea obiectelor serializate se utilizează fluxurile de intrare / ieşire : ObjectInputStream si ObjectOutputStream.

Listingul următor prezintă modul in care se poate serializa / deserializa un obiect.

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

public class SerialTest extends Thread{

      public void run(){

      try{
        ServerSocket ss = new ServerSocket(1977);
        Socket s = ss.accept();
        ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
        Pers p = (Pers)ois.readObject();
        System.out.println(p);
        s.close();
        ss.close();
      }catch(Exception e){e.printStackTrace();}

      }

  public static void main(String[] args) throws Exception{

        //trimite obiect prin socket
        (new SerialTest()).start();
      
        Socket s = new Socket(InetAddress.getByName("localhost"),1977);
        ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
        Pers p = new Pers("Alin",14);
        oos.writeObject(p);
        s.close(); 

  }
}

class Pers implements Serializable{
  String nume;
  int varsta;

  Pers(String n, int v){
    nume = n; varsta = v;
  }

  public String toString(){
    return "Persoana: "+nume+" vasrta: "+varsta;
  }
}

Programarea în reţea – Datagrame şi Url-uri

3. Server de timp (UDP)


In cadrul acestei secţiuni este construit un server de timp care va trimite la cerere data curentă către clienţii care solicită acest lucru. De asemenea este construit şi clientul care accesează serviciile serverului de timp.

La nivelul serverului se creează un obiect DatagramSocket, care va primi ca parametru portul pe care serverul va începe ascultarea.

            DatagramSocket socket = new DatagramSocket(1977);

In continuare se construieşte un obiect DatagramPacket, care va fi utilizat de către server pentru a recepţiona cererea de la client. O dată construit obiectul DatagramPacket, serverul va începe ascultarea portului 1977, prin invocarea metodei receive().

            byte[] buf = new byte[256];
            DatagramPacket packet = new DatagramPacket(buf,buf.length);
            socket.receive(packet);

In momentul în care un client doreşte să apeleze la serviciile serverului, acesta va trimite un pachet către server. Serverul citeşte din cadrul pachetului portul şi adresa clientului, şi îi va trimite acestuia un pachet ce conţine data curentă.

            InetAddress address = packet.getAddress();
            int port = packet.getPort();
             buf = ((new Date()).toString()).getBytes();
 packet = new DatagramPacket(buf,buf.length,address,port);
 socket.send(packet);
           
Un client, pentru a se conecta la server, trebuie să creeze un obiect DatagramSocket, şi să trimită un pachet către server. Spre deosebire de server, clientul nu este obligat să specifice nici un port în momentul creierii obiectuluiDatagramSocket, întrucât se atribuie automat un port liber respectivului obiect.

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

public class TimeServer extends Thread{

  boolean running=true;
  public TimeServer() {start();}

  public void run(){
  try{
    DatagramSocket socket = new DatagramSocket(1977);
    while(running){
      //asteapta client
      byte[] buf = new byte[256];
      DatagramPacket packet = new DatagramPacket(buf,buf.length);
      socket.receive(packet);
      //citeste adresa si portul clientului
      InetAddress address = packet.getAddress();
      int port = packet.getPort();
      //trimite un reply catre client
      buf = ((new Date()).toString()).getBytes();
      packet = new DatagramPacket(buf,buf.length,address,port);
      socket.send(packet);
   }
    }catch(Exception ex){ex.printStackTrace();}
  }
  public static void main(String[] args) {
    TimeServer timeServer1 = new TimeServer();
  }
}

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

public class Client {

  public static void main(String[] args) {
  try{
    DatagramSocket socket = new DatagramSocket();
    byte[] buf = new byte[256];

    DatagramPacket packet = new DatagramPacket(buf,buf.length,
      InetAddress.getByName("localhost"),1977);
    socket.send(packet);
    packet = new DatagramPacket(buf,buf.length);
    socket.receive(packet);

    System.out.println(new String(packet.getData()));
  }catch(Exception ex){ex.printStackTrace();}
  }
}

4. Lucrul cu URL-uri


In această secţiune este prezentat modul de lucru în java cu URL-uri.
URL este acronimul pentru Uniform Resource Locator si reprezinta o referinta (adresa) la o resursa aflata pe Internet. Aceasta este în general un fisier reprezentând o pagina Web sau o imagine, însa un URL poat referi si interogari la baze de date, rezultate ale unor comenzi (programe), etc.

Pentru a accesa o resursă din Internet identificată printr-un URL, în java, primul pas este de a crea un obiect URL.

            URL utcn = new URL(“www.utcluj.ro”);

Clasa URL conţine metode prin intermediul cărora se pot afla toate componentele unui URL: getProtocol(), getPort(),getHost(), getFile(), getRef().

Odată creat obiectul URL, se utilizează metoda openStream() pentru a deschide un flux de intrare, prin intermediul căruia se citeşte conţinutul respectivului URL.
               BufferedReader in = new BufferedReader(
                                                             new InputStreamReader(
                                                             utcn.openStream()));
               String inputLine;
               while ((inputLine = in.readLine()) != null)
                   System.out.println(inputLine);
               in.close();
 


                                                                                   Baze de date                                                                                

Realizarea conexiunii

Pentru realizarea conexiunii cel mai important parametru este URL-ul bazei de date.  URL-ul identifică baza de date la care urmează să se realizeze conexiunea.

Exemplu:
                        jdbc:mysql://localhost:3306/contacts/
                        jdbc:odbc:people
            URL-ul conţine următoarele componente:
  1. Componenta care specifică utilizarea driverului jdbc.
  2. Componenta care specifică mecanismul de conectare la baza de date.
  3. Identificatorul bazei de date. Acesta reprezintă un identificator – un nume logic – care este mapat de către softul de administrare al bazei de date la baza de date fizică.

Următorul cod realizează conexiunea cu baza de date:

Connection con = DriverManager.getConnection(url,"login", "password");


public class TestConectare {

    public static void main(String[] args) {
            try {
                //incarcare driver petru baza de date
                Class.forName("com.mysql.jdbc.Driver");
               
                //conectare la baza de date
              Connection conn = DriverManager.getConnection("jdbc:mysql://10.3.4.1/persoane?user=student&password=pas123”);
                System.out.println("Conexiune la baza de date realizata.");
               
                //inchide cnexiune la baza de date
                conn.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
   }
}          

4. Operatii asupra unei baze de date


Pentru a putea efectua operaţii asupra unei baze de date la care s-a realizat conexiunea se lucrează cu un obiect Statement. Acest obiect se obţine din cadrul obiectului Connection astfel:
            Statement stat = con.createStatement();

Din acest moment obiectul ‘stat’ va fi utilizat pentru a efectua operaţii asupra bazei de date.

Creare tabel

Se utilizează metoda executeUpade() care va primi ca parametru un String care reprezintă o comandă SQL validă pentru crearea unui tabel.
           
stat.executeUpdate(“CREATE TABLE PERS (NUME VARCHAR(32), VARSTA INTEGER)”);

Introducere date în tabel

Pentru adăugarea de înregistrări în cadrul unui tabel se utilizează aceiaşi metodă executeUpdate().

stat.executeUpdate(“INSERT INTO PERS VALUES(‘ADI’, 15)”);   

Citirea conţinutului unui tabel   

Pentru citirea conţinutului unui tabel se utilizeză metoda executeQuery() a clasei Statement.

ResultSet result = stat.executeQuery("SELECT PROD,PRET FROM STOC");       

Rezultatul interogării bazei de date va fi returnat într-un obiec de tip ResultSet. Pentru a parcurge , linie cu linie, a rezultatelor interogării se realizează utilizând metoda next() din cadrul obiectului ResultSet.
           
In cadrul programului următor este prezentat exemplu care realizează operaţiile de creare de tabel, inserare de înregistrări şi interogare a unui tabel.

import java.sql.*;

public class JdbcTest {

  public static void main(String[] args) {
    try{

      Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
      Connection con = DriverManager.getConnection("jdbc:odbc:db1");
      Statement stat = con.createStatement();

      stat.executeUpdate("CREATE TABLE STOC (PROD VARCHAR(32), PRET INTEGER)");

      stat.executeUpdate("INSERT INTO STOC VALUES ('PROD1' , 2500)");
      stat.executeUpdate("INSERT INTO STOC VALUES ('PROD2' , 7900)");

      ResultSet result = stat.executeQuery("SELECT PROD,PRET FROM STOC");

      while(result.next()){
       String prod = result.getString(1);
       int pret = result.getInt(2);

       System.out.println("Produs = "+prod+"   "+"Pret = "+pret);
      }
    }
    catch(Exception e){e.printStackTrace();}

  }
}          

                       

5. Alte operaţii cu baze de date


Utilizarea clasei PreparedStatement

Clasa PreparedStatement extinde clasa Statement. Spre deosebire de Statement, un obiect PreparedStatemtn primeşte o comandă SQL în momentul în care este creat. Un obiect PreparedStatement reprezintă o comandă SQL precompilată. Această clasă se utilizează in momentul în care se doreşte executarea unei comenzi de mai multe ori, intrucât cresc performanţele (viteza de execuţie) , deoarece comanda SQL este precompilată în momentul creerii obiectului PreparedStatement.

      PreparedStatement ps = con.prepareStatement("INSERT INTO STOC VALUES(?,?)");
      ps.setString(1,"PROD 3");ps.setInt(2,6000);
      ps.executeUpdate();
      ps.setString(1,"PROD 4");ps.setInt(2,9000);
      ps.executeUpdate();

Utilizarea procedurilor stocate

Procedurile stocate reprezintă un set de comenzi SQL (comenzi de updatare şi / sau interogare) care se vor executa împreună. Procedurile stocate sunt suportate de majoritatea DBMS-urilor dar pot apărea variaţii în ceea ce priveşte sintaxa.

In continuare este prezentat modul în care se creeză o procedură stocată utlizând JDBC.

      String pstoc="CREATE PROCEDURE DISP_TABLE"+
      "AS "+
      "SELECT PROD, PRET FROM STOC";

      Statement st = con.createStatement();
      st.executeUpdate(pstoc);
           
In acest moment procedura DISP_TABLE va fi compilată şi stocată în cadrul bazei de date, ea putând fi apelată ori de câte ori este nevoie.

Executarea unei proceduri stocate se realizează astfel:
      CallableStatement cs = con.prepareCall("{call DISP_TABLE}");
      ResultSet result = cs.executeQuery();