WebSocket and Socket.IO: A Guide to Real-Time Communication
Today, WebSocket is widely recognized as a popular protocol for real-time applications, enabling continuous, bidirectional communication between clients and servers, as well as between clients.
Socket.IO is a well-known library that provides powerful features for real-time communication, including WebSocket support. In this article, we'll explore the WebSocket protocol and learn the fundamentals of the Socket.IO library.
1. Real-Time Communication
1.1 Half-Duplex
With traditional HTTP/1.0 and HTTP/1.1 protocols in the client-server model, the client sends an HTTP request to the server, which processes the request and returns a response containing an HTML page and related resources. HTTP/1.0 was sufficient for retrieving a document from a server.
HTTP/1.1 introduced persistent connections, allowing a browser to establish a connection to a web server to retrieve an HTML page and then reuse that same connection to fetch additional resources, such as images and text. By reducing the number of connections established between clients and servers, HTTP/1.1 helps reduce the latency associated with making requests.
However, traditional HTTP request-response communication is often described as half-duplex in this context: communication follows a request-response pattern, meaning the server typically sends data in response to a client request rather than initiating communication independently. This can be inefficient for applications that need frequent, real-time updates.
Half-duplexSource: WebSocket β Wikipedia
1.2 Full-Duplex
Modern web applications have evolved significantly since the early days of the web. Numerous new technologies and techniques have emerged to deliver richer, more engaging, and more convenient user experiences.
Real-time web technology is becoming increasingly popular. Several technologies and approaches can be used to build real-time applications, including:
- AJAX long polling
- Server-Sent Events (SSE)
- Comet
- WebSocket
For applications that require real-time or near-real-time communication, traditional request-response patterns can be insufficient. WebSocket, a full-duplex communication protocol supported by modern web browsers, has become a popular solution.
Full-duplexSource: WebSocket β Wikipedia
2. WebSocket
2.1 What Is WebSocket?
The
WebSocket protocol is a widely supported standard for building real-time applications. Earlier approaches to simulating full-duplex communication relied heavily on polling, a technique in which the client repeatedly sends requests to the server to check whether new information is available.Polling works well when messages arrive at predictable intervals. However, in most real-time applications, message frequency is difficult to predict. Polling also requires clients to repeatedly send requests, creating unnecessary network traffic and processing overhead.
Long polling, also known as Comet, is another common communication technique. In this approach, the client sends a request to the server, which keeps the request open for a certain period. If no new information is available, the server waits until data becomes available or the request times out.
Essentially, Comet delays the completion of an HTTP response until the server has something to send to the client. This technique is often referred to as a hanging GET or pending POST.
Because the client must reconnect or issue another request to receive subsequent updates, long polling can be inefficient for applications that require frequent, continuous communication.
WebSocket is a computer communication protocol that provides full-duplex communication channels over a single TCP connection. It is standardized by the IETF, while the WebSocket API is defined by the WHATWG HTML Living Standard and supported by modern browsers.WebSocket provides full-duplex communication channels over a single TCP connection.Source: WebSocket β Wikipedia
With
WebSocket, you can build genuinely real-time applications, such as chat applications, collaborative document editors (like Google Docs), trading platforms, and multiplayer online games.2.2 WebSocket API
When establishing a
WebSocket connection, the client and server first perform a handshake over an existing TCP connection. During this process, they negotiate the upgrade from HTTP to the WebSocket protocol.A client-side handshake request typically looks like this:
GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13
The server responds with a handshake response similar to the following:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
To initiate the handshake, the client sends a
Sec-WebSocket-Key header containing a Base64-encoded random value to the server.The server then performs the following steps:
- Appends the protocol-defined GUID
258EAFA5-E914-47DA-95CA-C5AB0DC85B11to the value ofSec-WebSocket-Key. For example:
dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
- Computes the SHA-1 hash of the resulting string, producing the following hexadecimal value:
1d29ab734b0c9585240069a6e4e3e91b61da1969
- Base64-encodes the resulting hash bytes to produce:
s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
- Sends the response to the client with the resulting value in the
Sec-WebSocket-Acceptheader.
The client checks that the response status code is
101 Switching Protocols and verifies that Sec-WebSocket-Accept matches the expected value. If the handshake succeeds, the connection is upgraded to WebSocket.Once the handshake is complete, the
WebSocket API allows your application to communicate over the established connection and respond to events triggered by incoming data or connection state changes.When the server has new data to send, or when a resource monitored by the application changes, it can send an update directly to the client. With this event-driven communication model, the client no longer needs to repeatedly poll the server for the latest state of a resource.
To connect to a remote server, create a new instance of the
WebSocket object and provide the URL of the target endpoint.A WebSocket connection is established by upgrading an HTTP connection to the WebSocket protocol during the initial handshake between the client and server, using the same underlying TCP connection.
The HTTP
Upgrade header indicates that the client wants to establish a WebSocket connection. Once the connection is successfully established, messages are exchanged using the WebSocket protocol.To create a connection, call the JavaScript
WebSocket constructor, which returns a connection object. You can then listen for events on that object. These events are triggered when the connection opens or closes, a message arrives, or an error occurs.The
WebSocket constructor accepts the following arguments:urlβ Required. The URL of the endpoint to which you want to establish a connection.
protocolsβ Optional. A string or an array of strings specifying the subprotocols supported by the application. This allows a server to support different application-level communication protocols over WebSocket.
For example:
const socket = new WebSocket("wss://example.com/chat"); socket.addEventListener("open", () => { console.log("WebSocket connection established"); });
Because WebSocket communication is asynchronous, an application can listen for events throughout the lifetime of an open connection.
To listen for events, assign callback functions to the
WebSocket object or use the DOM addEventListener() method to register event handlers.A
WebSocket object supports four primary events:- Open: Triggered when the WebSocket handshake succeeds and the connection is established. The corresponding event handler is
onopen.
- Message: Triggered when data is received through the WebSocket connection. The corresponding event handler is
onmessage.
- Error: Triggered when an error occurs during WebSocket communication. The corresponding event handler is
onerror.
Note: An error may be followed by the connection closing, but the
error event does not necessarily mean that the connection has already closed.- Close: Triggered when the WebSocket connection closes. The corresponding event handler is
onclose.
Example:
const socket = new WebSocket("wss://example.com/chat"); socket.addEventListener("open", () => { console.log("Connected"); }); socket.addEventListener("message", (event) => { console.log("Received:", event.data); }); socket.addEventListener("error", (error) => { console.error("WebSocket error:", error); }); socket.addEventListener("close", () => { console.log("Connection closed"); });
The
WebSocket API provides two primary methods for sending data and managing a connection:send(): Thesocket.send(data)method sends data through the WebSocket connection. If the connection is not yet open, calling this method may throw an exception. Data sent through this method is queued for transmission when appropriate, subject to the connection's state and available resources.
close(): Thesocket.close()method initiates the process of closing the connection. If the connection is already closed, calling this method has no effect. It accepts two optional arguments:code, the closing status code, andreason, a string explaining why the connection is closing.
A
WebSocket connection object also exposes several useful properties:url(read-only): Returns the URL used when the connection was created.
readyState(read-only): Indicates the current state of the connection. It can have one of the following values:0βCONNECTING: The connection is being established.1βOPEN: The connection is open and data can be exchanged.2βCLOSING: The connection is in the process of closing.3βCLOSED: The connection is closed or could not be established.
bufferedAmount(read-only): Returns the number of bytes of application data that have been queued bysend()but have not yet been transmitted. This property can help applications manage outgoing data and avoid adding excessive amounts of data to the send buffer.
protocol(read-only): Returns the subprotocol selected by the server from the protocols provided when theWebSocketobject was created. It returns an empty string if no subprotocol was selected.
binaryType: Controls the format used to deliver binary messages. The two supported values areblob(the default) andarraybuffer.
3. Socket.IO
3.1 What Is Socket.IO?
Socket.IO is a library that enables low-latency, bidirectional, event-based communication between clients and servers.Socket.IO is a library that enables low-latency, bidirectional, event-based communication between a client and a server.Source: Socket.IO Documentation
Socket.IO connections can use several underlying transport mechanisms:
- HTTP long polling
- WebSocket
- WebTransport
Socket.IO selects an appropriate available transport based on factors such as:
- Browser support β See WebSocket browser compatibility and WebTransport browser compatibility.
- Network conditions β Some networks or misconfigured proxies may block WebSocket or WebTransport connections.
This transport flexibility helps Socket.IO maintain communication in environments where a direct WebSocket connection cannot be established.
3.2 Common Misconceptions About Socket.IO
Socket.IO is not simply an implementation of WebSocket.
Although Socket.IO uses WebSocket as a transport when possible, it also adds its own protocol, metadata, and communication features. Consequently, a Socket.IO client cannot directly communicate with a plain WebSocket server, and a standard WebSocket client cannot directly communicate with a Socket.IO server.
It's important to understand this distinction when choosing a real-time communication solution for your application.
A note about mobile applications
- Socket.IO is generally not the best choice for maintaining a persistent connection while a mobile application is running in the background.
- Keeping a TCP connection open can consume battery power, and mobile operating systems may suspend applications or restrict their network activity in the background.
- For background notifications, a dedicated push notification service such as Firebase Cloud Messaging (FCM) is often more appropriate.
3.3 Socket.IO Features
Socket.IO provides several features on top of basic WebSocket communication.
If a WebSocket connection cannot be established, Socket.IO can automatically fall back to HTTP long polling.
This was one of Socket.IO's defining features when browser support for WebSocket was less widespread.
Even though modern browsers broadly support WebSocket, the fallback mechanism remains useful. Some users may still be unable to establish WebSocket connections because of restrictive networks or misconfigured proxies.
Under certain conditions, the WebSocket connection between a client and server can be interrupted without either side immediately detecting that the connection has become unusable.
Socket.IO addresses this problem through a heartbeat mechanism, which periodically checks the connection's health.
When the client is disconnected, Socket.IO can automatically attempt to reconnect. It uses an increasing delay between attempts, known as exponential backoff, to avoid overwhelming the server with repeated reconnection requests.
Socket.IO automatically buffers outgoing events when the client is disconnected and sends them after the connection is restored.
This behavior can be useful when disconnections are brief. However, it may also cause a large number of queued events to be delivered at once after reconnection.
Depending on your use case, you can mitigate this behavior in several ways:
- Check the socket's
connectedproperty before sending events.
- Use volatile events for data that can be discarded when the connection is unavailable, such as transient status updates.
Socket.IO provides a convenient mechanism for sending an event and receiving a response from the recipient.
This is useful when the sender needs confirmation that an event has been received or processed.
The sender emits an event with an acknowledgement callback, while the receiver invokes that callback with the response data.
Socket.IO also supports timeouts, allowing the sender to stop waiting for an acknowledgement after a specified period.
On the server side, you can broadcast messages or events to all connected clients or to a specific group of clients.
This feature is useful for applications such as chat rooms, live notifications, collaborative tools, and real-time dashboards.
For example, a server can broadcast an event to all connected clients or target a specific room so that only its members receive the message.
Namespaces allow you to separate application logic over a single underlying connection.
This is useful when an application contains different functional areas that require separate event handlers or access controls. For example, an application might use one namespace for regular users and another for administrative functionality.
Namespaces help organize communication, while authorization and permission checks must still be implemented on the server.
4. Conclusion
We've explored how WebSocket enables continuous, bidirectional communication between clients and servers, providing an alternative to traditional polling techniques. WebSocket has opened up new possibilities for building modern, multi-user real-time applications.
However, implementing and managing real-time connections directly with the basic WebSocket API can be challenging. That's where Socket.IO comes in: it simplifies real-time communication by providing useful features such as automatic reconnection, event acknowledgements, packet buffering, broadcasting, and HTTP long-polling fallback.
Socket.IO is not limited to WebSocket as its underlying transport. It also provides its own communication protocol and a set of abstractions that make real-time application development easier.
For developers who want to build real-time applications without implementing every connection-management feature from scratch, Socket.IO is a practical option worth considering.