Exciting Times in Real-Time Web Communication!

Exciting Times in Real-Time Web Communication!

ยท

6 min read

Unveiling the Power of WebSockets!

With the thrill of the recent World Cup matches still echoing in our hearts, it's the perfect time to explore the world of real-time communication in web applications. In my previous blog post, we delved into the wonders of Server-Sent Events (SSE) and how it transformed our approach to instant updates. Today, let's elevate our understanding to the next level by unravelling the incredible capabilities of WebSockets!

In this new blog, we'll dive deep into the advantages of WebSockets, the real-time challenges they address, and the seamless solutions they provide.

SSE ๐Ÿ‘Ž

  1. Unidirectional Communication: SSE allows servers to push data to clients over a single HTTP connection. It's one-way communication from the server to the client. This limitation makes SSE unsuitable for interactive applications that require bidirectional communication.

  2. Connection Limitations: Servers might have limitations on the number of concurrent SSE connections they can handle. Establishing and maintaining numerous SSE connections could strain server resources, especially in scenarios with a large number of clients.

  3. Limited to Text-Based Formats: SSE events are transmitted as plain text. While JSON data can be embedded within the events, more complex data structures or custom binary formats are not supported out-of-the-box, limiting the types of data that can be efficiently transmitted.

Websocket ๐Ÿ‘

  1. Bidirectional Communication: WebSockets enable full-duplex communication, allowing both the server and the client to send messages independently at any time. This bidirectional communication allows interactive and real-time exchanges between clients and servers.

  2. Low-Latency: WebSockets provide low-latency, real-time communication. This low latency is crucial for applications where timely updates are essential, such as online gaming, financial platforms, collaborative tools, and live streaming applications.

  3. Event-Driven Architecture: WebSockets are event-driven, allowing developers to create interactive and dynamic applications. Events can be triggered on the server or client side, enabling responsive behaviours and real-time updates in applications.

What is WebSockets?

WebSocket is bidirectional, a full-duplex protocol that is used in the same scenario of client-server communication. It is a stateful protocol, which means the connection between client and server will stay alive until it is terminated by either party (client or server). After closing the connection by either the client or server, the connection is terminated from both ends.

ha... theories are enough let's understand this with the example of modifying our cricket application

In our current cricket application, users can access live scores and updates through Server-Sent Events (SSE). Now, let's elevate this experience by integrating WebSockets for more interactive and real-time user engagement.

Websocket Node-server

Here we are creating our websocket server with the help of socket.io npm package

Socket.IO server is initialized, allowing connections from any origin and supporting GET and POST methods.

When a client connects to our server (io.on("connection", ...)), the associated callback is executed. This callback accepts a parameter called socket, representing an independent connection for that specific client. For this client, specific handlers such as joinHandlers and event listeners are attached.

In essence, the io.on("connection") event is triggered for every client/user connecting to the server. Once connected, the corresponding callback is responsible for attaching appropriate events and listeners tailored to each client.

If any client disconnects, this event is observed specifically through socket.on("disconnect"), which refers to the instance of that particular client. This ensures that disconnections are handled at the client level, allowing for precise management of individual client connections.

let us discuss about the joinhandlers

  1. Event Listener Setup:

    • The socket.on("join-room", ...) sets up an event listener for the "join-room" event from the client. This event is emitted by the client when it wants to join the specific room.
  2. Room Joining:

    • When the "join-room" event is received, the server logs a message indicating that a user has joined the room named "INDVSNZ".

    • The socket.join("INVSNZ") line instructs the server to add the socket to the room named "INVSNZ". This enables the server to communicate with all clients inside this room.

  3. Emitting "update_match" Event:

    • After the socket joins the room, the server emits an "update_match" event back to the client.

    • The data sent with this event is obtained by calling the getScores() function, which likely retrieves the current match scores and details.

How are successive events propagated to the connected clients in real-time?

thats where the updateMatch handlers come into play

The scoring logic remains consistent with the previous blog. The key alteration lies in the updateMatch function, which takes the socket server instance as a parameter. Utilizing this instance, the function emits match updates when specific events occur. In this scenario, we simulate these events happening every minute. Every minute, the io instance emits a match update event to the event named "update_match" for the room "INDVSNZ". This entire process is managed by the following line:

io.to("INDVSNZ").emit("update_match",getScores())

This line ensures that match updates are sent to all clients within the "INDVSNZ" room using the io instance, triggering the "update_match" event with the latest scores obtained from the getScores() function.

now the another question get rise why we emit using the io(global instance) instead of socket (independent client instance)

We utilize the concept of rooms by directing responses exclusively to clients connected to a specific room("INDVSNZ"). This is achieved using the global io instance, ensuring that the response is broadcasted to all clients within the room("INDVSNZ"). The socket instance, on the other hand, pertains to a specific client, enabling targeted communication with that individual client alone.

so instead of emitting the update to single client, the io instance targets the room ("INDVSNZ") and emits the "update_match" event along with the scores obtained from the getScores() method. This method provides the current match score. All clients, whether they joined the room earlier or in the future via socket.join("join-room"), receive this event. It ensures that every client within the specified room stays up-to-date with the latest match scores.

Its time to take a look at our frontend

so for websocket communication we use the socket.io-client package

  1. useState is used to manage the state of the matchData object, which holds the current match information (score, wickets, overs).

  2. The useEffect hook establishes a connection to the Socket.IO server at localhost:3000.

  3. The component emits a 'join-room' event to join the room ('INDVSNZ'). In this step the joinhandlers in the server comes into play which put this client into the 'INDVSNZ' room

Note: for simplicity now the jion-room event defaulty joins in the INDVSNZ room but in real-time we may get the roomid along with he event.

  1. The component listens for 'update_match' events sent by the server.

    you guessed it right the data emitted by the io instance (io.to("INDVSNZ").emit("update_match",getScores())) is captured here and it updates the component's state with the new match data.

Here is the output

The first tab (console) indicates the client joined at the start of the match and the second screen(console) indicates the client joined after the first over also the client gets updated only on the occurrence of the events.

Unlike SSE, the WebSocket approach is bidirectional. In WebSocket communication, the client can both emit events to the server and listen for events, enabling seamless two-way communication. This bidirectional nature means that data can flow from the client to the server and vice versa over a single established connection. In contrast, SSE leads to unidirectional communication, where once the connection is established, it is the server that consistently communicates with the client without the client's ability to initiate communication.

Conclusion

Incorporating real-time functionality enhances user experiences, enabling dynamic and interactive applications. Whether you opt for SSE's simplicity or WebSockets' bidirectional capabilities, understanding the nuances of these technologies empowers you to craft responsive, real-time applications tailored to your users' needs. Choose wisely, and let your application thrive in the world of real-time communication. Happy coding!

ย