Protocoale de comunicatie de nivel aplicatie

Intr-o arhitectura distribuita aplicatiile schimba informatii intre ele prin intermediul mesajelor. Protocoalele TCP si UDP ofera mecanismele pentru transportul acestor mesaje. O data ajuns la destinatie mesajul trebuie interpretat.

Protocolul de comunicatie de nivel aplicatie descrie tipurile de conversatii care pot avea loc intre doua aplicatii in cadrul unui sistem distribuit, specificand: structura mesajului, modul de encodare al acestuia precum si modul de interpretare.

Exemple de protocoale de nivel aplicatie:

  • Hypertext Transfer Protocol (HTTP) - este protocolul de nivel aplicatie folosit in WEB. Acest protocol este folosit in comunicarea dintre un server WEB si un client (browser) WEB;
  • Simple Mail Transfer Protocol (SMTP) - este protocolul de nivel aplicatie folosit in trasmiterea de emailuri;
  • File Transfer Protocol (FTP) - este protocolul de nivel aplicatie folosit pentru transferul de fisiere;

In aplicatiile pe care la implementam putem folosi protocoale de comunicatie existente sau in anumite situatie trebuie sa implementam propriul nostru protocol de nivel aplicatie. In implementarea unui protocol de nivel aplicatie trebuie luate in considerare o serie de aspecte precum:

  • este o comunicare broadcast sau de tip unu la unu
  • este o comunicare ce necesita mentinerea unei conexiuni permanente (cu stare) sau dupa fiecare secventa de comunicare conexiunea poate fi inchisa (fara stare);
  • este nevoie de un protocol de transport sigur sau nu (TCP sau UDP);
  • mesajele vor fi transmise in format binar sau in format text;
  • in ce mod vor fi formatate mesajele (am putea folosi de exemplu XML sau JSON pentru a structura mesajele de tip text);

Dialogul client - server

Cand doua entitati comunica intre ele are loc un schimb de informatii. De exemplu o aplicatie client va cere de la server o anumita resursa, sau va cere acestuia sa execute o anumita operatie. Serverul va incerca sa execute actiunea ceruta si va raspunde inapoi. Un astel de schimb de informatii se numeste dialog client - server (eng. client - server roudtrip).

Cu cat vor fi definite mai multe astfel de dialoguri in cadrul unui unui protocol cu cat atat acesta va fi mai lent. De exemplu protocolul HTTP are un singur dialog ce contine o cerere de resursa de la client si un raspuns cu resursa ceruta de la server. Pe de alta parte protocolul SMTP defineste mai multe dialogur ce trebuie sa aiba loc in procesul de transmitere a unui email.

Formatul datelor

Avem doua solutii posibile pentru formatul datelor transmise: binar si text.

Formatul binar

Formatul binar are avanajul de a fi mai compact - si deci mai rapid. Dezavantajul este dat de dificultatea detectarii de erori de catre programator intrucat mesajele binare sunt mai greu de citit si interpretat. Exemple de protocoale ce folosesc formatul binar: DNS, protocoale de transmise stream-uri video si audio.

Delimitarea mesajelor in formatul binar
Pentru a delimita mesajele binare se poate utiliza o conventie in care orice mesaj se termina cu o secventta de octeti specifica. O alta solutie este ca mesajul sa contina in patea de inceput si lungimea mesajului. O a treia solutie (in cazul in care aplicatia permite) este sa se utilizeze o lungime fixa de mesaje.

Formatul text

In formatul text totul este transmis sub forma de caractere. De exemplu un intreg 123 va fi transmis ca o secventa formata din caracterele '1', '2' si '3'. Pentru a trimite date binare pe un protocol text atunci se va putea face o encodare Base64 astfel incat acestea sa pota fi transmise ca si caractere ASCII.

Delimitarea mesajelor in formatul text
In mod uzual mesajele text sunt delimitate printr-un marcaj de linie noua. Principala complicatie care apare este data de modul diferit in care sistemele de operare interpreteaza marcajul de linie noua. In sistemele Linux se utilizeaza caracterul '\n'. Windows si alte sisteme de operare utilizeaza secventa ā€œ\r\nā€. Cea mai uzuala forma de delimitare este ā€œ\r\nā€.

Exemplu protocol

In continuare este exemplificat modul de implementare in Java a unui protocol de nivel aplicatie folosind socket-uri (comunicatie TCP) si mesaje de tip text. Exemplul este preluat din tutorialele oficiale java (Link).

Aplicatia este formata din doua aplicatii independente. Aplicatia client implemntata in cadrul clasei KnockKnockClient si aplicatia server formata din clasele KnockKnockServer si KnockKnockProtocol.

Clasa KnockKnockProtocol implementeaza principalele functionalitati ale protocolului de comunicatie intre client si server. Tine cont de starea curenta a comunicatiei si returneaza diferite texte in functie de aceasta stare.

