Building Real-Time Experiences

Building Real-Time Experiences

ยท

8 min read

Unraveling Polling and Its Alternatives in App Development

With the Cricket World Cup in full swing, the current excitement is palpable. While this event serves as an exciting backdrop, it also presents an interesting challenge: how do we build a system that receives near real-time score updates? In this blog, we will delve into various approaches, exploring their advantages and differences. Let's embark on this journey together and unravel the world of real-time score updates amid the cricket fever.

Considering the requirements of our Cricket Application, it's clear that we need to update the application state in real time as the match unfolds.

Approach 1

what if we request the server within certain intervals like making a server call every 10 seconds and updating the state of the application.

By adopting this method, the application state gets updated every 10 seconds, ensuring that any changes on the server are promptly reflected. This real-time update mechanism keeps the scores in sync with the server data, providing users with accurate and timely information.

Let's explore an example of how we can achieve this

Node Server

Here's our Node.js server responsible for handling incoming requests and responding with the current score and wickets in JSON format (e.g., {score: 0, wickets: 0, Overs : 0.0}).

To achieve real-time scenarios, I've implemented two setInterval functions: one running every 1 minute and another every 2 minutes. The first interval updates the score and overs, while the second one updates the wickets.

const score = Math.floor(math.random()*6);

This line is responsible for generating random ranging from 1 to 6;

which is getting added to the totalScore.

So whenever a client requests the server, the server responds to the request with the current value of the scores and the wickets.

Client Side

The provided HTML code represents a basic screen displaying the score and the number of wickets in a cricket match and the current over. The logic to update the screen, contained within the script tag, is explained below.

const scoreDiv = document.getElementById('score'); // Retrieves the element with the id 'score'.

let matchStatus = {}; This variable holds the score and wickets, usually obtained from the server response.

The getScores() function serves as the core component of the application, responsible for initiating the server call and updating the user interface (UI) or screen.

This function utilizes the Fetch API to request the server, retrieving the current match scores and wickets. Once the response is received, it updates the content of the scoreDiv element.

getScores(); is the initial call to fetch the current score and wickets, updating the screen with the latest match data for the first time when the application loads

setInterval(() => { getScores(); }, 10000);

The real-time update approach discussed involves using the setInterval function. Here's how it works:

setInterval is used to repeatedly call the getScores() function every 10 seconds (10,000 milliseconds). This ensures that the match scores and wickets are regularly fetched from the server, keeping the application's UI in sync with the real-time data.

This approach of making periodic requests to the server at fixed intervals, such as every 10 seconds in this case, to retrieve updated data is known as polling.

The output of this is

Upon close observation of the output, we notice that we continue to receive the old state for consecutive responses. Which is unnecessary since we only need to update the application state when there is a change.

The approach of requesting the server at fixed intervals poses efficiency challenges. Unnecessary requests are made even if the data hasn't changed, leading to excessive server traffic. As the user base grows, this approach escalates the problem, flooding the server with redundant requests. This not only strains the server but also wastes bandwidth and resources.

Additionally, the constant polling can result in delayed updates as the server may not respond instantly to each request. Furthermore, it might lead to higher server costs due to increased data transfer and processing demands.

So what can be the alternative for these

Let's reconsider the requirements at hand. It's clear that updates originating from one source (the server) need to be promptly reflected back to the clients whenever they occur. This scenario indicates the necessity for an event-driven system. Each ball bowled in the game constitutes an event, and whenever such an event occurs, it triggers an update on the server, which in turn needs to be relayed to the clients in real-time.

To transform the system into an event-driven model, we rely on a concept known as Server-Sent Events, which operates on a push-based architecture.

What is SSE?

Server-Sent Events (SSE) is a standard web technology that enables servers to push real-time updates to web clients over a single HTTP connection. SSE is a simple and efficient way to establish a persistent connection between the client and server, allowing the server to send data to the client as soon as it's available. Unlike traditional web communication, where the client initiates requests(pull) to obtain updates, SSE flips this model by allowing the server to initiate the communication(push).

