Connectivité, Web Services Orienté clients/serveur TCP et Bluetooth jean-michel Douin, douin au cnam point fr version : 26 Mars 2013 Notes de cours Avertissement :
Bibliographie utilisée http://developer.android.com/resources/index.html http://developer.android.com/reference/android/os/AsyncTask.html Un ensemble de tutoriels à lire http://www.vogella.de/android.html http://www.vogella.de/articles/AndroidPerformance/article.html
Clients et serveurs TCP Sommaire Clients et serveurs TCP Un Client, une requête Serveur TCP (HTTP,…) Clients Bluetooth Annexes, cf. NFP121 Format XML, API SAX Format JSON json.org
Client et serveur Serveurs TCP Exemples Requête
Serveurs En mode TCP Appels distants en mode TCP/IP Point à point avec accusé de réception telnet, ftp, http, …
URL … URL Uniform Resource Locator une adresse sur internet http://jfod.cnam.fr http le protocole //jfod.cnam.fr le nom de la ressource http://jfod.cnam.fr:8999/ds2438/mesures.html
Exemples clients / serveurs protocole Client1 Client2 Client3 Serveur Le client s’adresse au serveur Établit une connexion Le serveur satisfait ses clients Mode synchrone, analogue à l’appel d’une méthode locale
Appels distants protocole « maison » Client1 Client2 Client3 serveur JVM JVM Le contexte Client Java, ou autres Serveur en java ou autre maison : //serveur/….
Appels distants protocole http Client1 Client2 un navigateur serveur JVM JVM Le contexte Client Java(application comme applette), ou autres Un navigateur Serveur en java , ou autres http: //serveur/index.html Standard, universel …
Implémentation en Java Paquetage java.net Principales classes ServerSocket Socket InetAddress URLConnection … Quelques lignes de sources suffisent …
usage de java.net TCP/IP Client1 Serveur JVM JVM 2 classes essentielles Côté Serveur java.net.ServerSocket Méthode accept() sur une instance de la classe ServerSocket Côté Client java.net.Socket, java.net.SocketAddress Envoi sur une instance de la classe Socket de données
Connexion / Principes Serveur Client1 port JVM JVM port port Le Serveur attend une requête sur son port ServerSocket server = new ServerSocket(port) Socket socket = server.accept(); Dès la connexion établie, une instance de la classe Socket est engendrée sur un port temporaire Établir une connexion par le client est effectuée par Socket s = new Socket(Serveur, port)
2 exemples Serveur et client Au protocole « maison » Au protocole http Le serveur ne connaît que la commande « parle » et répond « bonjour » Tout autre commande est ignorée ! Au protocole http Seule la méthode GET /index.html HTTP1.0 est possible Un sous-ensemble donc …
Exemple 1 maison: //serveur parle Client1 Serveur bonjour JVM JVM bonjour Au protocole « maison » Le serveur ne connaît que la commande « parle » et répond « bonjour » Tout autre commande est ignorée ! Client java ou autre
Un serveur avec un protocole « maison » public class Serveur{ public static void main(String[] args) throws Exception{ ServerSocket serveur = new ServerSocket(5000); while(true) { Socket socket = serveur.accept(); BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream())); String cmd = in.readLine(); // parle !!! DataOutputStream out = new DataOutputStream( socket.getOutputStream()); if(cmd.equals("parle")){ out.write("bonjour\n".getBytes()); }else{ out.write("commande inconnue ?\n".getBytes()); } socket.close();
Le client « maison » public class Client{ public static void main(String[] args) throws Exception{ Socket socket = new Socket("vivaldi.cnam.fr", 5000); DataOutputStream out= new DataOutputStream( socket.getOutputStream()); out.write(args[0].getBytes()); out.write("\n".getBytes()); BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream())); System.out.println(in.readLine()); socket.close(); }
Un client « maison », telnet telnet localhost 5000 parle // frappe sans écho petit outil utile : tcpview
Exemple 2 Le protocole HTTP Mise en œuvre / démo Les méthodes GET, POST, …. Mise en œuvre / démo Usage d’un client telnet sur un site existant Une application Java cliente Un serveur en java
Protocole HTTP HyperText Transfer Protocol Les Méthodes Au dessus de TCP Les Méthodes GET /index.html HTTP/1.0 HEAD POST PUT DELETE TRACE CONNECT Voir http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
Côté serveur, méthode accept ServerSocket listen = new ServerSocket(HTTP_PORT); while(!stopped()){ try{ Socket socket = listen.accept(); handleRequest(socket); }catch(Exception e){ } listen.close(); listen.accept() est bloquant : au moins un client.
Exemple minimaliste ! Un serveur Web au complet Extrait de Un seul client, une seule requête ! Extrait de
OneShot Httpd by Hendrik, j2se public class OneShotHttpd { protected static File docRoot; public final static int HTTP_PORT = 8080; public static void main(String argv[]){ try{ docRoot = new File("."); ServerSocket listen = new ServerSocket(HTTP_PORT); Socket client = listen.accept(); BufferedReader is = new BufferedReader(new InputStreamReader(client.getInputStream())); DataOutputStream os = new DataOutputStream(client.getOutputStream()); String request = is.readLine(); StringTokenizer st = new StringTokenizer(request); if((st.countTokens() == 3) && st.nextToken().equals("GET")){ String filename = docRoot.getPath() + st.nextToken(); if(filename.endsWith("/") || filename.equals("")) filename += "index.html"; File file = new File(filename); sendDocument(os,file); } else System.err.println("400 Bad Request"); is.close(); os.close(); client.close(); }catch(IOException ioe){ System.err.println("Error: " + ioe.toString()); }}
OneShot « envoi du document » public static void sendDocument(DataOutputStream out, File file) throws IOException{ try{ BufferedInputStream in = new BufferedInputStream( new FileInputStream(file)); byte[] buf = new byte[1024]; int len; while((len = in.read(buf,0,1024)) != -1) { out.write(buf,0,len); } in.close(); catch(FileNotFoundException fnfe) System.err.println("404 Not Found");
OneShotHttpd.main(s); OneShot avec Android AsyncTask<Params, Progress, Result> Rappel Réalise une encapsulation d’un Thread et de l’accès à l’écran onPreExecute() Préambule, l’UI exécute cette méthode Void doInBackground(String… s){ } onProgressUpdate(Progress…p) Mise à jour de l’UI à la suite de l’appel de publishProgress onPostExecute(Result) Mise à jour de l’UI à la fin de la méthode doInBackground OneShotHttpd.main(s);
Côté serveur, un thread à chaque requête ServerSocket listen = new ServerSocket(HTTP_PORT); while(!stopped()){ try{ new Connection(listen.accept()); // création d’une instance }catch(Exception e){ } listen.close(); Chaque requête engendre la création d’une instance de la classe Connection Et chaque instance créée engendre à son tour un « Thread »
Côté serveur, à chaque Connection un Thread public class Connexion extends Thread{ … public Connexion(Socket s){ this.s = s; start(); } public void run(){ try{ BufferedReader is = new BufferedReader( new InputStreamReader(s.getInputStream())); DataOutputStream os = new DataOutputStream(s.getOutputStream()); // analyse du contenu au bon protocole HTTP // envoi du document
Côté serveur, accept « peut-être » ServerSocket listen = new ServerSocket(HTTP_PORT); listen.setSoTimeout(TIME_OUT); while(!stopped()){ try{ new Connection(listen.accept()); }catch(SocketTimeoutException e){ // ici délai de garde échu }catch(Exception e){ } listen.close(); Par défaut l’appel de accept est bloquant Méthode accept avec délai de garde exception SocketTimeoutException à l’échéance
Schéma avec Un Pool de Thread class WebServer { // 2004 JavaOneSM Conference | Session 1358 ThreadPool pool = new ThreadPool(7); public static void main(String[] args) { ServerSocket socket = new ServerSocket(80); while (true) { final Socket s = socket.accept(); Runnable r = new Runnable() { public void run() { BufferedReader is = new BufferedReader( new InputStreamReader(s.getInputStream())); DataOutputStream os = new DataOutputStream(s.getOutputStream()); // analyse du contenu au bon protocole HTTP // envoi du document } }; pool.execute(r); }}
Côté client Usage de telnet Requêtes GET et POST en Java
Requête GET avec telnet Un client telnet et un site du Cnam telnet jfod.cnam.fr 80 GET /index.html HTTP/1.0 ( frappe sans écho) HTTP/1.0 200 OK Last-Modified: Thu, 08 Feb 2007 14:55:29 GMT Date: Thu, 08 Mar 2007 10:33:55 GMT Server: Brazil/1.0 Content-Length: 7624 Content-Type: text/html Connection: close <HTML> <HEAD> <META http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> ….. Le résultat est retourné, le source du fichier index.html précédé de quelques informations
Requête GET en Java L’essentiel Classe java.net.URL Créer une URL Ouvrir une connexion Écrire et lire sur les flots associés Classe java.net.URL Classe java.net.URLConnection URL url = new URL("http://jfod.cnam.fr/index.html" ); URLConnection connection = url.openConnection();
Requête GET au complet public void testGET()throws Exception{ URL url = new URL("http://jfod.cnam.fr/index.html" ); URLConnection connection = url.openConnection(); BufferedReader in = new BufferedReader( new InputStreamReader(connection.getInputStream())); String inputLine = in.readLine(); while(inputLine != null){ System.out.println(inputLine); inputLine = in.readLine(); } in.close();
Requête GET avec paramètres public void testGET()throws Exception{ URL url = new URL("http://jfod.cnam.fr:8999/ds2438/?listAll=on" ); URLConnection connection = url.openConnection(); connection.setDoInput(true); BufferedReader in = new BufferedReader( new InputStreamReader(connection.getInputStream())); String inputLine = in.readLine(); while(inputLine != null){ System.out.println(inputLine); inputLine = in.readLine(); } in.close();
Requête POST connection.setDoOutput(true); URL url = new URL("http://jfod.cnam.fr/index.html"); URLConnection connection = url.openConnection(); connection.setDoInput(true); connection.setDoOutput(true); PrintWriter out = new PrintWriter(connection.getOutputStream()); out.print("listAll=on"); out.close(); BufferedReader in = new BufferedReader( new InputStreamReader(connection.getInputStream())); String inputLine = in.readLine(); while(inputLine != null){ System.out.println(inputLine); inputLine = in.readLine(); } in.close();
Classes utiles InetAddress URL URLConnection Adresse IP en « clair » Pour Uniform Resource Locator, sur le www URLConnection Une classe abstraite, super classe de toutes les classes établissant un lien entre une application et une URL Sous-classes HttpURLConnexion, JarURLConnection Patron Fabrique afin d’écrire son propre gestionnaire de protocole Voir http://monge.univ-mlv.fr/~roussel/RESEAUJAVA/java.url2.html Méthode URLConnection.setContentHandlerFactory( …);
En résumé Classe d’accès aux informations Lecture écriture en 7 étapes indépendante du protocole choisi Lecture écriture en 7 étapes Après avoir créé l’URL. Obtenir l’instance URLConnection. Installer les capacités en sortie de cette instance de URLConnection. Ouvrir le flot en entrée. Obtenir le flot en sortie. Écrire sur ce flot. Fermer celui-ci.
Android Android Toute requête doit être effectuée en dehors de l’UIThread: En conséquence, usage de AsyncTask Service + Thread
En « rappel » le cycle de vie
En Rappel: AsyncTask<Params, Progress, Result> Avec la classe, AsyncTask<Params, Progress, Result> http://developer.android.com/reference/android/os/AsyncTask.html Nous avons Un thread et l’accès à l’UIThread Un thread : pour le traitement en tâche de fond Une mise à jour de l’UI incluse Les paramètres génériques sont Params type des paramètres transmis au Thread Progress type des paramètres en cours de traitement transmis au Handler Result type du résultat pour l’appelant
Résumé: AsyncTask<Params, Progress, Result> Depuis l’UIThread création d’une instance et appel de la méthode execute Exemple new WebAsyncTask().execute(url1, url2, url3); AsyncTask<Params, Progress, Result> Réalise une encapsulation d’un Thread et de l’accès à l’écran Méthodes onPreExecute() Préambule, l’UI exécute cette méthode Result doInBackground(Params…p) Le contenu de cette méthode s’exécute dans un autre Thread onProgressUpdate(Progress…p) Mise à jour de l’UI à la suite de l’appel de publishProgress onPostExecute(Result) Mise à jour de l’UI à la fin de la méthode doInBackground
AsyncTask et réseau, exemples Lire une page sur le web HTTP, requête GET private class LirePageHTML extends AsyncTask<String,Void,String>{ Schéma onPreExecute Afficher une fenêtre d’informations, ProgressDialog doInBackGround Ouvrir une connexion, avec un échec éventuel onPostExecute Informer l’utilisateur
Lire une page Web www.cnam.fr Si j’ai la permission … de naviguer sur le web <uses-permission android:name="android.permission.INTERNET"></uses-permission> Une IHM simple L’accès au web est une opération coûteuse alors héritons de AsyncTask
Une classe interne héritant de AsyncTask protected String doInBackground(String... args) { builder = new StringBuilder(); try { HttpClient client = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(args[0]); HttpResponse response = client.execute(httpGet); StatusLine statusLine = response.getStatusLine(); int statusCode = statusLine.getStatusCode(); if (statusCode == 200) { HttpEntity entity = response.getEntity(); InputStream content = entity.getContent(); BufferedReader reader = new BufferedReader( new InputStreamReader(content)); String line; while ((line = reader.readLine()) != null) { builder.append(line); } } else {error = "Failed to download file";} } catch (Exception e) {error = e.getMessage();} return builder.toString();}
Autres exemples, essai d’architecture 1) Ouverture d’une connexion TCP Obtention d’un flux (OutputStream) Le flux reste ouvert 2) Envois de données sur le flux En fonction des opérations de l’utilisateur Règle : l’ouverture de la connexion et l’envoi de données se font sur des threads Une solution : Ouverture d’une connexion TCP : dans une sous classe d’AsyncTask Envoi de données : dans un thread en attente sur une file (SynchronousQueue) java.util.concurrent.SynchronousQueue
Un schéma d’une architecture possible UIThread 1) Obtention de la connexion AsyncTask Un Thread 2) Envoi de données offer take SynchronousQueue Réseau
Obtention de la connexion, AsyncTask protected void onPreExecute() { dialog = ….); } protected DataOutputStream doInBackground(String... args) { boolean result = true; try{ InetAddress addr = InetAddress.getByName(args[0]); int port = Integer.parseInt(args[1]); int timeout = Integer.parseInt(args[2]); SocketAddress sockaddr = new InetSocketAddress(addr, port); this.socket = new Socket(); socket.connect(sockaddr, timeout); out= new DataOutputStream(socket.getOutputStream()); }catch(Exception e){ erreur = e.getMessage();result= false;} return out;
Envoi de données depuis l’UIThread // ici à chaque clic des données sont envoyées vers la file // les boutons de l’IHM contiennent la commande à envoyer au serveur public void onClickCommand(View v){ String cmd = v.getContentDescription().toString() + "\n"; try { sender.offer(cmd.getBytes()); } catch (Exception e) { } UIThread offer
Envois de données, vers la file public class Sender extends Thread{ private BlockingQueue<byte[]> queue; public Sender(){ queue = new SynchronousQueue<byte[]>(); this.start(); } public boolean offer(byte[] cmd){ return queue.offer(cmd); public void close(){ this.interrupt(); public void run(){ while(!isInterrupted()){ try { byte[] cmd = queue.take(); // lecture bloquante out.write(cmd); }catch (Exception e) { }}
Une connexion Bluetooth Recherche d’un périphérique bluetooth aux alentours Hypothèse : Nous connaissons l’adresse physique du périphérique 00-19-EF-01-17-9C (obtenu ipconfig /all sous windows) Cette recherche doit s’effectuer dans un thread Alors héritons de AsyncTask Au clic new ConnexionBT().execute("00:19:EF:01:17:9C"); private class ConnexionBT extends AsyncTask<String,String,BluetoothSocket>{ protected void onPreExecute() { protected BluetoothSocket doInBackground(String... args) { protected void onPostExecute(BluetoothSocket btSocket) {
onPreExecute : Patience, doInBackground : Recherche protected void onPreExecute() { dialog = ProgressDialog.show(BTClientActivity.this, "connexion Bluetooth", " patientez ", true); } protected BluetoothSocket doInBackground(String... args) { try{ this.btDevice = btAdapter.getRemoteDevice(args[0]); btSocket = btDevice.createRfcommSocketToServiceRecord(MY_UUID); btAdapter.cancelDiscovery(); btSocket.connect(); }catch(Exception e){ erreur = e.getMessage(); btSocket= null; return btSocket;
onPostExecute(BluetoothSocket btSocket) protected void onPostExecute(BluetoothSocket btSocket) { try { os = btSocket.getOutputStream(); // } catch (IOException e) { erreur = e.getMessage(); e.printStackTrace(); }finally{ dialog.dismiss(); }
Première conclusion Serveurs et service Client et lecture de flux au format XML et JSON XML, SAX rappels cf. supports NFP121 JSON
SAX XML Document SAX Objects Parser startDocument Parser startElement <?xml version=“1.0”?> Parser startDocument <addressbook> </addressbook> Parser startElement <person> </person> <name>John Doe</name> Parser startElement & characters <email>jdoe@yahoo.com</email> Parser startElement & characters Parser endElement <person> </person> Parser startElement <name>Jane Doe</name> Parser startElement & characters Parser startElement & characters <email>jdoe@mail.com</email> Parser endElement Parser endElement & endDocument
Implémenter les Handlers d'évènements du parseur DefaultHandler Il implémente ces différents Handler avec des méthodes vides, de sorte que l'on peut surcharger seulement celles qui nous intéressent.
org.xml.sax.ContentHandler Toutes les applications SAX doivent implanter un ContentHandler Méthodes : public void startDocument() throws SAXException public void endDocument() throws SAXException public void startElement(String nspURI, String localName, String qName, Attributes atts) throws SAXException public void characters(char[] ch, int start, int length) throws SAXException …
Un exemple: les stations Vélib http://www.velib.paris.fr http://www.velib.paris.fr/service/carto http://www.velib.paris.fr/service/stationdetails/{number}
http://www.velib.paris.fr/service/carto <carto> <markers> <marker name="00901 - STATION MOBILE 1" number="901" address="ALLEE DU BELVEDERE PARIS 19 - 0 75000 Paris -" fullAddress="ALLEE DU BELVEDERE PARIS 19 - 0 75000 Paris - 75000 PARIS" lat="48.892745582406675" lng="2.391255159886939" open="1" bonus="0"/> <marker name="03011 - TURBIGO" number="3011" address="55 RUE TURBIGO -" fullAddress="55 RUE TURBIGO - 75003 PARIS" lat="48.86558781525867"lng="2.356094545731025" open="1" bonus="0"/> Analyse des attributs de la balise marker en SAX -> méthode startElement
http://www.velib.paris.fr/service/stationdetails/3011 <station> <available>21</available> <free>10</free> <total>31</total> <ticket>1</ticket> </station> Analyse du contenu de la balise station en SAX -> des méthodes startElement, endElement, characters
Les stations Vélib: suite Les Classes, un premier découpage StationVelib, toutes les infos d’une station, (adresse, longitude, latitude,…) InfoStation, les informations comme le nombre de vélo et d’emplacements disponibles,... ListeDesStationsVelib La gestion de la liste des stations http://www.velib.paris.fr/service/carto http://www.velib.paris.fr/service/stationdetails/{number}
Initialisation du « parser » class ParserXML extends DefaultHandler { public ParserXML(InputStream in) throws Exception{ SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); XMLReader xr = sp.getXMLReader(); xr.setContentHandler(this); xr.parse(new InputSource(in)); }
startElement un extrait // Création d’une instance de la classe StationVelib // depuis XML en Java public void startElement(String uri, String localName, String qName,Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); if(qName.equals("marker")){ StationVelib station = new StationVelib(); station.setName(attributes.getValue("name")); station.setNumber(Integer.parseInt(attributes.getValue("number"))); station.setAddress(attributes.getValue("address")); station.setLatitude(Double.parseDouble(attributes.getValue("lat"))); station.setLongitude(Double.parseDouble(attributes.getValue("lng"))); }
Une Info à chaque Station class ParserXML extends DefaultHandler { private StringBuffer current; // la valeur public ParserXML(int ID){ URL url = new URL(URL_VELIB_INFO + ID); SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp; sp = spf.newSAXParser(); XMLReader xr = sp.getXMLReader(); xr.setContentHandler(this); xr.parse(new InputSource(url.openStream())); }
A chaque noeud public void startElement (String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); current = new StringBuffer(); } public void characters (char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); current.append(new String(ch, start, length)); public void endElement (String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); if(qName.equals("available")){ available = Integer.parseInt(current.toString()); …
XML plutôt verbeux, JSON plutôt concis JSON JavaScript Object Notation www.json.org/ JSONArray JSONObject JSONArray jsonArray = new JSONArray(); JSONObject jsonObject = new JSONObject(); jsonObject.put( "name", "paul"); jsonObject.put("number", 1900); jsonArray.put(jsonObject); // pierre idem System.out.println(jsonArray.toString(2));
JSON, exemple paul et pierre [ { "name": "paul", "number": 1900 }, "name": "pierre", } ]
JSON Lecture InputStream in = …; Reader r = new InputStreamReader(in); BufferedReader br = new BufferedReader(r); StringBuffer sb = new StringBuffer(); String str; while((str = br.readLine()) != null) { sb.append(str); } r.close(); JSONArray jsonarray = new JSONArray(sb.toString()); JSONObject jsonObject = (JSONObject)jsonStations.get(i); Auditeur a = new Auditeur (); a.setName(jsonObject.getString("name")); …
StationVelib, lecture JSON JSONArray jsonStations = new JSONArray(sb.toString()); for(int i=0; i< jsonStations.length(); i++){ JSONObject jsonObject = (JSONObject)jsonStations.get(i); StationVelib st = new StationVelib(); st.setName(jsonObject.getString("name")); st.setNumber(jsonObject.getInt("number")); st.setAddress(jsonObject.getString("address")); st.setFullAddress(jsonObject.getString("fullAddress")); st.setLatitude(jsonObject.getDouble("lat")); st.setLongitude(jsonObject.getDouble("lng")); st.setOpen(jsonObject.getBoolean("open")); st.setBonus(jsonObject.getBoolean("bonus"));
Démonstration
Conclusion Discussions