Catedra de Telecomunicatii                 

 

        10/11/2007                                             

 

Software pentru Retele de Telecomunicatii (SwRTc)  

 

 

Laborator 2

Fire de executie (threads) si socket-uri flux (TCP) in Java

 

2.1. Descrierea laboratorului

 

In aceasta lucrare de laborator vor fi acoperite urmatoarele probleme:

 

- Fire de executie (threads)

- Programe de lucru cu fire de executie

 

- Socket-uri flux (TCP) in Java:

- Incapsularea adreselor IP in Java. Utilizarea clasei java.net.InetAddress

- Socket-uri pentru fluxuri (conexiuni) TCP. Utilizarea clasei java.net.Socket

- Socket-uri pentru servere TCP. Utilizarea clasei java.net.ServerSocket

 

- Programe de lucru cu socket-uri flux (TCP) clienti si servere

 

- Teme de casa

 

Anexa: Modificarea programelor Java pentru a lucra cu socket-uri fluxsistem client-server

 

2.2. Utilizarea firelor de executie (threads) in Java

 

2.2.1. Modalitati de a crea un fir de executie

 

 

Programele de calcul simple sunt secventiale, fiecare avand un inceput, o secventa de executii si un sfarsit. In orice moment pe durata executiei unui astfel de program exista un singur punct de executie.

        

 

Un fir de executie (thread), sau mai simplu, un fir, este similar acestor programe secventiale, in sensul ca are un inceput, o secventa de executii si un sfarsit, si in orice moment pe durata executiei firului exista un singur punct de executie.

Totusi, un fir nu este el insusi un program, deoarece nu poate fi executat de sine statator. In schimb, firul este executat (ruleaza) intr-un program. Posibilitatea utilizarii mai multor fire de executie intr-un singur program, ruland (fiind executate) in acelasi timp si realizand diferite sarcini (nu in mod necesar diferite), este numita multithreading.

Un navigator (browser) Web este un exemplu de aplicatie multi-filara (multithreaded). In browser se poate parcurge pagina in timpul descarcarii unei miniaplicatii Java (applet), etc. JVM (Java Virtual Machine), permite aplicatiilor sa aiba mai multe fire de executie in paralel.

 

Pentru a crea un nou fir de executie exista doua modalitati.

 

 

1. Se poate declara o clasa ca subclasa a clasei Thread, subclasa care trebuie sa rescrie codul (override) metodei run() a clasei Thread (care nu contine nici un cod), noul fir de executie fiind creat prin alocarea si lansarea unei instante a subclasei.

 

Formatul pentru crearea unei subclase care extinde clasa Thread si ii reimplementeaza metoda run() este urmatorul:

                       

     class FirT extends Thread {
        public void run() {
                              // codul firului de executie
        }
     }

 

Formatul pentru crearea unei instante a subclasei este urmatorul:

                       

       FirT fir = new FirT();

 

 

2. Se poate declara o clasa care implementeaza interfata Runnable, interfata care contine doar declaratia unei metode run() (declaratie identical celei din clasa Thread, deoarece clasa Thread implementeaza interfata Runnable), noul fir de executie fiind creat prin alocarea unei instante a noii clase, pasarea acestei instante ca parametru al constructorului, la crearea unei instante a clasei Thread, si lansarea acelei instante a clasei Thread.

 

Formatul pentru crearea unei clase care implementeaza interfata Runnable si ii implementeaza metoda run() este urmatorul:

                       

     class FirR implements Runnable {
        public void run() {
                              // codul firului de executie
        }
     }

 

Formatul pentru crearea unei instante a noii clase si apoi a unei instante a clasei Thread este urmatorul:

 

       FirR r = new FirR();
       Thread fir = new Thread(r);

 

In ambele cazuri formatul pentru lansarea noului fir de executie, este urmatorul:

                       

      fir.start();    

 

Variante compacte pentru crearea si lansarea noilor fire de executie:

                                               

       new FirT().start();                   // nu exista variabila de tip FirT 
                                             // care sa refere explicit firul
       FirR r = new FirR();
       new Thread(r).start();                // nu exista variabila de tip Thread 
                                             // care sa refere explicit firul
       Thread fir = new Thread(new FirR());  // nu exista variabila de tip FirR
       fir.start();                          // care sa refere explicit firul                                        
       new Thread(new FirR()).start();       // nu exista variabila de tip Thread
                                             // si nici variabila de tip FirR 
                                             // care sa refere explicit firul 

 

2.2.2. Programe de lucru cu fire de executie

 

 

Urmatorul program va fi folosit pentru a ilustra lucrul cu fire de executie.

 

Clasa FirSimplu extinde clasa Thread, iar metoda principala lanseaza metoda run() ca nou fir de executie.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

  public class FirSimplu extends Thread { // obiectele din clasa curenta sunt thread-uri

    public FirSimplu(String str) {

        super(str);                       // invocarea constructorului Thread(String)

    }                                     // al superclasei Thread

    public void run() {                   // “metoda principala” a thread-ului curent

        for (int i = 0; i < 5; i++) {

            System.out.println(i + " " + getName()); // obtinerea numelui thread-ului

            try {

                sleep((long)(Math.random() * 1000)); // thread-ul “doarme” 0...1 secunda

            } catch (InterruptedException e) {}

        }

        System.out.println("Gata! " + getName());    // obtinerea numelui thread-ului

    }

    public static void main (String[] args) {

        new FirSimplu("Unu").start();         // “lansarea” thread-ului (apeleaza run())

    }

  }

 

Rezultatul executiei programului:

 

 

 

 

Clasa DemoDouaFire lanseaza doua fire de executie de tip FirSimplu executate concurent.

 

1

2

3

4

5

6

 public class DemoDouaFire {

    public static void main (String[] args) {

        new FirSimplu("Unu").start();     // “lansarea” primului thread

        new FirSimplu("Doi").start();     // “lansarea” celui de-al doilea thread

    }

 }

 

 