Approach 2

The logic mimicking the cricket scenario remains similar to the previous approach. However, in this case, we introduce an additional router specifically designed for Server-Sent Events (SSE) communication. This new router is responsible for managing the SSE connection and facilitating real-time updates between the server and the clients.

Updated Node Server

  1. res.writeHead(200, {...}):

    This line sets the response header with a status code of 200 (indicating a successful response) and specific headers required for Server-Sent Events (SSE). These headers include "Content-Type" set to "text/event-stream", indicating that the response is using SSE, "Connection" set to "keep-alive" to maintain a persistent connection, and "Cache-Control" set to "no-cache" to prevent caching of the SSE responses.

  2. res.write(data: ${JSON.stringify({score: totalScore, wickets, overs})}\n\n):

    This line sends the initial data to the client as a JSON string. The JSON.stringify() method converts the JavaScript object {score: totalScore, wickets, overs} into a JSON string. The data is preceded by "data: " to indicate that this line contains data. The double newline \n\n signifies the end of the message.

  3. setInterval(() => {...}, 60001:

    This code sets up an interval. Every 60,001 milliseconds (approximately 1 minute and 1 second), it sends the updated data to the client in the same format as the initial data. This interval ensures that the client receives periodic updates from the server, keeping the SSE connection active.

    the 60001 is because for every minute we change the total so running it 60001 will write the current status to all the connected clients.

    Note: In real time this will be handled by a queue system.

Why the Connection is Set as "keep-alive":

The Connection: The keep-alive header is crucial for SSE to maintain a persistent connection between the client and the server. Unlike traditional HTTP requests that close the connection after the response is sent, SSE connections remain open, allowing the server to send updates to the client whenever there is new data. The keep-alive setting ensures that the connection stays open, enabling real-time communication without the overhead of repeatedly establishing new connections for each update.

if you are still confused with what is connection and content type please gothrough this blogs blog1 and blog2.

So the updated html is

  1. const chatDiv = document.getElementById('score'):

    • This line retrieves the HTML element with the ID 'score' and stores it in the chatDiv variable. This element is where the real-time match status will be displayed.
  2. const eventSource = new EventSource('localhost:3000/sse'):

    • Here, an EventSource object is created. It establishes a connection to the SSE endpoint at localhost:3000/sse, enabling real-time communication with the server. The EventSource object listens for updates from the server.
  3. eventSource.onmessage = function(event) { ... }:

    • This code sets up an event handler for the onmessage event of the EventSource. When the server sends a message (data) over the SSE connection, this function is executed. The event object contains the data sent by the server.
  4. const matchStatus = JSON.parse(event.data):

    • Here, the data received from the server is parsed from JSON format to a JavaScript object. event.data contains the JSON string sent by the server, representing the match status (score, wickets, and overs).
  5. chatDiv.innerHTML += ...:

    • This line dynamically updates the content of the HTML element with the ID score to display the real-time match status. It uses string interpolation to create an HTML <p> element with the received match status data (score, wickets, and overs). The += operator appends the new content to the existing content inside the chatDiv element.
  6. eventSource.onerror = function(error) { ... }:

    • An onerror event handler is set to handle errors that might occur during the SSE communication. If an error occurs, it logs the error message to the console for debugging purposes.

With this change our system is now successfully changed to event based system that pushes updates to the connected clients when there is an event occured.

The Output

The successful implementation of SSE demonstrates that the application is now being updated in real-time based on the occurring events.

Conclusion:

Certainly! SSE is indeed a promising approach for real-time communication, especially when compared to polling, which has its limitations. However, it's essential to note that there are other communication methods worth exploring. In our upcoming blog, we'll delve into these alternatives.

Until then, happy learning, and enjoy following the World Cup! ๐Ÿ†.

ย