Clasa KnockKnockServer implementeaza mecansimul de comunicare cu clientul. Asteapta o conexiune, accepta conexiunea de la client, construieste fluxurile de intrare-iesire, accepta mesaje de la client pe care le transfera catre obiectul de tip KnockKnockProtocol si apoi transmite raspunsul inapoi catre client.

Aplicatia server

import java.net.*;
import java.io.*;
 
public class KnockKnockServer {
    public static void main(String[] args) throws IOException {
 
        if (args.length != 1) {
            System.err.println("Usage: java KnockKnockServer <port number>");
            System.exit(1);
        }
 
        int portNumber = Integer.parseInt(args[0]);
 
        try ( 
            ServerSocket serverSocket = new ServerSocket(portNumber);
            Socket clientSocket = serverSocket.accept();
            PrintWriter out =
                new PrintWriter(clientSocket.getOutputStream(), true);
            BufferedReader in = new BufferedReader(
                new InputStreamReader(clientSocket.getInputStream()));
        ) {
 
            String inputLine, outputLine;
 
            // Initiate conversation with client
            KnockKnockProtocol kkp = new KnockKnockProtocol();
            outputLine = kkp.processInput(null);
            out.println(outputLine);
 
            while ((inputLine = in.readLine()) != null) {
                outputLine = kkp.processInput(inputLine);
                out.println(outputLine);
                if (outputLine.equals("Bye."))
                    break;
            }
        } catch (IOException e) {
            System.out.println("Exception caught when trying to listen on port "
                + portNumber + " or listening for a connection");
            System.out.println(e.getMessage());
        }
    }
}
import java.net.*;
import java.io.*;
 
public class KnockKnockProtocol {
    private static final int WAITING = 0;
    private static final int SENTKNOCKKNOCK = 1;
    private static final int SENTCLUE = 2;
    private static final int ANOTHER = 3;
 
    private static final int NUMJOKES = 5;
 
    private int state = WAITING;
    private int currentJoke = 0;
 
    private String[] clues = { "Turnip", "Little Old Lady", "Atch", "Who", "Who" };
    private String[] answers = { "Turnip the heat, it's cold in here!",
                                 "I didn't know you could yodel!",
                                 "Bless you!",
                                 "Is there an owl in here?",
                                 "Is there an echo in here?" };
 
    public String processInput(String theInput) {
        String theOutput = null;
 
        if (state == WAITING) {
            theOutput = "Knock! Knock!";
            state = SENTKNOCKKNOCK;
        } else if (state == SENTKNOCKKNOCK) {
            if (theInput.equalsIgnoreCase("Who's there?")) {
                theOutput = clues[currentJoke];
                state = SENTCLUE;
            } else {
                theOutput = "You're supposed to say \"Who's there?\"! " +
                "Try again. Knock! Knock!";
            }
        } else if (state == SENTCLUE) {
            if (theInput.equalsIgnoreCase(clues[currentJoke] + " who?")) {
                theOutput = answers[currentJoke] + " Want another? (y/n)";
                state = ANOTHER;
            } else {
                theOutput = "You're supposed to say \"" + 
                clues[currentJoke] + 
                " who?\"" + 
                "! Try again. Knock! Knock!";
                state = SENTKNOCKKNOCK;
            }
        } else if (state == ANOTHER) {
            if (theInput.equalsIgnoreCase("y")) {
                theOutput = "Knock! Knock!";
                if (currentJoke == (NUMJOKES - 1))
                    currentJoke = 0;
                else
                    currentJoke++;
                state = SENTKNOCKKNOCK;
            } else {
                theOutput = "Bye.";
                state = WAITING;
            }
        }
        return theOutput;
    }
}

Aplicatia client

import java.io.*;
import java.net.*;
 
public class KnockKnockClient {
    public static void main(String[] args) throws IOException {
 
        if (args.length != 2) {
            System.err.println(
                "Usage: java EchoClient <host name> <port number>");
            System.exit(1);
        }
 
        String hostName = args[0];
        int portNumber = Integer.parseInt(args[1]);
 
        try (
            Socket kkSocket = new Socket(hostName, portNumber);
            PrintWriter out = new PrintWriter(kkSocket.getOutputStream(), true);
            BufferedReader in = new BufferedReader(
                new InputStreamReader(kkSocket.getInputStream()));
        ) {
            BufferedReader stdIn =
                new BufferedReader(new InputStreamReader(System.in));
            String fromServer;
            String fromUser;
 
            while ((fromServer = in.readLine()) != null) {
                System.out.println("Server: " + fromServer);
                if (fromServer.equals("Bye."))
                    break;
 
                fromUser = stdIn.readLine();
                if (fromUser != null) {
                    System.out.println("Client: " + fromUser);
                    out.println(fromUser);
                }
            }
        } catch (UnknownHostException e) {
            System.err.println("Don't know about host " + hostName);
            System.exit(1);
        } catch (IOException e) {
            System.err.println("Couldn't get I/O for the connection to " +
                hostName);
            System.exit(1);
        }
    }
}