This article introduces the basic concepts, operating principles, and common APIs of the WebSocket protocol, with a complete example showing how to implement two-way communication in a frontend project.
1. What Is WebSocket?
WebSocket is a network communication protocol required by many advanced features. People encountering it for the first time often ask the same question: if HTTP already exists, why do we need another protocol, and what does it offer?
The answer is simple: HTTP has a limitation—communication can only be initiated by the client. For example, to learn today's weather, the client must send a request and the server returns the result. HTTP does not let a server proactively push information to a client. This one-way request model becomes awkward when the server's state changes continuously. The client has to use polling, asking at intervals whether new information is available. Chat rooms are a typical example. Polling is inefficient and wastes resources because it must connect repeatedly or keep an HTTP connection open. WebSocket was created as a better solution.
Its defining feature is true, equal, two-way communication: the server can proactively push information to the client, and the client can send information to the server. It is a form of server push technology.
Other characteristics include:
-
It is built on TCP, so server-side implementations are relatively straightforward.
-
It is highly compatible with HTTP. The default ports are also 80 and 443, and its handshake uses HTTP, which makes it less likely to be blocked and allows it to pass through HTTP proxies.
-
Its data format is lightweight, with little overhead and efficient communication.
-
It can send both text and binary data.
-
It has no same-origin restriction; a client can communicate with any server.
-
Its protocol identifier is
ws(orwsswhen encrypted), and the server address is a URL.
2. A Simple WebSocket Example
var ws = new WebSocket("ws://echo.websocket.org");
ws.onopen = function (evt) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};
ws.onmessage = function (evt) {
console.log("Received Message: " + evt.data);
ws.close();
};
ws.onclose = function (evt) {
console.log("Connection closed.");
};3. Common WebSocket APIs
-
WebSocket constructor
let ws = new WebSocket("ws://localhost:8080");After this statement runs, the client starts connecting to the server.
-
WebSocket state (
readyState)The
readyStateproperty returns the instance's current state. There are four possible states.CONNECTING: value 0, connection in progress OPEN: value 1, connected CLOSING: value 2, connection is closing CLOSED: value 3, connection is closed -
websocket.onopen(callback after a successful connection)The instance's
onopenproperty specifies the callback to invoke after the connection succeeds.ws.onopen = function () { ws.send("Hello Server!"); };Use
addEventListenerwhen you need multiple callbacks.ws.addEventListener("open", function (event) { ws.send("Hello Server!"); }); -
websocket.onclose(callback after the connection closes)The instance's
oncloseproperty specifies the callback to invoke when the connection closes.ws.onclose = function (event) { console.log("onclose"); };Use
addEventListenerwhen you need multiple callbacks.ws.addEventListener("close", function (event) { console.log("onclose"); }); -
websocket.onmessage(callback after data arrives from the server)ws.onmessage = function (event) { // Read data from event.data var data = event.data; // Process the data }; -
websocket.send(send data to the server)ws.send("your message"); -
websocket.onerror(callback when an error occurs)ws.onerror = function (event) { // Handle the error event };
4. WebSocket Heartbeats and Reconnection
WebSocket creates a long-lived connection between the frontend and backend. Either side may lose the connection under certain conditions without receiving immediate feedback. Heartbeats and reconnection are therefore needed to keep the connection sustainable and stable.
When using the native WebSocket API, a device losing its network connection does not immediately trigger a WebSocket event, so the frontend cannot tell that the connection has been lost. Only after calling websocket.send does the browser notice; it then triggers onclose immediately or after a delay, depending on the browser and version. The backend WebSocket service may also fail and break the connection without notifying the frontend.
The frontend should therefore send periodic ping heartbeat messages. The backend immediately replies with pong to confirm that the connection is healthy. If no pong arrives within a certain time, the connection is considered unhealthy and the frontend reconnects. In short, the frontend takes the initiative: it sends regular ping messages to detect network and connection problems, and continues reconnecting after an error until the connection succeeds.
A typical heartbeat check looks like this:
// Periodically check the connection. If connected, send a message to reset
// the maximum connection lifetime on both server and client. Reconnect if closed.
let heartCheck = {
// Set slightly below the server timeout so communication can reset it
// before the connection is about to close.
timeout: 100000,
serverTimeoutObj: null,
reset: function () {
clearTimeout(this.serverTimeoutObj);
return this;
},
start: function () {
this.serverTimeoutObj = window.setInterval(() => {
if (websocket.readyState === 1) {
websocket.send("ping");
} else {
window.clearTimeout(this.serverTimeoutObj);
// Reconnect or perform other handling
}
}, this.timeout);
},
};5. Complete WebSocket Example
function newWebSocket(option) {
console.log("new webSocket.....");
let websocket = null;
// Check whether WebSocket is supported in the current environment
if (window.WebSocket) {
if (!websocket) {
websocket = new WebSocket("your request URL");
}
} else {
console.log("not support websocket");
}
// Callback invoked when the connection is established
websocket.onopen = function (e) {
// Reset the heartbeat after a successful connection
heartCheck.reset().start();
console.log("connected successfully");
};
// Keep trying to reconnect after a connection error
websocket.onerror = function () {
console.log(`onerror`);
newWebSocket();
};
// Callback invoked when a message arrives
websocket.onmessage = function (e) {
console.log("onmessage", e.data);
var message = e.data;
if (message) {
// Handle the received message
if (option != undefined) {
// Invoke the supplied object's method with the message
option.onmessage(message);
}
}
};
// Callback invoked when the server closes the connection
websocket.onclose = function () {
console.log("onclose");
};
// Close WebSocket proactively when the window closes to avoid server errors
window.onbeforeunload = () => {
return websocket.close();
};
// Periodically check the connection. Send a message when connected to reset
// the maximum lifetime; reconnect if the connection has closed.
var heartCheck = {
// Keep the heartbeat slightly shorter than the server-side timeout.
timeout: 100000,
serverTimeoutObj: null,
reset: function () {
clearTimeout(this.serverTimeoutObj);
return this;
},
start: function () {
this.serverTimeoutObj = window.setInterval(() => {
if (websocket.readyState === 1) {
websocket.send("ping");
} else {
console.log("websocket stop", websocket.readyState);
window.clearTimeout(this.serverTimeoutObj);
newWebSocket(option);
}
}, this.timeout);
},
};
return websocket;
}
Discussion
Support the author