Taula de continguts

WebSockets a Java

,

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:

- Java WebSockets (també a Github)

Necessitem les següents llibreries:

- Java-WebSocket-1.5.3.jar

- slf4j-api-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

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);
    }
}