proiect client-server. retele de calculatoare

15
Programarea în reţea – Socket-uri 1. Scopul lucrării Scopul acestei lucrări este insuşirea tehnicilor de programare în reţea utilizând socket-uri. 2. Consideraţii teoretice Calculatoarele conectate in reţea comunică între ele utilizând protocoalele TCP (Transport Control Protocol) şi UDP (User Datagram Protocol) conform diagramei: 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

Upload: cezara-cocoi

Post on 22-Oct-2015

93 views

Category:

Documents


5 download

DESCRIPTION

Retele de Calculatoare

TRANSCRIPT

Page 1: Proiect Client-Server. Retele de Calculatoare

Programarea în reţea – Socket-uri

1. Scopul lucrării

Scopul acestei lucrări este insuşirea tehnicilor de programare în reţea utilizând socket-uri.

2. Consideraţii teoretice

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

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 ScopURL 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

Page 2: Proiect Client-Server. Retele de Calculatoare

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 URLConnectionProgramarea 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 să 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.

Definitia socket-ului: Un socket reprezintă un punct de conexiune întro 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. Desfăşurarea lucrării

3.1. Program client-server 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.*;

Page 3: Proiect Client-Server. Retele de Calculatoare

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

Page 4: Proiect Client-Server. Retele de Calculatoare

// 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.

3.2 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.

Ideea de bază este simplă, şi anume, 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 crea un fir de execuţie care va servi respectivul clientul, iar severul va reveni în aşteptare.

In listingul următor este prezentat un server multifir – capabil de a servi mai multi clienţi simultan.

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){

Page 5: Proiect Client-Server. Retele de Calculatoare

Socket socket = ss.accept();new TratareClient(socket);

}

}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

Page 6: Proiect Client-Server. Retele de Calculatoare

}

3.3. Server HTTP

In această sectiune este creat un server HTTP care poate răspunde la cereri GET. Functia main() din cadrul clasei HttpServer startaează un fir de execuţie. In cadrul acestui fir se instanţiază un obiect ServerSocket şi se incepe ascultarea portului 80, care este portul standard pentru protocolul HTTP.

In momentul în care apare o cerere (un client se conectează pe portul 80) metoda accept() va returna un obiect Socket. In continuare se crează un obiect PrecesRequest (care este de tip fir de excutie), care va primi ca parametru, obiectul Socket returnat de metoda accept(). După crearea obiectului ProcesRequest, serverul revine în aşteptare şi va putea servi alţi clienţi.

Clasa ProcesRequest implementează o versiune simplificată a protocolului HTTP. In cadrul constructorului clasei ProcesRequest se crează fluxurile de intrare \ ieşire, după care este startat firul de execuţie. In cadrul firului de execuţie este analizată cererea primită de la client , şi în cazul în care aceasta este o cerere validă de tip GET, atunci se va transmite către client resursa solicitată. import java.io.*;import java.net.*;

class HttpServer extends Thread{

//portul standardprivate final static int PORT = 80;

private final String iniContext="c:/temp/ServerHTTP/webdocs";private boolean alive;

private ServerSocket ss;//constructorHttpServer()throws Exception{

System.out.println("Start server http.");ss = new ServerSocket(PORT);alive=true;start();

}

public void run(){while(alive){

//asteapta conexiunitry{System.out.println("Server asteapta...");new ProcesRequest(ss.accept(),iniContext);

}catch(IOException e){System.err.println("EROARE CONECTARE:"+e.getMessage());}

//..reia bucla de asteptare dupa ce am creat un fir pentru client}System.out.println("STOP SERVER");

Page 7: Proiect Client-Server. Retele de Calculatoare

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

try{new HttpServer();}catch(Exception e){e.printStackTrace();}

}

}

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

class ProcesRequest extends Thread{

private PrintWriter outStr;private BufferedReader inStr;private Socket s;private DataOutputStream dout;private String iniContext;

ProcesRequest(Socket s, String iContext){try{

outStr = new PrintWriter(new OutputStreamWriter(s.getOutputStream()),true);inStr = new BufferedReader(new InputStreamReader(s.getInputStream()));dout = new DataOutputStream(s.getOutputStream());iniContext = iContext;this.s = s;start();

}catch(IOException e){System.err.println("EROARE CONECTARE: "+e.getMessage());}

}

public void run(){try{

String fileName=null;String request = inStr.readLine();System.out.print(request);if(request.lastIndexOf("GET")==0) fileName = interpretGET(request);else throw new Exception("BAU");byte[] data = readFile(fileName);dout.write(data);dout.flush();

}catch(IOException e){outStr.println("<HTML><BODY><P>403

Forbidden<P></BODY></HTML>");}catch(Exception

e2){outStr.println("<HTML><BODY><P>"+e2.getMessage()+"<P></BODY></HTML>");}finally{

try{s.close();}catch(Exception e){}}

}

private String interpretGET(String rqst) throws Exception{

Page 8: Proiect Client-Server. Retele de Calculatoare

StringTokenizer strT = new StringTokenizer(rqst);String tmp="";String fileN=iniContext;tmp=strT.nextToken();if(!tmp.equals("GET")) throw new Exception("Comanda GET invalida .");

tmp=strT.nextToken();if((tmp.equals("/")) || (tmp.endsWith("/"))) {

fileN = fileN+tmp+"index.htm";System.err.println("CERERE:"+fileN);return fileN;

}

fileN = fileN+ tmp;System.err.println("CERERE:"+fileN);return fileN;

}

private byte[] readFile(String fileN) throws Exception{fileN.replace('/','\\');File f = new File(fileN);if(!f.canRead()) throw new Exception("Fisierul "+fileN+" nu poate fi citit");FileInputStream fstr = new FileInputStream(f);byte[] data = new byte[fstr.available()];fstr.read(data);return data;

}}

Pentru verificarea programului anterior se va modifica variabila iniContextm din cadrul clasei HTTPServer, astfel încât aceasta să indice calea corectă catre contextul iniţial (directorul unde se află toate resursele pe care clientul le poate accesa).

3.5 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(){

Page 9: Proiect Client-Server. Retele de Calculatoare

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

Există situaţii în care în momentul in care se selveaza starea unui obiect prin serializare, nu se doreşte salvare tuturor starilor obiectului, respectiv nu se doreşte salvarea sau transmiterea anumitor parametri ai obiectului. Pentru a bloca serializarea unui atribut al unui obiect serializabil se utilizează cuvantul cheie transient.

a. Plecând de la listingul anterior relizati un program client şi un program server care transmit între ele obiecte de tip Pers.

b. Adăugaţi în cadrul clasei Pers în faţa liniei „ String nume; „ cuvântul cheie transient şi observaţi efectul acestei modificări.

Page 10: Proiect Client-Server. Retele de Calculatoare

3.6. 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 obiectului DatagramSocket, î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

Page 11: Proiect Client-Server. Retele de Calculatoare

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. Întrebări si probleme

a. In ce scop sunt utilizate clasele ServerSocket şi Socket?

b. Ce clasă este utilizată în java pentru a identifica un calculator din reţea ?

c. Utilizând programarea prin socketuri construiţi un program simplu de tip chat.