Firele au evolutii diferite, in functie de durata intarzierii introdusa in linia de cod:

 

 

 

 

          sleep((long)(Math.random() * 1000));

 

 

 

 

a programului FirSimplu.

 

 

Rezultatele a doua executii succesive:      

 

              

 

In laborator:

1. Lansati mediul BlueJ. Inchideti proiectele anterioare (Ctrl+W). Creati un nou proiect fire

    (Project -> New Project…, selectati D:/, apoi SwRTc2007, apoi numarul grupei, si scrieti fire).

2. Creati o noua clasa, numita FirSimplu, folosind codul dat mai sus.

3. Tot in proiectul fire creati clasa DemoDouaFire folosind codul de mai sus. Compilati clasele.

4. Right-click pe clasa, selectati si executati main(). Urmariti efectul in Terminal Window.

 

Doua variante (aplicatie de sine statatoare si applet) de program "ceas" bazat pe threads:

 

import java.util.*;

import java.text.DateFormat;

import java.io.*;

 

public class CeasConsola implements Runnable

{

  private Thread firCeas = null;

  private static int contor = 0;

 

 

  public void start() {                   // (re)pornire fir

    if (firCeas == null) {

      firCeas = new Thread(this);

      firCeas.start();                    // apeleaza run()

    }

    System.out.println("Ceas pornit");

  }

  public void run() {                    // executie fir

    Thread firulMeu = Thread.currentThread();

    contor++;

 

    while (firCeas == firulMeu) {

 

      Calendar cal = Calendar.getInstance();

      Date date = cal.getTime();

      DateFormat df = DateFormat.getTimeInstance();

      System.out.println("[" + contor + "]  " +

                   df.format(date));   

 

      try { Thread.sleep(1000); }

      catch (InterruptedException e){ }

    }

  }

 

  public void stop() {                      // oprire fir

    System.out.println("Ceas oprit");

    firCeas = null;                          // firul dispare

  }

  public static void main(String[] args)

                            throws IOException {

    CeasConsola ceas;

    BufferedReader in = new BufferedReader

          (new InputStreamReader(System.in));

    while (true) {

      ceas = new CeasConsola();

      ceas.start();        // (re)pornire fir la citirea

      in.readLine();       // unui caracter de la consola

      ceas.stop();        // oprire fir la citirea

      in.readLine();       // unui caracter de la consola

    }

  }

}

import java.applet.Applet;

import java.awt.*;

import java.util.*;

import java.text.DateFormat;

public class CeasApplet extends Applet

                                             implements Runnable {

  private Thread firCeas = null;

  private static int contor = 0;

  public void init() { setBackground(Color.white); }

 

  public void start() {                       // (re)pornire fir

    if (firCeas == null) {

      firCeas = new Thread(this, "Ceas");

      firCeas.start();                         // apeleaza run()

    }

  }

 

  public void run() {                        // executie fir

    Thread firulMeu = Thread.currentThread();

    contor++;

 

    while (firCeas == firulMeu) {

      repaint();         // apeleaza paint() (redeseneaza)

      try { Thread.sleep(1000); }

      catch (InterruptedException e){ }

    }

  }

  public void paint(Graphics g) {      // deseneaza

     Calendar cal = Calendar.getInstance();

     Date date = cal.getTime();

     DateFormat df = DateFormat.getTimeInstance();

     g.drawString("[" + contor + "]  " +

                        df.format(date), 5, 10);

  }

  public void stop(){                        // oprire fir

    firCeas = null;                           // firul dispare

  }

}

// Applet-ul nu are metoda principala.

// Metodele start(), stop(), run(), etc. sunt invocate

// de Browserul Web in care este “executat”.

/*     Exemplu de includere intr-o pagina Web:

    <HTML>
         <TITLE>  
              Applet Ceas  
         </TITLE>
         <BODY> 
              <APPLET CODE=CeasApplet.class 
                     WIDTH=200 HEIGHT=50> 
              </APPLET> 
         </BODY>
    </HTML>     */

 

Varianta applet (miniaplicatie) Java CeasApplet:

 

 

Ceas

 

 

In laborator:

Testati applet-ul CeasApplet folosind link-ul de mai sus.

 

2.3. Introducere in socket-uri Java

2.3.1. Utilizarea clasei java.net.InetAddress

 

Clasa InetAddress incapsuleaza o adresa IP intr-un obiect care poate intoarce informatia utila. Aceasta informatie utila se obtine invocand metodele unui obiect al acestei clase. De exemplu, equals() intoarce adevarat daca doua obiecte reprezinta aceeasi adresa IP.

 

Clasa InetAddress nu are constructor public. De aceea, pentru a crea obiecte ale acestei clase trebuie invocata una dintre metodele de clasa getByAddress() si getByName().

 

O adresa IP speciala este adresa IP loopback (tot ce este trimis catre aceasta adresa IP se intoarce si devine intrare IP pentru gazda locala), cu ajutorul careia pot fi testate local programe care utilizeaza socket-uri. Pentru a identifica adresa IP loopback sunt folosite numele "localhost" si valoarea numerica "127.0.0.1".

 

Pentru a obtine InetAddress care incapsuleaza adresa IP loopback pot fi folosite apelurile:

 

 

InetAddress.getByName(null)

InetAddress.getByName("localhost")

InetAddress.getByName("127.0.0.1")

 

2.3.2. Socket-uri flux (TCP) Java

 

Java ofera, in pachetul java.net, mai multe clase pentru lucrul cu socket-uri flux (TCP).  Urmatoarele clase Java sunt implicate in realizarea conexiunilor TCP obisnuite: ServerSocket, Socket.

Clasa ServerSocket reprezinta socket-ul (aflat eventual pe un server bazat pe TCP) care asteapta si accepta cereri de conexiune (eventual de la un client bazat pe TCP).

