Protocolul TCP/IP

Prezentare arhitectura client server. Utilizarea tehnologiilor java pentru a construi o aplicatie client server in java.

Elementele unei retele de calculatoare

O retea de tip Local Area Network (LAN) este o retea de calculatoare interconectate intr-o zona limitata (cum ar fi birou, scoala, laborator). Cele mai utilizate tehnologii de comunicatie in retelele LAN sunt Ethernet si Wi-Fi.

Etheren este o tehnoologie de comunicatie pe fir torsadat ce a fost introdusa pe piata in anul 1980 si a fost standardizata pentru prima data in cadrul standardului IEEE 802.3.

Wi-Fi este o tehnologie de comunicate fara fir ce fost pentru prima data standardizata in cadrul standardului 802.11 in anul 1997 dar isi are originile in alte tehnologii de comunicatie fara fir mai vechi (ALOHAnet, WAveLAN, CSIRO).

In contrast cu LAN, Wide Area Network (WAN) este o retea de calculatoare ce acopera o zona geografica larga (zone metropolitane, nationale, regionale, legaturi internationale). Internet-ul poate fi condierat un WAN/

Internet-ul este o retea globala de retele interaconectate ce utilizeaza pentru comunicare protocoalele Internet. Acesta mai este cunoscut si sub denumirea de TCP/IP, deoarece Transmission Control Protocol (TCP) si Internet Protocol (IP) sunt cele mai cunoscute protocoale din cadrul Internet.

Protocoalele Internet sunt impartite pe 4 niveluri (Application. Transport, Internet, Link) conform figurii de mai jos:

Nivelul Application: este nivelul la care aplicatiile creeaza pachete de date si transmite aceste pachete catre alte aplicatii folosind serviciile nievlurilor de jos. Acesta este nivelul care se ocupa de structurare si interpretarea datelor ce sunt transportate in retea. La acest nivel se regasesc protocoale de nivel aplicatie sau protocoale de nivel inalt cum ar fi: SMTP, FTP, SSH, HTTP. La acest nivel fiecare aplicatie este identificata in mod unic printr-un port . Un port este un numar intreg pozitiv in intervalul 0 - 65535.

Nivelul Transort: se ocupa de livrarea datelor de la un calculator la altul oferind canalele de comunicatie necesare pentru transportarea datelor. Cele doua protocoale ce pot fi folosite pentru transportul datelor sunt TCP si UDP.

Nivelul Internet: defineste mecansimele de adresare si rutare a pachetelor in asa fel incat datele sa ajunga de la sursa la destinatie. Principalul protocol in cadrul acestui nivel este protocolul IP ce foloseste adresele de IP pentru a identifica calculatoarele.

Nivelul Link: acest nivel include protocoalele pentru schimbul de date in retele de tip LAN. La acest nivel regasim protocoalele Ethernet si Wi-Fi. La acest nivel regasim adresele de tip Media Access Control (MAC). O adresa de tip MAC identifica in mod unic o placa de retea.

Identificarea unui calculator in retea

Orice calculator conectat in 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. Acest tip de adresa mai este cunoscuta si ca IPv4, aceasta o data cu aparitia adreselor IPv6 ce utilizeaza 128 biti pentru adrese.

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.

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.

In exemplul de mai jos este utilizata clasa InetAddress pentru a: afisa adresele de IP a calculatorului local (1), lista placile de retea instalate si cu IP-urile aferente fiecareia (2), afisa adresa de IP a domeniului utcluj.ro si pentru a afisa adresele de IP asociate domeniului google.com(4).

import java.net.*;
import java.util.Enumeration;
 
class InetAddressTest 
{ 
public static void main(String args[]) throws 
UnknownHostException, SocketException { 
    //1.
    InetAddress local[] = InetAddress.getAllByName(InetAddress.getLocalHost().getCanonicalHostName()); 
    System.out.println("Network Addresses:"); 
    for (int i=0; i<local.length; i++) 
        System.out.println(local[i]); 
 
    //2
    System.out.println("Network Interfaces:");
    for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
        NetworkInterface intf = en.nextElement();
        System.out.println("    " + intf.getName() + " " + intf.getDisplayName());
        for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
          System.out.println("        " + enumIpAddr.nextElement().toString());
    }
 
    //3.
    InetAddress uctn = InetAddress.getByName("utcluj.ro"); 
    System.out.println("UT-Cluj domain address is: "+uctn); 
 
    //4.
    System.out.println("Google ip addresses are: ");
    InetAddress adr[] = InetAddress.getAllByName("google.com"); 
    for (int i=0; i<adr.length; i++) 
    System.out.println(adr[i]); 
  }
} 
}

Acelasi informatii cu privire la adresele de IP locale si placile de retea oaferente pot fi obtinute in Windows prin rularea din linia de comanda a utilitarului ipconfig.

Pentru a verifica daca axista conectivitate intre doua calculatoare se poate executa din linia de comanda utilitarul ping sub forma: ping 192.168.1.130 unde adresa de IP specificata reprezinta adresa calculatorului cu care dorim sa verificam conectivitatea.

