Socket-uri de rețea

De la WikiLabs
Jump to navigationJump to search

Limbajul Java, prin setul de biblioteci API, pune la dispoziție suport nativ pentru aplicații în rețea. Cel mai simplu mod de a realiza comunicația între două mașini este prin utilizarea socket-urilor (soclurilor) de comunicație. Socket-urile sunt mecanisme de comunicație în rețea (dezvoltate la Universitatea Berckley din California) care pot folosi orice protocol de comunicație, deși, în general, se folosesc protocoalele din stiva TCP/IP.

Orice calculator conectat la Internet trebuie să aibă o adresă prin care să fie identificat, adresă care se numește adresă IP (Internet Protocol). O adresă IPv4 (versiunea 4) este formată din 4 octeți, iar reprezentarea ei externă se face printr-o secvență de 4 numere separate prin puncte (ex: 192.168.0.1). Pe lângă această reprezentare, se mai pot folosi nume simbolice grupate în domenii, iar corespondențele dintre adresele IP și numele simbolice sunt stocate într-o bază de date distribuită, accesată prin serviciul numelor de domenii (DNS – Domain Name Service).

În Java, clasele și interfețele de programare în rețea sunt cuprinse în pachetul java.net. O adresă IP se reprezintă prin clasa InetAddress care conține atât adresa IP (în format numeric) cât și adresa simbolică corespunzătoare. Această clasă nu are un constructor public, un obiect din această clasă se poate crea la apelul uneia din metodele statice ale clasei: getByName(), getLocalHost() sau getAllByName() care retunează referința la obiectul creat. De exemplu, prototipul metodei getByName() este următorul:

public InetAddress getByName (String host) throws UnknownHostException;

Argumentul metodei (host) este un șir de caractere care reprezintă numele stației dat ca nume simbolic (de exemplu "arh.pub.ro") sau ca formă textuală a adresei IP ("141.85.252.94").

Socket-urile se pot folosi într-unul din două moduri posibile:

  • socluri neconectate;
  • socluri conectate (orientate pe conexiune).

În cazul soclurilor neconectate, se transmit pachete în protocolul UDP (User Datagram Protocol) între procese între care nu există o legătură permanentă, iar pachetele de date (datagramele - datagrams) sunt independente între ele și conțin adresa destinației pentru a putea fi rutate.

În cazul socket-urilor conectate, se stabilește mai întâi o legătură între două socket-uri aparținînd unor procese distincte (posibil plasate în stații diferite, conectate printr-o rețea de comunicație). Transmisia pe socluri conectate folosește protocolul TCP (Transmission Control Protocol) care este un protocol fiabil (cu verificări ale datelor transmise şi recepționate), spre deosebire de protocolul UDP care este un protocol nefiabil.

Pentru ca mai multe aplicații să poată avea acces la rețea în același timp, pachetele pentru fiecare aplicație trebuie să fie separate. Acest lucru se face cu ajutorul porturilor de comunicații. Un port este reprezentat de un număr pe 16 biți și fiecare aplicație folosește un port unic pentru a comunica în rețea. O conexiune are două porturi: cel local, alocat de sistemul de operare la inițierea conexiunii, și cel al server-ului, care este constant, în funcție de aplicație. Fiecare protocol standard are asociat un port conform unei liste menținute de cei de la Internet Assigned Numbers Authority. Ca exemple:

  • port 20 - File Transfer Protocol (date)
  • port 21 - File Transfer Protocol (control)
  • port 21 - Secure Shell (SSH)
  • port 25 - Simple Mail Transfer Protocol (SMTP)
  • port 80 - Hyper Text Transfer Protocol (HTTP)
  • etc.

Lista completă este disponibilă pe http://www.wikipedia.org.

Într-un sistem de operare Linux, porturile 0 - 1023 sunt rezervate de kernel și nu sunt accesibile unui utilizator. Din Linux, pentru a verifica ce porturi sunt deschise (utilizate), se poate folosi comanda nmap (daca este instalată):