Clasa Socket reprezinta punctul terminal al unei conexiuni TCP intre doua masini (eventual un client si un server).

 

Clientul (sau, mai general, masina conector) creeaza un punct terminal Socket in momentul in care cererea sa de conexiune este lansata si acceptata.

Serverul (sau, mai general, masina acceptor) creeaza un Socket in momentul in care primeste si accepta o cerere de conexiune, si continua sa asculte si sa astepte alte cereri pe ServerSocket.

 

Secventa tipica a mesajelor schimbate intre client si server este urmatoarea:

Odata conexiunea stabilita, metodele getInputStream() si getOutputSteam() ale clasei Socket trebuie utilizate pentru a obtine fluxuri de octeti, de intrare respectiv iesire, pentru comunicatia intre aplicatii.

 

2.3.3. Utilizarea clasei Socket

 

Secventa tipica pentru crearea socket-ului unei aplicatii conector (client):

 

1

2

3

4

5

6

7

8

    // Stabilirea adresei serverului

    String adresaServer = "localhost";

 

    // Stabilirea portului serverului

    int portServer = 2000;

 

    // Crearea socketului (implicit este realizata conexiunea cu serverul)

    Socket socketTCPClient = new Socket(adresaServer, portServer); 

 

Dupa utilizare, socket-ul este inchis. Secventa tipica pentru inchiderea socket-ului:

 

1

2

    // Inchiderea socketului (implicit a fluxurilor TCP)

    socketTCPClient.close();

 

Crearea unui socket pentru aplicatii conector/client si afisarea informatiilor privind socketul:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

import java.net.*; // pachetul claselor de biblioteca pentru socketuri

import java.io.*;  // pachetul claselor de biblioteca pentru intrare-iesire

 

public class InfoSocketTCP { // Afiseaza info. privind conexiunea TCP creata

 

   public static void main (String args[]) {

      BufferedReader inConsola  = new BufferedReader(new

                                    InputStreamReader(System.in));

      String adresaIP = null;

      int numarPort = 0;

      Socket conexiuneTCP;

      try {

         System.out.print("Introduceti adresa IP dorita: ");

         adresaIP = inConsola.readLine();

 

         System.out.print("Introduceti numarul de port dorit: ");

         numarPort = Integer.parseInt(inConsola.readLine());

 

         conexiuneTCP = new Socket(adresaIP, numarPort);  // creare socket

 

         afisareInfoSocketTCP(conexiuneTCP);              // afisare info socket

 

         conexiuneTCP.close();                            // inchidere socket

      }

      catch (NumberFormatException ex) {                  // eroare numar port

         System.err.println("Numarul de port nu are format intreg");

      }

      catch (UnknownHostException ex) {                   // eroare DNS

         System.err.println("Nu poate fi gasita adresa " + adresaIP);

      }

      catch (SocketException ex) {                        // eroare conectare

         System.err.println("Nu se conecteaza la " + adresaIP + ":" + numarPort);

      }

      catch (IOException ex) { 

         System.err.println(ex);

      }

   }

 

   // Afiseaza informatii privind socketul TCP primit ca parametru

   public static void afisareInfoSocketTCP (Socket socketTCP) {

      System.out.println("\nConexiune de la adresa "+socketTCP.getLocalAddress()

                         + " de pe portul " + socketTCP.getLocalPort()

                         + " catre adresa " + socketTCP.getInetAddress() 

                         + " pe portul " + socketTCP.getPort() + "\n");   

   }

}

 

In laborator:

1. Lansati mediul BlueJ. Inchideti proiectele anterioare (Ctrl+W). Creati un nou proiect socket

      (Project->New Project…, selectati D:/,   SwRTc2007,   numarul grupei, si scrieti socket).

2. Creati o noua clasa, numita InfoSocketTCP, folosind codul dat mai sus. Compilati codul.

3. Right-click pe clasa, selectati si executati main().

4. Folositi adresa localhost si numarul de port 3000. Urmariti efectul in Terminal Window.

5. Folositi apoi adresa www.google.com si numarul de port 7. Folositi si alte adrese.

 

Observatie: Cat timp bara de stare a executiei este activa () codul nu poate fi recompilat, nu poate fi inchisa fereastra Terminal Window, etc.

Pentru a opri executia, folositi right click pe  si selectati Reset Machine (sau folositi direct Ctrl+Shift+Tab).

 

2.3.4. Utilizarea clasei ServerSocket

 

Secventa tipica pentru crearea socket-ului server al unei aplicatii acceptor (server):

1

2

3

4

5

    // Stabilirea portului serverului

    int portServer = 2000;

 

    // Crearea socketului server (care accepta conexiunile)

    ServerSocket serverTCP = new ServerSocket(portServer);

 

Secventa tipica pentru crearea socket-ului pentru tratarea conexiunii TCP cu un client:

1

2

3

4

    // Blocare in asteptarea cererii de conexiune - in momentul acceptarii

    // cererii se creaza socketul care serveste conexiunea

 

    Socket conexiuneTCP = serverTCP.accept();

 

Un caz special este acela in care se creaza un socket server fara a se preciza portul pe care asculta serverul. In acest caz, este alocat un numar de port aleator (unul dintre cele neocupate in acel moment). Acest lucru se realizeaza prin pasarea valorii 0 constructorului ServerSocket().

 

Program pentru crearea unui socket server cu numar de port alocat aleator.

1

2

3

4

5

6

7

8

9

10

11

import java.net.*;

import java.io.*;

public class InfoPortTCPAleator {

public static void main (String args[]) {

    try { ServerSocket serverTCP = new ServerSocket(0);

          System.out.println("\nServer pe portul " + serverTCP.getLocalPort());

          serverTCP.close();

    }

    catch (IOException ex) {   System.err.println(ex);  }

  }

}

 

2.4. Programe de lucru cu socket-uri – clienti si servere

