websockets_-_exemple_android.zip
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.
Com que Java no els implementa directament, cal utilitzar alguna llibreria que ho faci. N’hi ha vàries, l’exemple fa servir:
- Java WebSockets (també a Github)
Necessitem les següents llibreries:
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();
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’
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) { }
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; }
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; }
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); } }