nmap arh.pub.ro

Socket de client

În general, o conexiunea de rețea se realizează dinspre o stație (host) numită client, către o altă stație, numită server. Pentru ca un client să se poată conecta la un server, are nevoie de două informații: adresa serverului și portul pe care ascultă aplicația la care se dorește conectarea. Clasa care realizează acest lucru în Java se numește java.net.Socket. Orice obiect de tip Socket, după instanțiere, are asociate două stream-uri:

  • un InputStream de pe care se pot citi date venite de la celălalt capăt al conexiunii, accesibil cu metoda Socket.getInputStream();
  • un OutputStream pe care se pot scrie date pentru a fi trimise la celălalt capăt al conexiunii, accesibil cu metoda Socket.getOutputStream().
import java.net.*;
import java.io.*;

public class Client{

public static void main(String[] _args){
    try{
        //connect to arh.pub.ro on the HTTP port
        Socket _clientSocket = new Socket("arh.pub.ro", 80);
        OutputStream _outputStream = _clientSocket.getOutputStream();
        InputStream _inputStream = _clientSocket.getInputStream();

        _outputStream.write("GET /java.txt\n".getBytes());

        int _char;
        while((_char = _inputStream.read()) != -1){
            System.out.print((char)_char);
        }

        _clientSocket.close();
    }catch(IOException _ioe){
        System.out.println("Communication problem: " + _ioe.getMessage());
    }
}

}

Socket de server

Rolul unui socket de server este de a accepta conexiuni pe la unul sau mai multi clienți. Clasa care implementează acest comportament este java.net.ServerSocket. Pentru ca un ServerSocket să poată funcționa, are nevoie de o singură informație, și anume portul pe care să accepte conexiuni. Pentru a efectua o conectare concretă, clasa conține o metodă numită accept().

Atenție: Metoda ServerSocket.accept() este blocantă! Asta implică faptul că odată intrat în metodă, programul se blochează până în momentul în care un client încearcă să se conecteze. În acel moment abia, metoda accept() întoarce un obiect de tip Socket asociat acelei conexiuni. În același timp, un client nu se poate conecta atâta timp cât serverul nu este blocat în metoda accept().


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

public class Server{

public static void main(String[] _args){
    try{
        // start a new server on port 9000 (must be higher or equal to 1024)
        ServerSocket _serverSocket = new ServerSocket(9000);

        // listen to an incoming connection and creating a communication socket
        // accept() is blocking
        System.out.print("Listening for inc. connections... ");
        Socket _socket = _serverSocket.accept();
        System.out.println("connected!");

        // get the streams associated with the socket
        // output one always first
        OutputStream _outputStream = _socket.getOutputStream();
        InputStream _inputStream = _socket.getInputStream();

        // read the input char by char until newline char (\n)
        // we could do the same thing easier using an InputStreamReader
        // and a BufferedReader
        String _command = "";
        char _char;
        while((_char = (char)_inputStream.read()) != '\n'){
            _command = _command + _char;
        }

        // send a response depending on the received string 
        if(_command.equals("GET /java.txt")){
            _outputStream.write("Congratulations, you have received a message from a Java server!\n".getBytes());
        }else{
            _outputStream.write("Congratulations, you still received a message from a Java server, even though you sent the wrong text!\n".getBytes());
        }

        _socket.close();
        _serverSocket.close();
    }catch(IOException _ioe){
        System.out.println("Communication problem: " + _ioe.getMessage());
    }
}

}

Puteți verifica funcționarea acestui server modificând clientul pentru a se conecta la "localhost", pe portul 9000 și rulând ambele programe pe același calculator, în console diferite.

Pentru a putea programa un server care să administreze mai mulți clienți simultan, trebuie obligatoriu create mai multe fire de execuție (thread-uri).