2.4.1. Server ecou TCP care poate trata doar un client (single threaded)

 

Secventa tipica pentru crearea fluxurilor de octeti asociate socket-ului:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

    // Obtinerea fluxului de intrare octeti TCP

    InputStream inTCP = socketTCPClient.getInputStream();

 

    // Obtinerea fluxului de intrare caractere dinspre retea

    InputStreamReader inTCPCaractere = new InputStreamReader(inTCP);

    // Adaugarea facilitatilor de stocare temporara

    BufferedReader inRetea = new BufferedReader(inTCPCaractere);

 

    // Obtinerea fluxului de iesire octeti TCP

    OutputStream outTCP = socketTCPClient.getOutputStream();

 

    // Obtinerea fluxului de iesire spre retea,

    // cu facilitate de afisare (similare consolei standard de iesire)

    PrintStream outRetea  = new PrintStream(outTCP);

Secventa tipica pentru trimiterea de date:

 

1

2

3

4

5

6

7

8

    // Crearea unui mesaj

    String mesajDeTrimis = "Continut mesaj";

 

    // Scrierea catre retea (trimiterea mesajului)

    outRetea.println(mesajDeTrimis);

 

    // Fortarea trimiterii

    outRetea.flush();                 

 

Secventa tipica pentru primirea de date:

 

1

2

3

4

5

    // Citirea dinspre retea (receptia unui mesaj)

    String mesajPrimit = inRetea.readLine();

       

    // Afisarea mesajului primit

    System.out.println(mesajPrimit);

 

Program server ecou TCP care raspunde mai multor mesaje trimise succesiv, mesajul format dintr-un punct (".") semnaland serverului terminarea mesajelor de trimis (clientul urmand sa isi termine executia):

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

import java.net.*;

import java.io.*;

import javax.swing.JOptionPane; 

public class ServerEcouFluxRepetiv {          // Server ecou flux

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

 

    int portServer = Integer.parseInt(JOptionPane.showInputDialog(

                              "Introduceti numarul de port dorit: "));

 

    // Crearea socketului server (care accepta conexiunile)

    ServerSocket serverTCP = new ServerSocket(portServer);

    System.out.println("Server in asteptare pe portul "+portServer+"...");

 

    // Blocare in asteptarea cererii de conexiune - in momentul acceptarii

    // cererii se creaza socketul care serveste conexiunea

    Socket conexiuneTCP = serverTCP.accept();

    System.out.println("Conexiune TCP pe portul " + portServer + "...");

 

    // Crearea fluxurilor de caractere conectate la fluxurile de octeti

    // obtinute de la socketul TCP

    PrintStream outRetea = new PrintStream(conexiuneTCP.getOutputStream());

    BufferedReader inRetea = new BufferedReader(

                   new InputStreamReader(conexiuneTCP.getInputStream()));

 

    while (true) {

      // Citirea unei linii din fluxul de intrare TCP

      String mesajPrimit = inRetea.readLine();

       

      // Afisarea liniei citite la consola de iesire

      System.out.println("Mesaj primit: " + mesajPrimit);

 

      // Scrierea liniei in fluxul de iesire TCP, cu fortarea trimiterii

      outRetea.println(mesajPrimit);

      outRetea.flush();

  

      // Testarea conditiei de oprire a servirii

      if (mesajPrimit.equals(".")) break;

    }

 

    // Inchiderea socketului (si implicit a fluxurilor)

    conexiuneTCP.close();

    System.out.println("Bye!");

  }

}

 

In laborator:

1. In acelasi proiect (socket) creati o clasa numita ServerEcouFluxRepetiv folosind codul dat.

2. Compilati codul, apoi right-click pe clasa, selectati si executati main().

3. Folositi numarul de port 4000. Urmariti efectul in Terminal Window.

 

Nu uitati: Pentru a opri executia, right click pe  si Reset Machine (sau Ctrl+Shift+Tab).

 

2.4.2. Client ecou TCP

 

Cu ajutorul unui client potrivit putem testa serverul ecou TCP.

Program client pentru serverul ecou TCP anterior (care raspunde mai multor mesaje trimise succesiv):

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

import java.net.*;

import java.io.*;

import javax.swing.JOptionPane; 

 

public class ClientEcouFluxRepetitiv {    // Client pentru server ecou flux

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

    String adresaServer = JOptionPane.showInputDialog(

                              "Introduceti adresa IP a serverului: ");

    int portServer = Integer.parseInt(JOptionPane.showInputDialog(

                              "Introduceti numarul de port al serverului: "));

 

    Socket conexiuneTCP = new Socket(adresaServer, portServer); // Creare socket

    System.out.println("Conexiune TCP cu serverul " + adresaServer +

                       ":" + portServer + "...");

    System.out.println("Pentru oprire introduceti '.' si <Enter>");

 

    PrintStream outRetea = new

                 PrintStream(conexiuneTCP.getOutputStream());  // Creare fluxuri

    BufferedReader inRetea = new BufferedReader(

                 new InputStreamReader(conexiuneTCP.getInputStream()));

 

    while (true) {               // Cat timp conditia de oprire nu este indeplinita

      String mesajTrimis = JOptionPane.showInputDialog("Se trimite: ");  // Mesaj

 

      outRetea.println(mesajTrimis);           // Scrierea in fluxul de iesire TCP

      outRetea.flush();

 

      String mesajPrimit = inRetea.readLine(); // Citirea din fluxul de intrare TCP

       

      JOptionPane.showMessageDialog(null, "S-a primit: " + mesajPrimit); // Afisare

      System.out.println("S-a primit: " + mesajPrimit);    // Afisarea la consola

 

      if (mesajPrimit.equals(".")) break;     // Testarea conditiei de oprire

    }

    conexiuneTCP.close();    // Inchiderea socketului (si implicit a fluxurilor)

    System.out.println("Bye!");

  }

}

 

 

In laborator:

