We will demonstrate how to build websockets using Tomcat 8 and HTML5 websocket API.
Websockets are designed to work like TCP, providing full duplex communication but they are not TCP. Websockets are built on top of HTTP. That's why they work on the web. The client needs to make its regular HTTP request (handshake) to initiate websocket connection. After the successful handshake connection, only the duplex connection is established.
So ws://
is just like http://
and wss://
uses same SSL certificate used by https://
The below server socket accepts the connection via a blank message and sends the message back to the client after repeated intervals.
This socket demonstrates how the client has to listen to messages from the server without further sending any information to the server. This works the opposite way if compared with HTTP requests, where the client has to request each time it expects a reply from the server.
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/push/")
public class PushSocket {
@OnMessage
public void onMessage(String message, final Session session) {
System.out.println("Message from " + session.getId() + ": " + message);
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
try {
session.getBasicRemote().sendText("Push");
} catch (IOException ex) {
Logger.getLogger(PushSocket.class.getName()).log(Level.SEVERE, null, ex);
}
}
};
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(timerTask, 0, 3 * 1000);
}
}
Creating a socket in Java is similar to creating a servlet. The message function has to be annotated with @OnMessage
. This method will be invoked when the client sends a message.
The sendText()
function can be used wherever server wishes to push messages to the client.
WebSocket object in javascript points to the socket endpoint.
var pushSocket = new WebSocket("ws://localhost:8080/websocket/push/")
pushSocket.onmessage = function (event) {
console.log(event.data);
//do ui update here
}
pushSocket.onopen = function (event) {
//send empty message to initialize socket connnection
pushSocket.send("");
};
pushSocket.onclose = function (event) {
//send empty message to initialize socket connnection
alert("Socket Closed by Server");
};
Above we saw how we can keep pushing messages to a single client. What about multiple clients connecting to the server. How do we push separately to each client?
The Socket API provides two methods @OnOpen
and @OnClose
to store the list of existing clients connected to the server.
Add incoming session in @OnOpen
to list. Remove session from the list in @OnClose
List<Session> socketSessions = Collections.synchronizedList(new ArrayList<Session>());
We will store the list in a SessionManager
and add and remove elements w.
@OnOpen
public void open(Session session) {
sessionManager.addSession(session);
}
@OnClose
public void close(Session session) {
sessionManager.removeSession(session);
}
So SessionManager can have a broadcast method that can be used to broadcast the message to all clients connected.
public void broadcast(String message){
for(Session session: sessionList){
session.getBasicRemote().sendText(message);
}
}
You can use the sessionManager.broadcast()
method wherever the push event is triggered.
If you noticed, we don't need @OnMessage
anymore, since we are storing the sessions. We can push to any client, which is available in the list i.e. connected to the server.
We can store user info to identify the connected user in the map provided by the Session
object
@OnOpen
public void open(@PathParam("userid") String userId, Session session){
session.getUserProperties().put("USER_ID" , userId);
}
At this stage, we have multiple clients we can broadcast to. But we want to send a specific notification to a single client. We have to loop over all the sessions to identify the userId
match. This is not an efficient solution.
So we rather handle the identification at handshake level. So we can discard unauthenticated connections.
And we convert over Synchronized ArrayList
into a concurrent hashmap.
Read more about how to send data to websockets in javascript https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications
Websocket is JSR 356 implementation. https://www.oracle.com/technetwork/articles/java/jsr356-1937161.html