ATENTIE: Prezenta unui firewall activ poate impiedica transmisia de date intre cele doua masini astfel incat utilitarul ping va raporta ca adresa destinatie nu este accesibila. Acelasi lucru se poate intampla si la nivelul a doua aplicatii ce incearca sa comunice in retea. Verificati setarile firwall si asigurativa ca acesta permite conexiunile necesare pentru aplicatiile pe care le rulati.

Protocolul TCP

Protocolul TCP este un protocol cu stare (orientat pe conexiune) ce ofera mecanisme de corectie a erorilor si de recuperare a pachetelor pierdute. In mod uzual se foloseste denumitea TCP/IP pentru a denota o comunicatie ce are loc prin protocolul TCP.

Un socket reprezintă un punct de conexiune într-o comunicatie ce are loc prin 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.

Arhitectura client-server

Modelul de comunicatie client-server este un model de comunicatie distribuita in care entitatile implicate in comunicare sunt de doua feluri: clienti si server. Serverul este acea aplicatie care pune la dispoziti unul sau mai multe servicii care pot fi accesate folosind un anumit protocol de comunicatie. Clientul este acea aplicatie care acceseaza serviciile puse la dispozitie de server.

Nota: In contextul arhitecturii client-server - conceptul de server nu desemneaza o masina fizica ci o aplicatie software. La un moment dat pe o masina fizica pot rula mai multe aplicatii server.

Distribuirea de informatii in cadrul World Wide Web utilizeaza arhitectura client-server. Avem servere WEB care proceseaza cerere sosite de la clienti WEB, si transmit inapoi catre acestia informatiile cerute.

Aplicatie client-server

Pentru a realiza o conexiune TCP/IP in java se utilizeaza clasele ServerSocket si 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().

Program 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();
     }
  }
}

Program client

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();
    }
  }
}

Aplicatie client-server multifir

Analizând programul server prezentat în secţiunea anterioarăm se observă că acesta poate servi doar un singur client la un moment dat. Pentru ca serverul să poată servi mai mulţi clienţi simultan, se va utiliza programarea multifir.

Serverul va aştepta conexiuni prin apelarea metodei accept(). In momentul in care un client s-a conectat şi metoda accept() a returnat un Socket, se va construi un obiect de tip fir de execuţie care va servi respectivul clientul, iar severul va reveni în aşteptare pentru a deservi alti clienti.

Program server multifir

import java.io.*;
import java.net.*;
 
public class ServerMultifir 
{
               public static final int PORT = 1900;
               void startServer()
               {
                               ServerSocket ss=null;
                               try
                               {
 
                               ss = new ServerSocket(PORT);
                               while (true)
                               {
                                              Socket socket = ss.accept();
                                              new TratareClient(socket).start();
                               }
 
                               }catch(IOException ex)
                               {
                                              System.err.println("Eroare :"+ex.getMessage());
                               }
                               finally
                               {
                                              try{ss.close();}catch(IOException ex2){}
                               }
               }
 
               public static void main(String args[])
               {
                               ServerMultifir smf = new ServerMultifir();
                               smf.startServer();
               }
 
 
}
 
class TratareClient extends Thread
{
                 private Socket socket;
                 private BufferedReader in;
      private PrintWriter out;
                 TratareClient(Socket socket)throws IOException 
                 {
                              this.socket = socket;
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                               out = new PrintWriter(
                                              new BufferedWriter(
new OutputStreamWriter( socket.getOutputStream())));
                 }
 
                 public void run()
                 {
                 try {
                 while (true) 
                 {  
           String str = in.readLine();
           if (str.equals("END")) break;
           System.out.println("Echoing: " + str);
           out.println(str);
          }//.while
                 System.out.println("closing...");
                 } 
                 catch(IOException e) {System.err.println("IO Exception");} 
                 finally {
          try {
           socket.close();
               }catch(IOException e) {System.err.println("Socket not closed");}
      }
               }//.run
}

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{
 
        (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;
 
    public Pers(String nume, int varsta) {
        this.nume = nume;
        this.varsta = varsta;
    }
 
    @Override
    public String toString() {
        return "Pers{" + "nume=" + nume + ", varsta=" + varsta + '}';
    }
 
}

In programul anterior atat clientul cat si serverul ruleaza in cadrul aceluiasi program (acelasi proces). Firul de executie lansat in main ce ruleaza metoda run() deshide o conexiune pe portul 197 si asteapta un client. In momentul in care clientul s-a conectat deshide un flux de citire din care citeste un obiect pe care il afiseaza.

In main, dupa lansare firului de executie, se construieste un socket client ce se conecteaza la un server pe calculatorul local (localhost) pe portul 1977 - deci se conecteaza la serverul ce asteapta conexiuni pe firul ce executa run pornit mai sus. Dupa deshiderea conexiunii se construieste un obiect de tip Pers pe care il transmite prin fluxul de iesire catre server.