1. In proiectul socket creati o clasa ClientEcouFluxRepetitiv cu codul dat. Compilati codul.

2. Right-click pe clasa, executati main(). Folositi adresa localhost si portul 4000. Ce observati?

 

In laborator:

1. La unul dintre calculatoare right-click pe clasa ServerEcouFluxRepetiv.

2. Selectati si executati main(). Folositi numarul de port 4000.

 

 

3. La un alt calculator right-click pe clasa ClientEcouFluxRepetitiv, selectati si executati main().

4. Folositi adresa primului calculator (cel pe care se executa ServerEcouFluxRepetiv)

         si numarul de port 4000. Urmariti efectul in Terminal Window pe cele doua calculatoare.

 

In laborator (codul complet este parte din tema de casa!):               

1. Pornind de la codul ServerEcouFluxRepetiv sa se creeze o clasa ServerRepetiv care sa afiseze mesajele primite de la client si sa astepte un utilizator uman sa furnizeze mesajul de raspuns catre client. Sa se compileze si testeze codul folosind un calculator pentru server si unul pentru client.

 

2.4.3. Server ecou TCP care trateaza mai multi clienti succesiv (single threaded)

 

Serverul ecou TCP poate fi transformat astfel incat sa poata trata mai multi clienti (de exemplu, ClientEcouFluxRepetitiv) succesiv.

 

 

Program server ecou TCP care poate trata mai multi clienti (ClientEcouFluxRepetitiv) succesiv.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

import java.net.*;

import java.io.*;

import javax.swing.JOptionPane; 

 

public class ServerEcouFluxRepetivSecvential {

 

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

 

    int portServer = Integer.parseInt(JOptionPane.showInputDialog(

                              "Introduceti numarul de port al serverului: "));

 

    // Crearea socketului server (care accepta conexiunile)

    ServerSocket serverTCP = new ServerSocket(portServer);

 

    // Servirea mai multor clienti succesivi (in mod secvential)

    while (true) {

      System.out.println("Server in asteptare pe port "+portServer+"...");

 

      // Blocare in asteptarea cererii de conexiune - in momentul

      // acceptarii cererii se creaza socketul care serveste conexiunea

      Socket conexiuneTCP = serverTCP.accept();

      System.out.println("Conexiune TCP pe portul " + portServer + "...");

 

      // Crearea fluxurilor de caractere conectate la fluxurile de octeti

      // obtinute de la socketul TCP

      PrintStream outRetea = new

             PrintStream(conexiuneTCP.getOutputStream());

      BufferedReader inRetea = new BufferedReader(

             new InputStreamReader(conexiuneTCP.getInputStream()));

 

      // Servirea clientului curent

      while (true) {

        // Citirea unei linii din fluxul de intrare TCP

        String mesajPrimit = inRetea.readLine();

       

        // Afisarea liniei citite la consola de iesire

        System.out.println("Mesaj primit: " + mesajPrimit);

 

        // Scrierea liniei in fluxul de iesire TCP, cu fortarea trimiterii

        outRetea.println(mesajPrimit);

        outRetea.flush();

  

        // Testarea conditiei de oprire a servirii

        if (mesajPrimit.equals(".")) break;

      }

 

      // Inchiderea socketului (si implicit a fluxurilor)

      conexiuneTCP.close();

      System.out.println("Bye!");

    }

  }

}

 

 

In laborator:

1. In acelasi proiect (socket) creati o clasa ServerEcouFluxRepetivSecvential cu codul dat.

2. Compilati codul.

 

In laborator:

1. La unul dintre calculatoare right-click pe clasa ServerEcouFluxRepetivSecvential.

2. Selectati si executati main(). Folositi numarul de port 4000.

 

 

3. La un alt calculator right-click pe clasa ClientEcouFluxRepetitiv, selectati si executati main().

4. Folositi adresa primului calculator (cel pe care se executa ServerEcouFluxRepetivSecvential)

         si numarul de port 4000. Urmariti efectul in Terminal Window pe cele doua calculatoare.

 

 

5. Pastrand serverul deschis repetati operatia cu clienti de pe diverse calculatoare.

 

 

2.4.4. Server ecou TCP care trateaza mai multi clienti succesiv (single threaded)

 

Serverul ecou TCP poate fi transformat astfel incat sa poata trata mai multi clienti in acelasi timp (concurent).

 

 

In cazul serverelor care pot trata mai multi clienti in mod concurent (in paralel), codul necesar pentru stabilirea conexiunilor cu clientii ce urmeaza a fi tratati de obiecte fir de executie (a caror clasa este numita generic ClasaFiruluiDeTratare) este urmatorul:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

    // initializare portServer

    // Crearea socketului server (care accepta conexiunile)

    ServerSocket serverTCP = new ServerSocket(portServer);

 

    // Servirea mai multor clienti in paralel (in mod concurent)

    while (true) {

      // Blocare in asteptarea cererii de conexiune

      Socket conexiuneTCP = serverTCP.accept();

 

      // Crearea si lansarea firului de executie pentru tratarea noului client

      ClasaFiruluiDeTratare firExecutie = new ClasaFiruluiDeTratare(conexiuneTCP);

      firExecutie.start();

 

    } // Gata pentru a accepta urmatorul client

 

Codul necesar pentru comunicatia cu un client (care se va afla in clasa din care se vor crea obiecte fir de executie, numita generic ClasaFiruluiDeTratare) este urmatorul:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

    Socket socketTCP;                         // Atribut

    public ClasaFiruluiDeTratare(Socket s) {  // Constructor

      socketTCP = s;                          // Initializarea atributului

    }

    public void run() {                      // Implementarea firului de executie

      // Crearea fluxurilor conectate la fluxurile obtinute de la socketul TCP

      PrintStream out = new PrintStream(conexiuneTCP.getOutputStream());

      BufferedReader in = new BufferedReader(

                   new InputStreamReader(conexiuneTCP.getInputStream()));

      // Tratarea clientului curent

      while (true) {

        // Citiri din fluxul de intrare TCP cu in.readLine();

        // Scrieri in fluxul de iesire TCP cu out.println(); out.flush();

      } // Incheierea tratarii clientului

 

      // Inchiderea socketului

      conexiuneTCP.close();

    }

 

