O problemă des întâlnită de un dispozitiv mobil este descoperirea de funcționalități care pot fi accesate într-o rețea locală vizitată. De obicei, se dorește stabilirea de conexiuni punct la punct peste care pot fi tehnologiile clasice de tipul client/server sau RPC. Totodată, pot fi expuse și servicii clasice ale unor alte dispozitive din cadrul rețelei locale cum ar fi: calculatoare, imprimante, televizoare, ceasuri inteligente, acestea putând fi astfel accesate fără configurare prealabilă.
Serviciile din rețea pot fi implementate folosind două variante:
Operațiile utilizate în implementarea serviciilor de rețea sunt:
Cele mai multe servicii accesibile prin rețeaua locală sunt descrise prin intermediul unui tip care are de obicei forma _<protocol_nivel_aplicatie>._<protocol_nivel_transport>.
, unde:
<protocol_nivel_aplicatie>
poate fi standard sau definit de utilizator;<protocol_nivel_transport>
este de regulă tcp
sau udp
(sau variații ale acestora).Se poate consulta lista cu tipurile de servicii rezervate, gestionată de IANA (Autoritatea Internațională pentru Numere Alocate). Unele tipuri de servicii au definite și porturile pe care pot fi accesate.Pentru rezervarea unui astfel de tip de serviciu, este necesară o solicitare prealabilă, care poate fi aprobată sau respinsă, după caz.
Pentru Android NSD, pentru cele mai multe servicii nespecifice, se poate folosi tipul _http._tcp.
(se folosește transferul de date prin HTTP folosind protocolul de transport TCP).
În cazul JmDNS, trebuie să se precizeze faptul că serviciul rulează local, definindu-se un tip corespunzător aplicației care implementează funcționalitatea respectivă (_<denumire_aplicatie>._tcp.local.
).
Se poate crea un advertisement de serviciu sub Linux prin crearea unui fișier de configurare, folosiți un alt nume în loc de perfectdesktop:
$ cat /etc/avahi/services/chat.service <?xml version="1.0" standalone='no'?> <!DOCTYPE service-group SYSTEM "avahi-service.dtd"> <service-group> <name>Chat-perfectdesktop</name> <service> <type>_chatservice._tcp</type> <port>5003</port> </service> </service-group>
Se restartează severul de zeroconf cu comanda
# /etc/init.d/avahi-daemon restart
Apoi se poate verifica cu tcpdump traficul specific DNS-SD care este generat:
#tcpdump -ni eth0 -s0 -w 'file1.pcap' 'udp port 5353'
Traficul capturat în file1.pcap poate fi examinat cu wireshark file1.pcap
. Dacă ați rulat tcpdump pe telefon, este necesară aducerea fișierului .pcap pe desktop cu jurorul comanzii adb pull /sdcard/file1.pcap .
Din alt terminal, se poate lansa o căutare de servicii de tipul chatservice:
#avahi-browse -rk _chatservice._tcp
Iar în tcpdump se observă primirea query-urilor pentru serviciu, și răspunsul desktop-ului. Atenție, acesta nu este un serviciu care rulează pe portul 5003, ci doar anunțul(advertisement) pentru serviciu.
Inițializarea mediului de lucru presupune instanțierea unor obiecte care gestionează operațiile ce pot fi realizate la nivelul serviciilor de rețea, respectiv a unor obiecte ascultător pentru evenimentele care pot surveni în cadrul acestora.
Întrucât versiunea curentă a JmDNS (3.4.1) oferă o bibliotecă sub forma unei arhive .jar care nu poate fi procesată în momentul în care este transformată în formatul .dex
, este necesar ca aceasta să fie prelucrată, în sensul menținerii acelor clase care sunt strict necesare pentru gestiunea serviciilor de rețea, și anume cele din pachetul javax.jmdns
.
student@eg106:~$ jar xf jmdns.jar student@eg106:~$ jar cfm jmdns.jar META-INF/MANIFEST.MF javax/
JmDNS folosește pachete de tip multicast pentru a gestiona serviciile disponibile în rețea. Politica Android este de a dezactiva implicit astfel de transferuri, pentru a optimiza bateria, motiv pentru care această funcționalitate trebuie activată temporar, doar pe parcursul aplicației Android. În acest sens, trebuie obținut mutext-ul corespunzător operațiilor de acest tip, care va fi eliberat ulterior:
public class ChatActivity extends Activity { // ... protected WifiManager wifiManager = null; @Override protected void onCreate(Bundle state) { super.onCreate(state); // ... WifiManager wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE); multicastLock = wifiManager.createMulticastLock(Constants.TAG); multicastLock.setReferenceCounted(true); multicastLock.acquire(); } @Override protected void onDestroy() { super.onDestroy(); // ... if (multicastLock != null) { multicastLock.relase(); multicastLock = null; } } // ... }
Configurarea JmDNS presupune crearea unei instanțe a unui obiect de tipul JmDNS, care oferă funcționalități referitoare la gestiunea serviciilor existente doar în cadrul rețelei locale. Se utilizează metoda statică create(InetAddress, String), care primește ca parametri:
getIpAddress()
apelată pe obiectul ConnectionInfo
asociat obiectului care gestoniează interfața pentru comunicația prin intermediul rețelei fără fir;try { WifiManager wifiManager = ((ChatActivity)context).getWifiManager(); InetAddress address = InetAddress.getByAddress( ByteBuffer.allocate(4).putInt( Integer.reverseBytes(wifiManager.getConnectionInfo().getIpAddress()) ).array() ); String name = address.getHostName(); Log.i(Constants.TAG, "address = " + address + " name = " + name); jmDns = JmDNS.create(address, name); } catch (IOException ioException) { Log.e(Constants.TAG, "An exception has occurred: " + ioException.getMessage()); if (Constants.DEBUG) { ioException.printStackTrace(); } }
JmDNS
trebuie să se realizeze pe firul de execuție al comunicației prin rețea, în caz contrar generându-se o excepție de tipul android.os.NetworkOnMainThreadException
.
API-ul pe care îl pune la dispoziție un astfel de obiect este asincron, astfel încât metodele apelate în cadrul claselor ascultător pentru diferite evenimente (înregistrare, descoperire, rezolvare) sunt executate în contextul unor fire de execuție dedicate, așa cum trebuie procedat în condițiile unor operații ce implică comunicația prin rețea.
Se observă că de regulă aceste metode primesc parametrii de tip:
Resusele asociate obiectului de tip JmDNS
trebuie eliberate în momentul în care acesta nu mai este necesar (aplicația Android este distrusă):
try { if (jmDns != null) { jmDns.close(); jmDns = null; } } catch (IOException ioException) { Log.e(Constants.TAG, "An exception has occurred: " + ioException.getMessage()); if (Constants.DEBUG) { ioException.printStackTrace(); } }
În fișierul AndroidManifest.xml
, permisiunile care trebuie oferite aplicației vizează:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ro.pub.cs.systems.eim.lab08.chatservice" android:versionCode="1" android:versionName="1.0"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- ... --> </manifest>
Pentru înregistrarea unui serviciu, se apelează metoda registerService(ServiceInfo) din clasa JmDNS
, ulterior serviciul putând fi accesat din cadrul altor mașini fizice / dispozitive mobile.
Pentru deînregistrarea unui serviciu, se poate apela una din metodele unregisterService(ServiceInfo), respectiv unregisterAllServices() din clasa JmDNS
.
ServiceInfo
, ce reține informații precum denumirea și tipul serviciului, adresa și portul mașinii / dispozitivului pe care va fi disponibil (ce pot fi preluate, din cadrul unui obiect de tip ServerSocket
).
public void registerNetworkService() throws Exception { chatServer = new ChatServer(this); ServerSocket serverSocket = chatServer.getServerSocket(); if (serverSocket == null) { throw new Exception("Could not get server socket"); } chatServer.start(); ServiceInfo serviceInfo = ServiceInfo.create( Constants.SERVICE_TYPE, Constants.SERVICE_NAME, port, Constants.SERVICE_DESCRIPTION ); if (jmDns != null && serviceInfo != null) { serviceName = serviceInfo.getName(); jmDns.registerService(serviceInfo); } } public void unregisterNetworkService() { if (jmDns != null) { jmDns.unregisterAllServices(); } chatServer.stopThread(); // ... }
Operațiile de pornire / oprire a descoperirii serviciilor disponibile trebuie să aibă în vedere resursele afectate precum și impactul asupra altor funcționalități cum ar fi viteza de transfer prin rețea, respectiv autonomia.
De regulă, pornirea operației de descoperire a serviciilor disponibile este realizată pe metoda onResume()
, adică din momentul în care activitatea este vizibilă.
Similar, oprirea operației de descoperire a serviciilor disponibile este realizată pe metoda onPause()
, adică din momentul în care activitatea nu este vizibilă.
Totodată, este recomandat să se pună la dispoziția utilizatorilor elemente în cadrul interfeței grafice prin intermediul cărora aceste operații să poată fi controlate prin interacțiunea cu cei care folosesc aplicația Android.
Pornirea operației de descoperire a serviciilor disponibile se face prin intermediul metodei addServiceListener(String, ServiceListener) din clasa JmDNS
care primește ca parametri:
ServiceListener
, care reacționează la evenimentele legate de serviciile găsite / pierdute.
Oprirea operației de descoperire a serviciilor disponibile se face prin intermediul metodei removeServiceListener(String, ServiceListener) din clasa JmDNS
care primește ca parametru un obiect ascultător de tipul ServiceListener
, care reacționează la evenimentele legate de serviciile găsite / pierdute.
Ca atare, informațiile cu privire la găsirea / pierderea serviciilor în rețeaua locală vor fi furnizate numai între apelurile metodelor addServiceListener()
, respectiv removeServiceListener()
.
public void startNetworkServiceDiscovery() { if (jmDns != null && serviceListener != null) { jmDns.addServiceListener(Constants.SERVICE_TYPE, serviceListener); } } public void stopNetworkServiceDiscovery() { if (jmDns != null && serviceListener != null) { jmDns.removeServiceListener(Constants.SERVICE_TYPE, serviceListener); } // ... }
Pentru obiectul de tip ServiceListener
trebuie implementate metodele apelate în mod automat în momentul în care un serviciu este găsit, respectiv este pierdut:
requestServiceInfo(String, String)
din cadrul obiectului de tip JmDNS
.
serviceResolved()
), prin apelul metodei getServiceInfo(String, String), cu precizarea că timpul său de execuție poate fi considerabil:
ServiceInfo serviceInfo = serviceEvent.getDNS().getServiceInfo( serviceEvent.getType(), serviceEvent.getName() );
Un obiect de tipul ServiceInfo conține, printre altele, și informații cu privire la:
ServiceListener serviceListener = new ServiceListener() { // ... @Override public void serviceAdded(ServiceEvent serviceEvent) { if (!serviceEvent.getType().equals(Constants.SERVICE_TYPE)) { Log.i(Constants.TAG, "Unknown Service Type: " + serviceEvent.getType()); } else if (serviceEvent.getName().equals(serviceName)) { Log.i(Constants.TAG, "The service running on the same machine has been discovered: " + serviceName); } else if (serviceEvent.getName().contains(Constants.SERVICE_NAME_SEARCH_KEY)) { Log.i(Constants.TAG, "The service should be resolved now: " + serviceEvent); jmDns.requestServiceInfo(serviceEvent.getType(), serviceEvent.getName()); } } @Override public void serviceRemoved(final ServiceEvent serviceEvent) { ServiceInfo serviceInfo = serviceEvent.getInfo(); if (serviceInfo == null) { Log.e(Constants.TAG, "Service Info for Service is null!"); return; } String[] hosts = serviceInfo.getHostAddresses(); String host = null; if (hosts.length != 0) { host = hosts[0]; if(host.startsWith("/")) { host = host.substring(1); } } int port = serviceInfo.getPort(); // ... ArrayList<NetworkService> discoveredServices = chatActivity.getDiscoveredServices(); NetworkService networkService = new NetworkService(serviceEvent.getName(), host, port, -1); if (discoveredServices.contains(networkService)) { int index = discoveredServices.indexOf(networkService); discoveredServices.remove(index); chatActivity.setDiscoveredServices(discoveredServices); } } };
Rezolvarea unui serviciu descoperit anterior implică obținerea de informații suplimentare cu privire la acesta.
Astfel, pentru un serviciu pentru care se cunoaște doar tipul și denumirea, se poate determina, prin intermediul multi-cast DNS, adresa și portul la care acesta este disponibil, putând fi accesat.
În cadrul metodei serviceAdded()
din ascultătorul de tip ServiceListener
, în situația în care denumirea serviciului corespunde unui anumit șablon (deși o astfel de condiție nu este întotdeauna necesară), se invocă metoda requestServiceInfo(String, String) care primește ca parametri:
Service serviceListener = new ServiceListener() { // ... @Override public void serviceAdded(ServiceEvent serviceEvent) { // ... if (serviceEvent.getName().contains(Constants.SERVICE_NAME_SEARCH_KEY)) { Log.i(Constants.TAG, "The service should be resolved now: " + serviceEvent); jmDns.requestServiceInfo(serviceEvent.getType(), serviceEvent.getName()); } } };
În situația în care operația de rezolvare a serviciului a fost realizată cu succes, se apelează metoda serviceResolved(ServiceEvent) în cadrul aceluiași obiect ascultător (de tip ServiceListener
). Aceasta primește ca parametru un obiect ServiceEvent
care încapsulează informații cu privire la serviciul rezolvat, sub forma unui obiect de tip ServiceInfo
. Și în această situație se va gestiona corespunzător lista de servicii descoperite în rețeaua locală, obținându-se și un canal de comunicație către parametrii determinați.
ServiceListener serviceListener = new ServiceListener() { @Override public void serviceResolved(ServiceEvent serviceEvent) { if (serviceEvent.getName().equals(serviceName)) { Log.i(Constants.TAG, "The service running on the same machine has been discovered."); return; } ServiceInfo serviceInfo = serviceEvent.getInfo(); if (serviceInfo == null) { Log.e(Constants.TAG, "Service Info for Service is null!"); return; } String[] hosts = serviceInfo.getHostAddresses(); String host = null; if (hosts.length == 0) { Log.e(Constants.TAG, "No host addresses returned for the service!"); return; } host = hosts[0]; if(host.startsWith("/")) { host = host.substring(1); } int port = serviceInfo.getPort(); ArrayList<NetworkService> discoveredServices = chatActivity.getDiscoveredServices(); NetworkService networkService = new NetworkService(serviceEvent.getName(), host, port, Constants.CONVERSATION_TO_SERVER); if (!discoveredServices.contains(networkService)) { discoveredServices.add(networkService); communicationToServers.add(new ChatClient(null, host, port)); chatActivity.setDiscoveredServices(discoveredServices); } Log.i(Constants.TAG, "A service has been discovered on " + host + ":" + port); } // ... };
Fiecare dispozitiv poate fi avea în același timp rolul de:
Î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:
public class NetworkServiceDiscoveryOperations { private ChatServer chatServer = null; private List<ChatClient> communicationToServers = null; private List<ChatClient> communicationFromClients = null; // ... }
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.
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(); } } } }
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.
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) { 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(); } } if (socket != null) { startThreads(); } } public ChatClient(Context context, Socket socket) { this.socket = socket; if (socket != null) { 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(); } } } }
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:
Va fi utilizat următorul cod de culori pentru butoanele care au atașată funcționalitatea de înregistrare / deînregistrare, respectiv pornire / oprire:
În cazul în care descoperirea serviciilor este pornită, vor fi întreținute două liste:
În situația în care descoperirea serviciilor este oprită, lista cu serviciile descoperite va fi vidă.
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.
1. În contul Github personal, să se creeze un depozit denumit 'Laborator08'. 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/eim-lab/Laborator08.
În urma acestei operații, directorul Laborator08 va trebui să se conțină directoarele labtasks
și solutions
.
student@eg106:~$ git clone https://www.github.com/eim-lab/Laborator08.git
3. Să se încarce conținutul descărcat în cadrul depozitului 'Laborator08' de pe contul Github personal.
student@eg106:~$ cd Laborator08 student@eg106:~/Laborator08$ git remote add Laborator08_perfectstudent https://github.com/perfectstudent/Laborator08 student@eg106:~/Laborator08$ git push Laborator08_perfectstudent master
4. Să se importe în mediul integrat de dezvoltare Android Studio proiectul 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.
5a.
<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
.
Pentru interfața WiFi, genymotion nu poate folosi decât NAT sau Bridge (manual Genymotion). Pentru acest laborator, este necesară configurația bridge.
Î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ă Bridge.
În acest sens, trebuie realizate următoarele operații:
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.eim.lab08.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:
5b. Să se utilizeze utilitarul ZeroConf Browser, deja instalat pe emulatoare pentru a identifica serviciile pornite în rețea. Dacă emulatoarele sunt în aceeași rețea cu o mașină Linux, se poate rula avahi-browse -rk _chatservice._tcp
pentru a vizualiza serviciile pornite
5c. La momentul publicării cu succes a serviciului (când butonul devine verde), să se colecteze adresele IP locale pe care serviciul devine vizibil. Aceasta se face în funcția OnCreateView a listener-ului care primește schimbarea stării serviciului (ServiceDiscoveryStatusButtonListener).
... if (view == null) { view = inflater.inflate(R.layout.fragment_chat_network_service, parent, false); } // List all IPs where the server is visible Enumeration interfaces = null; try { interfaces = NetworkInterface.getNetworkInterfaces(); } catch (IOException e){ Log.e(Constants.TAG, "Could not query interface list: " + e.getMessage()); if (Constants.DEBUG) { e.printStackTrace(); } } String IPs = ""; while(interfaces.hasMoreElements()) { NetworkInterface n = (NetworkInterface) interfaces.nextElement(); Enumeration ee = n.getInetAddresses(); while (ee.hasMoreElements()) { InetAddress i = (InetAddress) ee.nextElement(); if (i instanceof Inet4Address) { if(IPs.length() > 0) IPs += ", "; IPs += i.getHostAddress().toString(); } } } TextView LocalIPs = (TextView)view.findViewById(R.id.service_discovery_local_addr); LocalIPs.setText(IPs); return view;
Verificați afișarea adreselor pe care serviciul devine disponibil. Utilitarele de explorare servicii (ZeroconfBrowser, avahi-browser) vor vedea serviciul pe una dintre aceste adrese.
5d. La apăsarea butonului de publicare serviciu, se va modifica titlul aplicatiei pentru a reflecta starea curentă a publicării:
În register:
chatActivity.setTitle(serviceInfo.getName()); Log.i(Constants.TAG, "Register service " + serviceInfo.getName() + ":" + serviceInfo.getTypeWithSubtype() + ":" + serviceInfo.getPort() );
În unregister:
chatActivity.setTitle("Chat Service JmDNS");
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.eim.lab08.chatservice.networkservicediscoveryoperations
.
Metoda va rula cât timp firul de execuție nu este întrerupt, informație oferită de metoda isInterrupted()
.
while (!Thread.currentThread().isInterrupted()) { // ... }
Pe fiecare iterație, se vor realiza următoarele operații:
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ă;println()
a obiectului de tip PrintWriter
);Message
, format din:Constants.MESSAGE_TYPE_SENT
);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;ChatConversationFragment
(prin intermediul metodei appendMessage()
), dacă acesta este asociat activității la momentul respectiv 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); } }
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.eim.lab08.chatservice.networkservicediscoveryoperations
.
Metoda va rula cât timp firul de execuție nu este întrerupt, informație oferită de metoda isInterrupted()
.
while (!Thread.currentThread().isInterrupted()) { // ... }
Pe fiecare iterație, se vor realiza următoarele operații:
readLine()
a obiectului de tip BufferedReader
);Message
, format din:Constants.MESSAGE_TYPE_RECEIVED
);conversationHistory
este de tipul List<Message>
);ChatConversationFragment
(prin intermediul metodei appendMessage()
), dacă acesta este asociat activității la momentul respectiv.8. Să se examineze folosind tcpdump/wireshark schimbul de mesaje între dispozitive
ping
că telefoanele sunt în contact IP între ele, și cu masina host (Linux)ip ro
. avahi-browse -ac
, identificați serviciile și stațiile disponibile în rețeaua localăZeroconfBrowser
, identificați serviciile și stațiile disponibile în rețeaua localăavahi-browse -rk _chatservice._tcp
, asigurați-vă că nu există instanțe ale serviciului pe vreuna dintre mașinitcpdump -ni any 'udp port 5353' -w ./dnssd.pcap
9. Folosind instrucțiunile din secțiunea Zeroconf sub Linux de mai sus
while true; do nc -v -l -p 5003; done
nc <adresă> <port>
pentru a conversa cu un server descoperit la
adresa:port
(pe Android sau Linux).
10. Să se încarce modificările realizate în cadrul depozitului 'Laborator08' de pe contul Github personal, folosind un mesaj sugestiv.
student@eg106:~/Laborator08$ git add * student@eg106:~/Laborator08$ git commit -m "implemented tasks for laboratory 08" student@eg106:~/Laborator08$ git push Laborator08_perfectstudent master
Multicast DNS
Service Name and Transport Protocol Port Number Registry
Android Tutorial on Connecting Devices Wirelessly
JmDNS - Project Official Page
JmDNS - API Documentation
JmDNS - Library Download
Tutorial on Using JmDNS on Android
How to discover device (or service) on Android?
Service discovery using JmDNS in Eclipse: Wifi State Machine errors