Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
laboratoare:laborator09 [2015/05/04 04:05]
Andrei Roșu-Cojocaru [Resurse Utile]
laboratoare:laborator09 [2015/05/05 02:00] (current)
Andrei Roșu-Cojocaru
Line 1: Line 1:
-====== Laborator 09. Implementarea Serviciilor de Rețea ​în Android ​======+====== Laborator 09. Implementarea Serviciilor de Rețea ======
  
 În Android, prin intermediul serviciilor,​ sunt puse la dispoziție funcționalități care pot fi accesate de către dispozitivele care se găsesc în aceeași rețea locală. De obicei, o astfel de abordare se folosește pentru stabilirea de conexiuni punct la punct peste care pot fi dezvoltare jocuri la care pot participa mai mulți utilizatori,​ aplicații de partajare a unor resurse sau pentru transmiterea de mesaje. Totodată, pot fi expuse și servicii ale unor alte dispozitive din cadrul rețelei locale cum ar fi: calculatoare,​ imprimante, televizoare,​ ceasuri inteligente,​ acestea putând fi astfel accesate la distanță. În Android, prin intermediul serviciilor,​ sunt puse la dispoziție funcționalități care pot fi accesate de către dispozitivele care se găsesc în aceeași rețea locală. De obicei, o astfel de abordare se folosește pentru stabilirea de conexiuni punct la punct peste care pot fi dezvoltare jocuri la care pot participa mai mulți utilizatori,​ aplicații de partajare a unor resurse sau pentru transmiterea de mesaje. Totodată, pot fi expuse și servicii ale unor alte dispozitive din cadrul rețelei locale cum ar fi: calculatoare,​ imprimante, televizoare,​ ceasuri inteligente,​ acestea putând fi astfel accesate la distanță.