O varianta mai simpla poate fi utilizarea unei singure clase, care sa extinda clasa Thread, si:

- in metoda main() sa implementeze stabilirea conexiunilor cu clientii

- in metoda run() sa implementeze codul necesar pentru comunicatia cu un client.

 

Exemplu de server ecou care poate trata mesaje sosite de la mai multi clienti in paralel:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

import java.net.*;

import java.io.*;

import javax.swing.JOptionPane; 

 

public class ServerEcouFluxRepetivConcurent extends Thread {

  private Socket conexiuneTCP;

 

  public ServerEcouFluxRepetivConcurent(Socket socketTCP) {

    conexiuneTCP = socketTCP;

  }

 

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

    int portServer = Integer.parseInt(JOptionPane.showInputDialog(

                              "Introduceti numarul de port al serverului: "));

 

    // Crearea socketului server (care accepta conexiunile)

    ServerSocket serverTCP = new ServerSocket(portServer);

 

    System.out.println("Server in asteptare pe portul "+portServer+"...");

 

    // Servirea mai multor clienti in acelasi timp (in mod concurent)

    while (true) {

      // Blocare in asteptarea cererii de conexiune - in momentul

      // acceptarii cererii se creaza socketul care serveste conexiunea

      Socket socketTCP = serverTCP.accept();

      System.out.println("Conexiune TCP pe portul " + portServer + "...");

 

      new ServerEcouFluxRepetivConcurent(socketTCP).start(); // run()

    }

  }

 

  public void run() {  // Fir de servire client

 

    try {

      // Crearea fluxurilor de caractere conectate la fluxurile de octeti

      // obtinute de la socketul TCP

      PrintStream outRetea = new

            PrintStream(conexiuneTCP.getOutputStream());

      BufferedReader inRetea = new BufferedReader(

            new InputStreamReader(conexiuneTCP.getInputStream()));

 

      // Servirea clientului curent

      while (true) {

        // Citirea unei linii din fluxul de intrare TCP

        String mesajPrimit = inRetea.readLine();

       

        // Afisarea liniei citite la consola de iesire

        System.out.println("Mesaj primit: " + mesajPrimit);

 

        // Scrierea liniei in fluxul de iesire TCP, cu fortarea trimiterii

        outRetea.println(mesajPrimit);

        outRetea.flush();

  

        // Testarea conditiei de oprire a servirii

        if (mesajPrimit.equals(".")) break;

      }

 

      // Inchiderea socketului (si implicit a fluxurilor)

      conexiuneTCP.close();

      System.out.println("Bye!");

    }

    catch (IOException ex) {

      System.err.println(ex);

    }

  }

}

 

 

In laborator:

1. In acelasi proiect (socket) creati o clasa ServerEcouFluxRepetivConcurent cu codul dat.

2. Compilati codul.

 

 

In laborator:

1. La unul dintre calculatoare right-click pe clasa ServerEcouFluxRepetivConcurent.

2. Selectati si executati main(). Folositi numarul de port 5000.

 

3. La un alt calculator right-click pe clasa ClientEcouFluxRepetitiv, selectati si executati main().

4. Folositi adresa primului calculator (cel pe care se executa ServerEcouFluxRepetivConcurent)

         si numarul de port 5000. Urmariti efectul in Terminal Window pe cele doua calculatoare.

 

5. Pastrand serverul deschis lansati si utilizati si alti clienti de pe diverse calculatoare.

 

 

 

2.5. Teme pentru acasa

 

 

I. Codul final al clasei ServerRepetiv (cerut in paragraful 2.4.2).

 

II. O varianta  a clasei ServerRepetiv, numita ServerRepetivClientiSuccesivi, care permite tratarea mai multor clienti unul dupa altul.

 

Codurile vor fi predate sub forma de listing sau scrise de mana!

 

 

 

 


Anexa. Modificarea programelor Java pentru a lucra cu socket flux (TCP)

 

Clasa Polinom reprezinta o varianta a clasei Polinom4 de la lucrarea anterioara. Initializarea atributelor este realizata de:

- un constructor fara parametri, care obtine gradul polinomului si valorile coeficientilor de la utilizator, initializeaza atributele cu aceste valori, si apoi le scrie intr-un fisier pe disc (cu nume prestabilit "coef.txt"),

- un constructor cu un parametru de tip int[], care foloseste tabloul primit pentru a initializa valorile atributelor, apoi le scrie intr-un fisier pe disc (cu nume prestabilit "coef.txt"),

