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