Line 680: Line 680:
  
 ===== Gestiunea unei Conexiuni către Servicii Disponibile ===== ===== Gestiunea unei Conexiuni către Servicii Disponibile =====
 +
 +Fiecare dispozitiv poate fi avea în același timp rolul de:
 +  * server pentru serviciile pe care le-a înregistrat,​ putând primi solicitări din partea clienților;​
 +  * client pentru serviciile pe care le-a descoperit, putând trimite solicitări către servere.
 +
 +În acest sens, va trebui menținut un obiect server, care va gestiona comunicația cu clienții.
 +
 +Totodată, vor trebui menținuți mai mulți clienți, reprezentând canalele de comunicație de tip punct-la-punct care pot fi de două tipuri:
 +  * canale de comunicație pentru solicitări primite (clienți);
 +  * canale de comunicație pentru servicii descoperite (servere).
 +
 +<code java>
 +public class NetworkServiceDiscoveryOperations {
 +
 +  private ChatServer chatServer = null;
 +  ​
 +  private List<​ChatClient>​ communicationToServers ​  = null;
 +  private List<​ChatClient>​ communicationFromClients = null;
 +  ​
 +  // ...
 +  ​
 +}
 +</​code>​
 +
 +Actualizarea acestor obiecte va fi realizată mai ales în situația în care un serviciu este rezolvat, respectiv un serviciu este pierdut.
 +
 +Obiectul de tip **''​ChatServer''​** este de fapt un fir de execuție pe care se instanțiază un obiect de tip ''​ServerSocket'',​ așteptându-se,​ în bucla principală,​ solicitări de la clienți, actualizându-se corespunzător lista conținând canalele de comunicație pentru solicitările primite.
 +
 +De remarcat faptul că a fost definită și o metodă pentru oprirea firului de execuție și închiderea obiectului ''​ServerSocket'',​ aceasta urmând a fi apelată la deînregistrarea serviciului respectiv.
 +
 +<file java ChatServer.java>​
 +public class ChatServer extends Thread {
 +
 +  private NetworkServiceDiscoveryOperations networkServiceDiscoveryOperations = null;
 +  ​
 +  private ServerSocket serverSocket = null;
 +  ​
 +  public ChatServer(NetworkServiceDiscoveryOperations networkServiceDiscoveryOperations,​ int port) {
 +    this.networkServiceDiscoveryOperations = networkServiceDiscoveryOperations;​
 +    try {
 +      serverSocket = new ServerSocket(port);​
 +    } catch (IOException ioException) {
 +      Log.e(Constants.TAG,​ "An error has occurred during server run: " + ioException.getMessage());​
 +      if (Constants.DEBUG) {
 +        ioException.printStackTrace();​
 +      }
 +    }
 +  }
 +  ​
 +  public void run() {
 +    try {
 +      while (!Thread.currentThread().isInterrupted()) {
 +        Socket socket = serverSocket.accept();​
 +        List<​ChatClient>​ communicationFromClients = networkServiceDiscoveryOperations.getCommunicationFromClients();​
 +        communicationFromClients.add(new ChatClient(null,​ socket));
 +        networkServiceDiscoveryOperations.setCommunicationFromClients(communicationFromClients);​
 +      }
 +    } catch (IOException ioException) {
 +      Log.e(Constants.TAG,​ "An error has occurred during server run: " + ioException.getMessage());​
 +      if (Constants.DEBUG) {
 +        ioException.printStackTrace();​
 +      }
 +    }
 +  }
 +  ​
 +  public void stopThread() {
 +    interrupt();​
 +    try {
 +      if (serverSocket != null) {
 +        serverSocket.close();​
 +      }
 +    } catch (IOException ioException) {
 +      Log.e(Constants.TAG,​ "An error has occurred while closing server socket: " + ioException.getMessage());​
 +      if (Constants.DEBUG) {
 +        ioException.printStackTrace();​
 +      }
 +    }
 +  }
 +}
 +</​file>​
 +
 +Obiectul de tip **''​ChatClient''​** definește un canal de comunicație bidirecțional între două mașini / dispozitive,​ care poate fi creat pe baza unuei adrese și a unui port (conexiune către server) sau pe baza altui canal de comunicație (conexiune de la client).
 +
 +Acesta încapsulează două fire de execuție:
 +  - ''​SendThread''​ - pentru trimiterea de mesaje;
 +  - ''​ReceiveThread''​ - pentru primirea de mesaje.
 +Atât mesajele trimise cât și mesajele trimise sunt plasate și în interfața grafică (dacă aceasta este vizibilă) dar și într-un obiect membru al clasei.
 +
 +Au fost definite metode atât pentru pornirea cât și pentru oprirea firelor de execuție. Astfel, firele de execuție pentru trimiterea / primirea de mesaje rulează cât timp nu sunt întrerupte.
 +
 +<file java ChatClient.java>​
 +public class ChatClient {
 +  private Socket ​ socket ​ = null;
 +  ​
 +  private SendThread ​   sendThread ​   = null;
 +  private ReceiveThread receiveThread = null;
 +  ​
 +  private BlockingQueue<​String>​ messageQueue ​       = new ArrayBlockingQueue<​String>​(Constants.MESSAGE_QUEUE_CAPACITY);​
 +  private List<​Message> ​        ​conversationHistory = new ArrayList<​Message>​();​
 +  ​
 +  public ChatClient(final String host, final int port) {
 +    new Thread(new Runnable() {
 +      @Override
 +      public void run() {
 +        try {
 +          socket = new Socket(host,​ port);
 +        } catch (IOException ioException) {
 +          Log.e(Constants.TAG,​ "An exception has occurred while creating the socket: " + ioException.getMessage());​
 +          if (Constants.DEBUG) {
 +            ioException.printStackTrace();​
 +          }
 +        }
 +        startThreads();​
 +      }
 +    }).start();
 +  }
 +  ​
 +  public ChatClient(Context context, Socket socket) {
 +    this.socket ​ = socket;
 +    ​
 +    startThreads();​
 +  }
 +  ​
 +  public void sendMessage(String message) {
 +    try {
 +      messageQueue.put(message);​
 +    } catch (InterruptedException interruptedException) {
 +      Log.e(Constants.TAG,​ "An exception has occurred: " + interruptedException.getMessage());​
 +      if (Constants.DEBUG) {
 +        interruptedException.printStackTrace();​
 +      }
 +    }
 +  }
 +  ​
 +  private class SendThread extends Thread {
 +    @Override
 +    public void run() {
 +      PrintWriter printWriter = Utilities.getWriter(socket);​
 +      if (printWriter != null) {
 +        try {
 +          while (!Thread.currentThread().isInterrupted()) {
 +            String content = messageQueue.take();​
 +            if (content != null) {
 +              printWriter.println(content);​
 +              printWriter.flush();​
 +              Message message = new Message(content,​ Constants.MESSAGE_TYPE_SENT);​
 +              conversationHistory.add(message);​
 +              // display the message in the graphic user interface
 +            }
 +          }
 +        } catch (InterruptedException interruptedException) {
 +          Log.e(Constants.TAG,​ "An exception has occurred: " + interruptedException.getMessage());​
 +          if (Constants.DEBUG) {
 +            interruptedException.printStackTrace();​
 +          }
 +        }
 +      }
 +    }
 +    ​
 +    public void stopThread() {
 +      interrupt();​
 +    }
 +  }
 +  ​
 +  private class ReceiveThread extends Thread {
 +    @Override
 +    public void run() {
 +      BufferedReader bufferedReader = Utilities.getReader(socket);​
 +      if (bufferedReader != null) {
 +        try {
 +          while (!Thread.currentThread().isInterrupted()) {
 +            String content = bufferedReader.readLine();​
 +            if (content != null) {
 +              Message message = new Message(content,​ Constants.MESSAGE_TYPE_RECEIVED);​
 +              conversationHistory.add(message);​
 +              // display the message in the graphic user interface
 +            }
 +          }
 +        } catch (IOException ioException) {
 +          Log.e(Constants.TAG,​ "An exception has occurred: " + ioException.getMessage());​
 +          if (Constants.DEBUG) {
 +            ioException.printStackTrace();​
 +          }
 +        }
 +      }
 +    }
 +    ​
 +    public void stopThread() {
 +      interrupt();​
 +    }
 +  }
 +  ​
 +  public void setConversationHistory(List<​Message>​ conversationHistory) {
 +    this.conversationHistory = conversationHistory;​
 +  }
 +  ​
 +  public List<​Message>​ getConversationHistory() {
 +    return conversationHistory;​
 +  }
 +  ​
 +  public void startThreads() {
 +    sendThread = new SendThread();​
 +    sendThread.start();​
 +    ​
 +    receiveThread = new ReceiveThread();​
 +    receiveThread.start();​
 +  }
 +  ​
 +  public void stopThreads() {
 +    sendThread.stopThread();​
 +    receiveThread.stopThread();​
 +    try {
 +      if (socket != null) {
 +        socket.close();​
 +      }
 +    } catch (IOException ioException) {
 +      Log.e(Constants.TAG,​ "An exception has occurred while closing the socket: " + ioException.getMessage());​
 +      if (Constants.DEBUG) {
 +        ioException.printStackTrace();​
 +      }
 +    }
 +  }
 +}
 +</​file>​
  
 ===== Activitate de Laborator ===== ===== Activitate de Laborator =====
 +
 +Se dorește implementarea unei aplicații Android de tip mesagerie instantanee bazată pe legături de tip punct-la-punct stabilite între dispozitive mobile care oferă un serviciu de acest tip și dispozitive mobile care l-au descoperit, putându-l accesa în cadrul rețelei locale.
 +
 +Funcționalitățile pe care le implementează această aplicație Android sunt:
 +  - înregistrarea / deînregistrarea unui serviciu de mesagerie instantanee,​ pe un anumit port pe care îl specifică;
 +  - pornirea / oprirea operației de descoperire a serviciilor în rețeaua locală;
 +  - conectarea / deconectarea la un serviciu descoperit sau la un dispozitiv mobil care a accesat serviciul înregistrat;​
 +  - transmiterea de mesaje în cadrul legăturii de tip punct-la-punct.
 +
 +Va fi utilizat următorul cod de culori pentru butoanele care au atașată funcționalitatea de înregistrare / deînregistrare,​ respectiv pornire / oprire:
 +  * roșu - serviciul este oprit;
 +  * verde - serviciul este pornit.
 +
 +{{ :​laboratoare:​laborator09:​chatservice01.png?​nolink&​400 }}
 +
 +În cazul în care descoperirea serviciilor este pornită, vor fi întreținute două liste:
 +  * lista cu serviciile descoperite,​ disponibile în cadrul altor dispozitive mobile din rețeaua locală;
 +  * lista conținând conversațiile cu alte dispozitive mobile care au accesat serviciul înregistrat.
 +
 +{{ :​laboratoare:​laborator09:​chatservice02.png?​nolink&​400 }}
 +
 +În situația în care descoperirea serviciilor este oprită, lista cu serviciile descoperite va fi vidă.
 +
 +{{ :​laboratoare:​laborator09:​chatservice03.png?​nolink&​400 }}
 +
 +Comunicația se va realiza în cadrul unei ferestre dedicate, în care pot fi vizualizate diferit mesajele trimise și mesajele primite. Din cadrul acestei interfețe grafice se poate reveni în orice moment la panoul de control.
 +
 +{{ :​laboratoare:​laborator09:​chatservice04.png?​nolink&​400 }}
 +
 +**1.** În contul Github personal, să se creeze un depozit denumit '​Laborator09'​. Inițial, acesta trebuie să fie gol (nu trebuie să bifați nici adăugarea unui fișier ''​README.md'',​ nici a fișierului ''​.gitignore''​ sau a a fișierului ''​LICENSE''​).
 +
 +**2.** Să se cloneze în directorul de pe discul local conținutul depozitului la distanță de la [[https://​www.github.com/​pdsd2015/​Laborator09|]]. ​
 +
 +În urma acestei operații, directorul Laborator09 va trebui să se conțină directoarele ''​labtasks''​ și ''​solutions''​. ​
 +
 +<​code>​
 +student@pdsd2015:​~$ git clone https://​www.github.com/​pdsd2015/​Laborator09
 +</​code>​
 +
 +**3.** Să se încarce conținutul descărcat în cadrul depozitului '​Laborator09'​ de pe contul Github personal. <​code>​
 +student@pdsd2015:​~$ cd Laborator09
 +student@pdsd2015:​~/​Laborator09$ git remote add Laborator09_perfectstudent https://​github.com/​perfectstudent/​Laborator09
 +student@pdsd2015:​~/​Laborator09$ git push Laborator09_perfectstudent master
 +</​code>​
 +
 +**4.** Să se importe în mediul integrat de dezvoltare Eclipse Luna SR1a (4.4.1) proiectul ''​ChatServiceAndroidNSD''​ sau ''​ChatServiceJmDNS''​ din directorul ''​labtasks''​.
 +
 +**5.** Să se ruleze aplicația Android pe două dispozitive mobile care să se găsească în aceeași rețea.
 +
 +<note tip>
 +În cazul Genymotion, se pot crea două imagini pentru două dispozitive mobile (virtuale) de același tip, care vor putea fi rulate separat, fiecare dintre acestea primind adrese Internet diferite din intervalul ''​192.168.56.101''​ → ''​192.168.56.254''​.
 +
 +{{ :​laboratoare:​laborator09:​genymotion01.png?​nolink&​600 }}
 +
 +În VirtualBox (> versiunea 4.3.26), se verifică faptul că dispozitivele virtuale comunică între ele prin intermediul unei interfețe de rețea, configurată să folosească NAT (Network Address Translation). ​
 +
 +În acest sens, trebuie realizate următoarele operații:
 +
 +  * se va crea o rețea NAT în cadrul VirtualBox (//File// → //​Preferences//​ sau Ctrl + G)
 +
 +{{ :​laboratoare:​laborator09:​virtualbox01.png?​nolink&​400 }}
 +
 +{{ :​laboratoare:​laborator09:​virtualbox02.png?​nolink&​400 }}
 +
 +  * în configurația aferentă fiecărui dispozitiv virtual (//​Machine//​ → //​Settings//​ sau Ctrl + S), se va selecta //NAT Network// folosind rețeaua astfel definită pentru interfața //Adapter 2//
 +
 +{{ :​laboratoare:​laborator09:​virtualbox03.png?​nolink&​400 }}
 +</​note>​
 +
 +Acestea vor putea rula instanțe diferite ale aplicației Android, fiecare folosind __**o denumire proprie pentru serviciu**__ (la valoarea generică ''​Constants.SERVICE_NAME''​ definită în pachetul ''​ro.pub.cs.systems.pdsd.lab09.chatservice.general''​ se sufixează în mod automat un șir de caractere generat aleator, astfel încât aceasta să fie unică în rețeaua locală).
 +
 +Se verifică faptul că fiecare aplicație Android rulează pe un dispozitiv diferit:
 +
 +{{ :​laboratoare:​laborator09:​genymotion02.png?​nolink&​600 }}
 +
 +În Logcat, se pot utiliza filtre diferite pentru fiecare dintre dintre instanțele aplicației Android, astfel încât să se faciliteze procesul de depanare.
 +
 +**6.** Să se implementeze rutina pentru trimiterea mesajelor, pe metoda ''​run()''​ a firului de execuție ''​SendThread''​ din clasa ''​ChatClient''​ a pachetului ''​ro.pub.cs.systems.pdsd.lan09.chatservice.networkservicediscoveryoperations''​.
 +
 +Metoda va rula cât timp firul de execuție nu este întrerupt, informație oferită de metoda ''​isInterrupted()''​.
 +
 +<code java>
 +while (!Thread.currentThread().isInterrupted()) {
 +  // ...
 +}
 +</​code>​
 +
 +Pe fiecare iterație, se vor realiza următoarele operații:
 +  - se va prelua un mesaj din coada ''​messageQueue''​ (de tip ''​BlockingQueue<​String>''​);​ metoda ''​take()''​ este blocantă, în așteptarea unei valori care să poată fi furnizată - astfel, în momentul în care firul de execuție este întrerupt, se va genera o excepție de tipul ''​InterruptedException''​ care trebuie tratată;
 +  - în cazul unui mesaj nenul, sunt realizate următoarele operații:
 +    - se trimite mesajul pe canalul de comunicație corespunzător (se apelează metoda ''​println()''​ a obiectului de tip ''​PrintWriter''​);​
 +    - se construiește un obiect de tip ''​Message'',​ format din:
 +      - conținut: valoarea propriu-zisă a mesajului (șirul de caractere);
 +      - tip: mesaj transmis (''​Constants.MESSAGE_TYPE_SENT''​);​
 +    - se atașează mesajul istoricului conversației (obiectul ''​conversationHistory''​ este de tipul ''​List<​Message>''​) - în acest fel, chiar dacă interfața grafică nu este vizibilă la momentul în care este trimis mesajul, conversația poate fi încărcată atunci când se întâmplă acest lucru;
 +    - se afișează mesajul în fragmentul de tip ''​ChatConversationFragment''​ (prin intermediul metodei ''​appendMessage()''​),​ dacă acesta este asociat activității la momentul respectiv <code java>
 +if (context != null) {
 +  ChatActivity chatActivity = (ChatActivity)context;​
 +  FragmentManager fragmentManager = chatActivity.getFragmentManager();​
 +  Fragment fragment = fragmentManager.findFragmentByTag(Constants.FRAGMENT_TAG);​
 +  if (fragment instanceof ChatConversationFragment && fragment.isVisible()) {
 +    ChatConversationFragment chatConversationFragment = (ChatConversationFragment)fragment;​
 +    chatConversationFragment.appendMessage(message);​
 +  }
 +}
 +</​code>​
 +
 +**7.** Să se implementeze rutina pentru primirea mesajelor, pe metoda ''​run()''​ a firului de execuție ''​ReceiveThread''​ din clasa ''​ChatClient''​ a pachetului ''​ro.pub.cs.systems.pdsd.lan09.chatservice.networkservicediscoveryoperations''​.
 +
 +Metoda va rula cât timp firul de execuție nu este întrerupt, informație oferită de metoda ''​isInterrupted()''​.
 +
 +<code java>
 +while (!Thread.currentThread().isInterrupted()) {
 +  // ...
 +}
 +</​code>​
 +
 +Pe fiecare iterație, se vor realiza următoarele operații:
 +  - se primește mesajul pe canalul de comunicație corespunzător (se apelează metoda ''​readLine()''​ a obiectului de tip ''​BufferedReader''​);​
 +  - în cazul unui mesaj nenul, sunt realizate următoarele operații:
 +    - se construiește un obiect de tip ''​Message'',​ format din:
 +      - conținut: valoarea propriu-zisă a mesajului (șirul de caractere);
 +      - tip: mesaj transmis (''​Constants.MESSAGE_TYPE_RECEIVED''​);​
 +    - se atașează mesajul istoricului conversației (obiectul ''​conversationHistory''​ este de tipul ''​List<​Message>''​);​
 +    - se afișează mesajul în fragmentul de tip ''​ChatConversationFragment''​ (prin intermediul metodei ''​appendMessage()''​),​ dacă acesta este asociat activității la momentul respectiv.
 +
 +**8.** ​ Să se încarce modificările realizate în cadrul depozitului '​Laborator09'​ de pe contul Github personal, folosind un mesaj sugestiv. <​code>​
 +student@pdsd2015:​~/​Laborator09$ git add *
 +student@pdsd2015:​~/​Laborator09$ git commit -m "​implemented taks for laboratory 09"
 +student@pdsd2015:​~/​Laborator09$ git push Laborator09_perfectstudent master
 +</​code>​
  
 ===== Resurse Utile ===== ===== Resurse Utile =====
laboratoare/laborator09.1430701518.txt.gz · Last modified: 2015/05/04 04:05 by Andrei Roșu-Cojocaru
CC Attribution-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0