====== WebSockets a Java ====== {{tag> #DamMp09Uf3 #DamMp09Uf03}} ---- ==== Exemples ==== {{ ::websockets_-_exemple_java.zip |}} {{ ::websockets_-_exemple_android.zip |}} ==== WebSockets ==== Tradicionalment cada aplicació s’ha implementat la comunicació per sockets Actualment hi ha un protocol estàndard: WebSockets que implementa la comunicació amb sockets TCP i facilita el desenvolupament d’aplicacions que comuniquen diversos clients a través d’un servidor. - Faciliten el desenvolupament d’apps que es comuniquen sense bloquejos. - Funcionen per events, no cal esperar una lectura o escriptura. - Poden funcionar amb ports estàndard a través del web. ==== WebSockets, llibreria websockets ==== Com que Java no els implementa directament, cal utilitzar alguna llibreria que ho faci. N’hi ha vàries, l’exemple fa servir: - [[http://tootallnate.github.io/Java-WebSocket/|Java WebSockets]] (també a [[https://github.com/TooTallNate/Java-WebSocket|Github]]) Necessitem les següents llibreries: - [[https://github.com/TooTallNate/Java-WebSocket/releases|Java-WebSocket-1.5.3.jar]] - [[https://repo1.maven.org/maven2/org/slf4j/slf4j-api/2.0.3/slf4j-api-2.0.3.jar|slf4j-api-2.0.3.jar]] - [[https://repo1.maven.org/maven2/org/slf4j/slf4j-simple/2.0.3/slf4j-simple-2.0.3.jar|slf4j-simple-2.0.3.jar]] ==== WebSockets, iniciar i tancar la connexió ==== Per crear un servidor de WebSockets cal extendre la classe ‘WebSocketServer’ i definir les funcions d’events java.lang.System.setProperty("jdk.tls.client.protocols", "TLSv1,TLSv1.1,TLSv1.2"); WsServidor socket = new WsServidor(port); socket.start(); System.out.println("WsServidor funciona al port: " + socket.getPort()); // Lògica del programa Servidor … if (socket != null) socket.stop(1000); Per crear un client de WebSockets cal extendre la classe ‘WebSocketClient’ i definir les funcions d’events int port = 8888; String host = "localhost"; String location = "ws://" + host + ":" + port; try { client = new WsClient(new URI(location), (Draft) new Draft_6455()); client.connect(); } catch (URISyntaxException e) { e.printStackTrace(); System.out.println("Error: " + location + " no és una direcció URI de WebSocket vàlida"); } // Lògica del programa Servidor … if (socket != null) socket.close(); ==== WebSockets, funció ‘onMessage’ al servidor ==== Quan el servidor rep un missatge, executa la funció ‘**onMessage**’ i ens diu quin client l’ha enviat a través de ‘conn’. Pot rebre un “String” o un “ByteBuffer” @Override public void onMessage(WebSocket conn, String message) { broadcast(message); conn.send("Missatge enviat"); } @Override public void onMessage(WebSocket conn, ByteBuffer message) { } La funció ‘**broadcast**’ envia un missatge a tots els clients connectats ‘**conn.send**’ envia un missatge al client que ens ha enviat ‘message’ ==== WebSockets, funció ‘onMessage’ al client ==== Quan el client rep un missatge, executa la funció ‘**onMessage**’ amb el missatge. Pot rebre un ‘String’ o un ‘ByteBuffer’ @Override public void onMessage(WebSocket conn, String message) { conn.send("Missatge enviat"); } @Override public void onMessage(WebSocket conn, ByteBuffer message) { } ==== WebSockets, serialitzar objecte a bytes === Cal tenir en compte que es poden enviar missatges en forma de ‘**byte[]**’ i per tant es poden enviar objectes serialitzats També cal preveure que els missatges serialitzats no són compatibles si les versions de Java són diferents (per exemple entre Android i Java) L'objecte ha de implementar Serialize public static byte[] objToBytes (Object obj) { byte[] result = null; try { // Transforma l'objecte a bytes[] ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); oos.flush(); result = bos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return result; } ==== WebSockets, restaurar ByteBuffer a Objecte ==== Després caldrà fer un 'cast' a la classe de l'objecte en concret public static Object bytesToObject (ByteBuffer arr) { Object result = null; try { // Transforma el ByteBuffer en byte[] byte[] bytesArray = new byte[arr.remaining()]; arr.get(bytesArray, 0, bytesArray.length); // Transforma l'array de bytes en objecte ByteArrayInputStream in = new ByteArrayInputStream(bytesArray); ObjectInputStream is = new ObjectInputStream(in); return is.readObject(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return result; } ==== Exemple ==== {{ :videowebsockets.mp4 |Vídeo explicatiu}} WsClient.java import java.net.URI; import java.net.URISyntaxException; import java.util.Scanner; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft; import org.java_websocket.drafts.Draft_6455; import org.java_websocket.exceptions.WebsocketNotConnectedException; import org.java_websocket.handshake.ServerHandshake; // Compilar amb: // javac -cp "lib/*:." WsClient.java // java -cp "lib/*:." WsClient public class WsClient extends WebSocketClient { private boolean running = true; private static Scanner sc = new Scanner(System.in); public static void main(String[] args) { int port = 8888; String host = "localhost"; String location = "ws://" + host + ":" + port; String text = ""; WsClient client = connecta(location); while (client.running) { text = sc.nextLine(); try { client.send(text); } catch (WebsocketNotConnectedException e) { System.out.println("Connexió perduda, reconnectant ..."); client = connecta(location); } if (text.compareTo("exit") == 0) { client.running = false; } } if (client != null) { client.close(); } } public WsClient (URI uri, Draft draft) { super (uri, draft); } @Override public void onMessage(String message) { System.out.println("Has rebut un missatge: " + message); if (message.compareTo("exit") == 0) { System.out.println("El servidor s'ha aturat"); } } @Override public void onOpen(ServerHandshake handshake) { System.out.println("T'has connectat a: " + getURI()); System.out.println("list per veure la llista de ids"); System.out.println("to(id)missatge per enviar missatges privats"); } @Override public void onClose(int code, String reason, boolean remote) { System.out.println("T'has desconnectat de: " + getURI()); } @Override public void onError(Exception ex) { System.out.println("Error amb la connexió del socket"); } static public WsClient connecta (String location) { WsClient client = null; try { client = new WsClient(new URI(location), (Draft) new Draft_6455()); client.connect(); } catch (URISyntaxException e) { e.printStackTrace(); System.out.println("Error: " + location + " no és una direcció URI de WebSocket vàlida"); } return client; } } WsGuiClient.java import java.awt.Container; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowEvent; import java.net.URI; import java.net.URISyntaxException; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft; import org.java_websocket.drafts.Draft_6455; import org.java_websocket.handshake.ServerHandshake; public class WsGuiClient extends JFrame implements ActionListener { private final JTextField uriField; private final JButton connect; private final JButton close; private final JTextArea ta; private final JTextField chatField; private WebSocketClient cc; public static void main(String[] args) { int port = 8888; String location = "localhost"; location = "ws://" + location + ":" + port; System.out.println("Default server url not specified: defaulting to \'" + location + "\'"); new WsGuiClient(location); } public WsGuiClient(String defaultlocation) { super("WebSocket Chat Client"); Container container = getContentPane(); GridLayout layout = new GridLayout(); layout.setColumns(1); layout.setRows(6); container.setLayout(layout); uriField = new JTextField(); uriField.setText(defaultlocation); container.add(uriField); Container containerButtons = new Container(); GridLayout layoutButtons = new GridLayout(); layoutButtons.setColumns(2); layoutButtons.setRows(1); containerButtons.setLayout(layoutButtons); connect = new JButton("Connecta"); connect.addActionListener(this); containerButtons.add(connect); close = new JButton("Desconnecta"); close.addActionListener(this); close.setEnabled(false); containerButtons.add(close); container.add(containerButtons); JLabel labelArea = new JLabel("Consola:"); container.add(labelArea); JScrollPane scroll = new JScrollPane(); ta = new JTextArea(); scroll.setViewportView(ta); container.add(scroll); JLabel labelEscriu = new JLabel("Escriu un missatge:"); container.add(labelEscriu); chatField = new JTextField(); chatField.setText(""); chatField.addActionListener(this); container.add(chatField); java.awt.Dimension d = new java.awt.Dimension(300, 600); setPreferredSize(d); setSize(d); addWindowListener(new java.awt.event.WindowAdapter() { @Override public void windowClosing(WindowEvent e) { if (cc != null) { cc.close(); } dispose(); } }); setLocationRelativeTo(null); setVisible(true); } public void actionPerformed(ActionEvent e) { if (e.getSource() == chatField) { if (cc != null) { cc.send(chatField.getText()); chatField.setText(""); chatField.requestFocus(); } } else if (e.getSource() == connect) { try { cc = new WebSocketClient(new URI(uriField.getText()), (Draft) new Draft_6455()) { @Override public void onMessage(String message) { ta.append("got: " + message + "\n"); ta.setCaretPosition(ta.getDocument().getLength()); } @Override public void onOpen(ServerHandshake handshake) { String text = "T'has connectat a: " + getURI() + "\nlist per veure la llista de ids" + "\nto(id)missatge per enviar missatges privats"; ta.append(text); ta.setCaretPosition(ta.getDocument().getLength()); } @Override public void onClose(int code, String reason, boolean remote) { ta.append("T'has desconnectat de: " + getURI() + "\n"); ta.setCaretPosition(ta.getDocument().getLength()); connect.setEnabled(true); uriField.setEditable(true); close.setEnabled(false); } @Override public void onError(Exception ex) { ta.append("Error amb la connexió del socket\n"); ta.setCaretPosition(ta.getDocument().getLength()); connect.setEnabled(true); uriField.setEditable(true); close.setEnabled(false); } }; close.setEnabled(true); connect.setEnabled(false); uriField.setEditable(false); cc.connect(); } catch (URISyntaxException ex) { ta.append(uriField.getText() + " no és una direcció URI de WebSocket vàlida\n"); } } else if (e.getSource() == close) { cc.close(); } } } WsServidor.java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import org.java_websocket.WebSocket; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; // Compilar amb: // javac -cp "lib/*:." WsServidor.java // java -cp "lib/*:." WsServidor // Tutorials: http://tootallnate.github.io/Java-WebSocket/ public class WsServidor extends WebSocketServer { static BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); public static void main(String[] args) throws InterruptedException, IOException { int port = 8888; boolean running = true; // Deshabilitar SSLv3 per clients Android java.lang.System.setProperty("jdk.tls.client.protocols", "TLSv1,TLSv1.1,TLSv1.2"); WsServidor socket = new WsServidor(port); socket.start(); System.out.println("WsServidor funciona al port: " + socket.getPort()); while (running) { String line = in.readLine(); socket.broadcast(line); if (line.equals("exit")) { running = false; } } System.out.println("Aturant WsServidor"); socket.stop(1000); } public WsServidor(int port) throws UnknownHostException { super(new InetSocketAddress(port)); } public WsServidor(InetSocketAddress address) { super(address); } @Override public void onOpen(WebSocket conn, ClientHandshake handshake) { // Saludem personalment al nou client conn.send("Benvingut a WsServer"); // Enviem la direcció URI del nou client a tothom broadcast("Nova connexió: " + handshake.getResourceDescriptor()); // Mostrem per pantalla (servidor) la nova connexió String host = conn.getRemoteSocketAddress().getAddress().getHostAddress(); System.out.println(host + " s'ha connectat"); } @Override public void onClose(WebSocket conn, int code, String reason, boolean remote) { // Informem a tothom que el client s'ha desconnectat broadcast(conn + " s'ha desconnectat"); // Mostrem per pantalla (servidor) la desconnexió System.out.println(conn + " s'ha desconnectat"); } @Override public void onMessage(WebSocket conn, String message) { if (message.equalsIgnoreCase("list")) { // Enviar la llsita de connexions al client System.out.println("Llista de connexions:"); String strList = ""; for (WebSocket ws : this.getConnections()) { strList += " " + getConnectionId(ws); } conn.send(strList); } else if (message.contains("to(")) { // Missatge privat // Trobar el client amb aquest identificador String strDesti = message.substring(message.indexOf("(") + 1, message.indexOf(")")); WebSocket desti = null; for (WebSocket ws : this.getConnections()) { if (strDesti.compareTo(getConnectionId(ws)) == 0) { desti = ws; break; } } // Enviar el missatge si s'ha trobat el client o retornar un error en cas contrari if (desti != null) { String idOrigen = getConnectionId(conn); String idDesti = getConnectionId(desti); desti.send("Missatge privat de " + idOrigen + ": " + message); conn.send("S'ha entregat el missatge privat a: " + idDesti); System.out.println("Missatge privat entre " + idOrigen + " i " + idDesti + ": " + message); } else { conn.send("No s'ha trobat el destí " + strDesti); } } else { // Enviem el missatge del client a tothom broadcast(getConnectionId(conn) + " ha dit: " + message); // Mostrem per pantalla (servidor) el missatge System.out.println("Broadcast de " + getConnectionId(conn) + ": " + message); } } @Override public void onMessage(WebSocket conn, ByteBuffer message) { // Enviem el missatge del client a tothom broadcast(message.array()); // Mostrem per pantalla (servidor) el missatge System.out.println(conn + ": " + message); } @Override public void onError(WebSocket conn, Exception ex) { ex.printStackTrace(); } @Override public void onStart() { // S'inicia el servidor System.out.println("Escriu 'exit' per aturar el servidor"); setConnectionLostTimeout(0); setConnectionLostTimeout(100); } public String getConnectionId (WebSocket connection) { String name = connection.toString(); return name.replaceAll("org.java_websocket.WebSocketImpl@", "").substring(0, 3); } }