- un constructor cu un parametru de tip String, care formeaza din el numele unui fisier de pe disc (cu nume prestabilit "coef.txt"), din care citeste valorile coeficientilor si cu ele initializeaza atributele.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

  import javax.swing.JOptionPane;

  import java.io.*;

 

  public class Polinom {

    protected int N;         // gradul polinomului

    protected int[] C;       // coeficientii polinomului

 

    // Constructor - initializeaza elementele polinomului

    // cu valori obtinute de la utilizator, si le scrie in fisier

    public Polinom() throws IOException {

      PrintWriter outFisier = new PrintWriter(new

                          BufferedWriter(new FileWriter("coef.txt")));

      N = Integer.parseInt(JOptionPane.showInputDialog(

                                  "Introduceti gradul polinomului"));

      C = new int[N+1];

      for (int i=0; i<=N; i++) {

        C[i] = Integer.parseInt(JOptionPane.showInputDialog(

                                      "Coeficientul de grad " + i));

        outFisier.println(C[i]);

      }

      outFisier.close();

      System.out.println("A fost creat un obiect Polinom...");

    }

    // Constructor - initializeaza elementele polinomului

    // cu valori obtinute ca parametru, si le scrie in fisier

    public Polinom(int[] coeficienti) throws IOException {

      PrintWriter outFisier = new PrintWriter(new

                          BufferedWriter(new FileWriter("coef.txt")));

      N = coeficienti.length-1;

      C = new int[N+1];

      for (int i=0; i<=N; i++) {

        C[i] = coeficienti[i];

        outFisier.println(C[i]);

      }

      outFisier.close();

      System.out.println("A fost creat un obiect Polinom...");

    }

    // Constructor - initializeaza elementele polinomului

    // cu valori citite din fisier

    public Polinom(int grad) throws IOException {

      BufferedReader inFisier = new BufferedReader(new FileReader("coef.txt"));

      N=grad;

 

      C = new int[grad+1];

      for (int i=0; i<=grad; i++) {

        C[i] = Integer.parseInt(inFisier.readLine());

      }

      System.out.println("A fost creat un obiect Polinom...");

    }

    public void afisarePolinom() {

      System.out.print("P(X) = " + C[0]);

      for (int i=1; i<=N; i++) {

        System.out.print(" + " + C[i]  + "*X^" + i);

      }

      System.out.println();     

    }

    // Metoda care calculeaza valoarea polinomului pentru o necunoscuta

    public int valoarePolinom(int X) {

      int P_X = 0;

      int X_i = 1;

 

      // Termenii +Ci*X^i, unde 0=1,N

      for (int i=0; i<=N; i++) {

        P_X = P_X + C[i]*X_i;

        X_i = X_i * X;

      }

      return (P_X);     

    }

    // Metoda care returneaza tabloul coeficientilor polinomului

    public int[] coeficienti() {

      int[] coef = new int[N+1];

      System.arraycopy(C, 0, coef, 0, N+1);

      return (coef);

    }

    public String toString() {

      String polinom = "" + C[0];

      for (int i=1; i<=N; i++) {

        polinom = polinom + " + " + C[i]  + "*X^" + i;

      }

      System.out.println("N=" + N);

      return (polinom);     

    }

  }

 

 

Metoda main() a clasei UtilizareDirectaPolinom contine un scenariu de utilizare a clasei Polinom bazat pe apelul primului constructor (care obtine valorile coeficientilor de la utilizator).

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

  import java.io.*;

  import javax.swing.JOptionPane;

 

    public class UtilizareDirectaPolinom  {

 

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

      // Apelul constructorului care initializeaza elementele polinomului

      Polinom pol = new Polinom();

 

      // Obtinerea unei valori a necunoscutei (X)

      int X = Integer.parseInt(JOptionPane.showInputDialog(

                                     "Introduceti valoarea necunoscutei"));

      // Afisarea valorii necunoscutei (X)

      JOptionPane.showMessageDialog(null, "X = " + X);

      

      // Apelul metodei care calculeaza polinomul pentru necunoscuta X

      int P_X = pol.valoarePolinom(X);

      // Afisarea valorii P(X)

      JOptionPane.showMessageDialog(null, "P(" + X + ") = " + P_X);

 

      System.exit(0);      // Inchiderea interfetei grafice

    }

}

 

Programul TCPServerPolinom utilizeaza clasa Polinom. Programul UtilizareTCPClientPolinom (prin metoda sa principala) utilizeaza clasa TCPClientPolinom, care comunica prin socket TCP cu un obiect al clasei Polinom. Sistemul client server astfel creat permite unui utilizator la distanta sa efectueze un scenariu asemanator utilizarii directe a clasei Polinom.

 

Programul UtilizareTCPClientPolinom:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

  import java.io.*;

  import java.net.*;

  import javax.swing.JOptionPane;

 

  public class UtilizareTCPClientPolinom  {

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

      // Apelul constructorului care initializeaza elementele polinomului

      TCPClientPolinom pol = new TCPClientPolinom();

 

      // Obtinerea unei valori a necunoscutei (X)

      int X = Integer.parseInt(JOptionPane.showInputDialog(

                       "UtilizareClient: Introduceti valoarea necunoscutei"));

      // Afisarea valorii necunoscutei (X)

      JOptionPane.showMessageDialog(null, "UtilizareClient: X = " + X);

     

      // Apelul metodei care calculeaza polinomul pentru necunoscuta X

      int P_X = pol.valoarePolinom(X);

      // Afisarea valorii P(X)

      JOptionPane.showMessageDialog(null, "UtilizareClient: P(" + X + ") = " + P_X);

 

      // Apelul metodei care inchide sesiunea

      pol.close();

      System.exit(0);      // Inchiderea interfetei grafice

    }

}

 

