Real-Time Stock Data Updates with WebSockets using Ballerina
Simulation of real-time data updates with WebSockets. In this article, we'll implement a WebSocket-based service and clients using the Ballerina WebSocket module.
Join the DZone community and get the full member experience.
Join For FreeThe internet is built on the HTTP standard for communicating data between clients and servers. Although HTTP is widely-used, when there is a requirement to support the continuous transmission of data streams or real-time updates between client and server, repeatedly making regular HTTP requests will slow things down.
Messaging on the Internet
Before discussing the real power and use of WebSocket let’s see what are the different ways of messaging on the internet.
Request-Response - In traditional HTTP-based systems the communication can be initiated in one direction, that is from the client to the server. Webserver receives and responds to the requests from the clients via HTTP messages.
- HTTP is stateless, as it treats each new request as completely independent.
- HTTP request-response behavior is not sufficient when the client needs real-time updates.
Short polling - This is for clients to get regular updates from the server. The client sends a request to the server to get the updates, and the server responds with new data or no new data response. Then client repeats the same request at a configured interval to get new data.
- The protocol is simple and it uses widely supported HTTP.
- But the drawback is the request overhead on both sides as the client has to initiate lots of requests and the server has to handle a lot of such requests whether or not there is new information.
Long polling - This is an improved version of short polling. The client sends an HTTP request for new information to the server. Then Server waits until there’s new information to respond (a “hanging” response). The client repeats the request as soon as it gets the previous response back.
- This is more efficient than short polling as it produces less traffic and it is simple as it uses HTTP.
But now the server has to hold unfulfilled requests and it can take more server resources. Also if there are multiple open requests from the same client, message ordering can’t be guaranteed, and messages can get lost.
Server sent Events - This provides a one-way connection for a server to push new data to a client, without reestablishing a connection every time.
- This is good for apps where we don’t need to send the server any data—for example, a Twitter-style news feed or a real-time dashboard of stock quotes.
But this doesn’t support bi-directional data communication.
Websockets - WebSockets is a two-way message-passing protocol (full-duplex) based on TCP and faster for data transmission than HTTP because it has less protocol overhead and operates at a lower level in the network stack. First, the client and server establish a connection over HTTP and then “upgraded” using the WebSockets handshake. Then WebSockets TCP messages are transmitted in both directions over port 443 (or 80 if it’s not TLS encrypted).
Websocket is defined in RFC 6455.
How Websocket Works
Websocket protocol has two parts as the initial handshake and then the data transfer. For the initial communication, WebSocket uses HTTP. In this HTTP request client ask to open a WebSocket connection and it is an HTTP upgrade request which includes a few required headers as follows.
GET ws://websocket.example.com/ HTTP/1.1
Origin: http://example.com
Connection: Upgrade
Host: websocket.example.com
Upgrade: websocket
Then if the server is able to use WebSocket it will reply and the handshake is successful. The successful handshake demonstrates that the client and server are in agreement for using the existing TCP/IP connection established for the HTTP request as a WebSocket connection. Then it keeps the TCP connection alive after the HTTP response is received and that will be used for sending messages between client and server after that.
WebSocket URIs use a new scheme ws: (or wss: for a secure WebSocket)
Simulating Real-Time Stock Updates Using WebSockets
Now, let’s explore what we can do with WebSockets in real life. Social feeds, multiplayer games, financial feeds, location-based updates, multimedia chat are a few common examples of WebSocket usage. Let’s implement a demo stock update service using the Ballerina programming language. If Ballerina is not installed already, follow the instructions here and install Ballerina. Refer to the following video for more details:
Use Case
The WebSocket-based stock management server is getting stock data feed via WebSocket-based connections from different exchanges. The client applications will subscribe to the preferred stock symbols by registering those symbols with the stock management server.
When the stock management server receives updates to the subscribed symbols, it will broadcast the data to the subscribed clients using a WebSocket connection.
Stock Management Server
Following is the stock management server implementation and save it in a file named ws_stock_mgt_server.bal
import ballerina/websocket;
import ballerina/io;
import ballerina/regex;
isolated map<websocket:Caller[]> clientSymbolSubscriptionMap = {};
listener websocket:Listener stockMgtListner = new websocket:Listener(9090);
service /subscribe on stockMgtListner {
//Accepts the websocket upgrade from clients by returining a websocket:service
resource function get .() returns websocket:Service|websocket:UpgradeError {
return new WsSubscribeService();
}
}
service /feed on stockMgtListner {
//Accepts the websocket upgrade from exchange feed by returining a websocket:service
resource function get .() returns websocket:Service|websocket:UpgradeError {
return new WsStockFeedService();
}
}
//Websocket service to handle client subscriptions
service class WsSubscribeService {
*websocket:Service;
//Register the client
remote function onOpen(websocket:Caller caller) returns websocket:Error? {
string message = "Client with ID :" + caller.getConnectionId() + " registered successfully!";
check caller->writeTextMessage(message);
io:println(message);
}
//Register the symbol subscriptions of client.
isolated remote function onTextMessage(websocket:Caller caller, string symbol) returns websocket:Error? {
lock {
websocket:Caller[]? clientList = clientSymbolSubscriptionMap[symbol];
if clientList is websocket:Caller[] {
clientList.push(caller);
} else {
clientSymbolSubscriptionMap[symbol] = [caller];
}
}
io:println("Client " + caller.getConnectionId() + " subscribed for " + symbol);
}
}
//Websocket service to handle incoming exchange data feed and broadcast to subscribed clients
service class WsStockFeedService {
*websocket:Service;
//Register the stock exchange feed
remote function onOpen(websocket:Caller caller) returns websocket:Error? {
string message = "Exchange with ID :" + caller.getConnectionId() + " registered successfully!";
check caller->writeTextMessage(message);
io:println(message);
}
//Receives exchange feed from the exchange and send the updates to registered clients
isolated remote function onTextMessage(websocket:Caller caller, string text) returns error? {
string[] result = regex:split(text, ":");
string symbol = result[0];
string price = result[1];
lock {
if (clientSymbolSubscriptionMap.hasKey(symbol)) {
websocket:Caller[] clients = clientSymbolSubscriptionMap.get(symbol);
foreach websocket:Caller c in clients {
check c->writeTextMessage(symbol + ":" + price);
}
}
}
}
}
Client
Following is the demo client implementation which sends subscriptions for a few stock symbols. Save the code in a file named client.bal
.
import ballerina/io;
import ballerina/lang.runtime;
import ballerina/websocket;
public function main() returns error? {
websocket:Client wsClient = check new (string `ws://localhost:9090/subscribe/`);
string message = check wsClient->readTextMessage();
io:println(message);
//Subscribe for `MSFT` symbol
check wsClient->writeTextMessage("MSFT");
//Subscribe for `GOOG` symbol later
runtime:sleep(20);
check wsClient->writeTextMessage("GOOG");
//Read stock updates received from the server
while true {
string stockPrice = check wsClient->readTextMessage();
io:println(stockPrice);
}
}
Exchange
This is the exchange feed simulator that sends stock updates every 2 seconds. Save the code in a file named exchange.bal
.
import ballerina/io;
import ballerina/lang.runtime;
import ballerina/random;
import ballerina/websocket;
string[] symbolArr = ["MSFT", "AAPL", "GOOG", "NFLX", "CSCO"];
public function main() returns error? {
websocket:Client wsClient = check new (string `ws://localhost:9090/feed/`);
string message = check wsClient->readTextMessage();
io:println(message);
//Calculate dummy prices for each symbol randomly and send to server in every 2 seconds
while true {
int randomSymbolIndex = check random:createIntInRange(0, 5);
float price = random:createDecimal() + 18.0;
string stockData = symbolArr[randomSymbolIndex] + ":" + price.toString();
io:println(stockData);
check wsClient->writeTextMessage(stockData);
runtime:sleep(2);
}
}
Running the Applications
Let's run the three applications using the below commands in the following order.
bal run ws_stock_mgt_server.bal
.
bal run client.bal
bal run exchange.bal
You will see an output similar to below.
Stock Management Server
You can see both the client and exchange WebSocket clients have registered. And, the client has sent subscriptions for two stock symbols.
Client
After subscribing to the symbols, the client will wait for the symbol updates. You can see the client is receiving updates only for the supported symbols.
Exchange
The exchange is sending the stock updates for the supported symbols by the exchange.
Summary
Websocket is a good choice for the full-duplex data communication requirements. In this article, we implemented a WebSocket-based service and clients using the Ballerina WebSocket module. Refer to the following resources for more details.
Opinions expressed by DZone contributors are their own.
Comments