Programul TCPClientPolinom:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

  import java.io.*;

  import java.net.*;

  import javax.swing.JOptionPane;

 

  public class TCPClientPolinom  {

    protected int N;         // gradul polinomului

    protected int[] C;       // coeficientii polinomului

    protected PrintStream outRetea;

    protected BufferedReader inRetea;

 

    // Constructor - initializeaza polinomul cu valori obtinute de la utilizator

    public TCPClientPolinom() throws IOException {

      String adresaServer = JOptionPane.showInputDialog(

                         "Client: Introduceti adresa IP a serverului");

      int portServer = Integer.parseInt(JOptionPane.showInputDialog(

                         "Client: Introduceti numarul de port al serverului"));

 

      // Crearea socketului (implicit este realizata conexiunea)

      Socket conexiuneTCP = new Socket(adresaServer, portServer);

 

      // Crearea fluxurilor de caractere conectate la fluxurile de octeti

      // obtinute de la socketul TCP

      outRetea = new PrintStream(conexiuneTCP.getOutputStream());

      inRetea = new BufferedReader(

                 new InputStreamReader(conexiuneTCP.getInputStream()));

 

      // Obtinerea gradului polinomului (N)

      N = Integer.parseInt(JOptionPane.showInputDialog(

                                  "Client: Introduceti gradul polinomului"));

      // Crearea tabloului coeficientilor (de dimensiune N+1)

      C = new int[N+1];

      // Obtinerea coeficientilor Ci, unde i=0,N

      for (int i=0; i<=N; i++) {

        C[i] = Integer.parseInt(JOptionPane.showInputDialog(

                           "Client: Introduceti coeficientul de grad " + i));

      }

 

      // Comanda creare polinom

      String comanda = "CREARE_POLINOM";

 

      // Trimiterea comenzii ca String prin fluxul de iesire TCP

      outRetea.println(comanda);

      outRetea.flush();

 

      // Trimiterea gradului polinomului prin fluxul de iesire TCP

      outRetea.println(N);

      outRetea.flush();

 

      // Trimiterea coeficientilor polinomului prin fluxul de iesire TCP

      for (int i = 0; i < (N+1); i++) {

        outRetea.println(C[i]);

        outRetea.flush();

      }

 

      // Obtinerea polinomului

      String polinom = inRetea.readLine();

 

      // Afisarea polinomului

      JOptionPane.showMessageDialog(null, "Client: P(X) = " + polinom);

    }

 

    // Metoda care calculeaza valoarea polinomului pentru o necunoscuta

    public int valoarePolinom(int X) throws IOException {

      // Comanda creare polinom

      String comanda = "CALCUL_POLINOM";

 

      // Trimiterea comenzii ca String prin fluxul de iesire TCP

      outRetea.println(comanda);

      outRetea.flush();

 

      // Trimiterea gradului polinomului prin fluxul de iesire TCP

      outRetea.println(N);

      outRetea.flush();

 

      // Trimiterea valorii necunoscutei prin fluxul de iesire TCP

      outRetea.println(X);

      outRetea.flush();

 

      // Obtinerea valorii polinomului pentru necunoscuta data

      String valoare = inRetea.readLine();

 

      return (Integer.parseInt(valoare));     

    }

    // Metoda care anunta inchiderea sesiunii

    public void close() throws IOException {

      // Comanda inchidere sesiune

      String comanda = "BYE";

 

      // Trimiterea comenzii ca String prin fluxul de iesire TCP

      outRetea.println(comanda);

      outRetea.flush();

    }

}

 

Programul TCPServerPolinom:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

  import java.io.*;

  import java.net.*;

  import javax.swing.JOptionPane;

 

  public class TCPServerPolinom  {

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

      int portServer = Integer.parseInt(JOptionPane.showInputDialog(

                        "Server: Introduceti numarul de port al serverului"));

      // Crearea socketului server (care accepta conexiunile)

      ServerSocket serverTCP = new ServerSocket(portServer);

 

      // Servirea mai multor clienti succesivi (in mod secvential)

      while (true) {

        JOptionPane.showMessageDialog(null,

                  "Server: In asteptare pe portul " + portServer);

        // Blocare in asteptarea cererii de conexiune - in momentul

        // acceptarii cererii se creaza socketul care serveste conexiunea

        Socket conexiuneTCP = serverTCP.accept();

 

        JOptionPane.showMessageDialog(null,

                  "Server: Conexiune TCP pe portul " + portServer);

 

        // Crearea fluxurilor de caractere conectate la fluxurile de octeti

        // obtinute de la socketul TCP

        PrintStream outRetea = new

                  PrintStream(conexiuneTCP.getOutputStream());

        BufferedReader inRetea = new BufferedReader(

                  new InputStreamReader(conexiuneTCP.getInputStream()));

 

        // Servirea clientului curent

        while (true) {

          // Citirea unei linii din fluxul de intrare TCP

          String comanda = inRetea.readLine();

       

          // Afisarea liniei citite la consola de iesire

          JOptionPane.showMessageDialog(null,

                                        "Server: Mesaj primit: " + comanda);

          // Comanda creare polinom

          if (comanda.equals("CREARE_POLINOM")) {

            // Obtinerea gradului polinomului

            String gradS = inRetea.readLine();

            int grad = Integer.parseInt(gradS);

            JOptionPane.showMessageDialog(null, "Server: grad = " + grad);

 

            // Obtinerea coeficientilor polinomului

            String coefS;

            int[] C = new int[grad+1];

           

            for (int i = 0; i < (grad+1); i++) {

              coefS = inRetea.readLine();

              C[i] = Integer.parseInt(coefS);

              JOptionPane.showMessageDialog(null, "Server: C[" + i + "] = " + C[i]);

            }

            // Apelul metodei care initializeaza elementele polinomului

            Polinom pol = new Polinom(C);

            String polinom = pol.toString();

           

            // Trimiterea polinomului ca String prin fluxul de iesire TCP

            outRetea.println(polinom);

            outRetea.flush();

          }

 

          // Comanda creare polinom

          else if (comanda.equals("CALCUL_POLINOM")) {

            // Obtinerea gradului polinomului

            String gradS = inRetea.readLine();

           

            int grad = Integer.parseInt(gradS);

            JOptionPane.showMessageDialog(null, "Server: grad = " + grad);

 

            // Obtinerea necunoscutei

            String necS = inRetea.readLine();

 

            int nec = Integer.parseInt(necS);

            JOptionPane.showMessageDialog(null, "Server: nec = " + nec);

 

            // Utilizare Polinom

            Polinom polCopy = new Polinom(grad);

            int valoare = polCopy.valoarePolinom(nec);

           

            // Trimiterea valorii polinomului prin fluxul de iesire TCP

            outRetea.println(valoare);

            outRetea.flush();

          }

 

          // Testarea conditiei de oprire a servirii

          else if (comanda.equals("BYE")) break;

        }

        // Inchiderea socketului (si implicit a fluxurilor)

        conexiuneTCP.close();

        System.out.println("Bye!");

      }

    }

}