Websocket
Our usual client-server architecture works on request and response. Client requests to server. And server responds. And connection ends there~
But what if, server has a message of client? Then it's gonna have to wait until client requests again. Which is not ideal.
One thing we can do is to request sever every second to check of responses. Which is called Polling. And that is far more ideal. Too much burden on server. Overkill.
Another solution is Web socket.
- Computer communication protocol providing full-duplex communication.
- They basically update normal HTTP connection to Web Socket connection.
- For Persistent connection and Real time communication.
Socket.io
Socket.IO is composed of two parts:
- A server that integrates with (or mounts on) the Node.JS HTTP Server (the
socket.iopackage) - A client library that loads on the browser side (the
socket.io-clientpackage)
// app.js
const express = require("express");
const { createServer } = require("node:http");
const { Server } = require("socket.io");
const app = express();
const server = createServer(app);
const io = new Server(server);
app.get("/", (req, res) => {
res.sendFile(__dirname + "/public/index.html");
});
io.on("connection", (socket) => {
console.log("a user connected");
socket.on("disconnect", () => {
console.log("user disconnected!");
});
});
server.listen(3000);- Need to create server using Node, and then upgrade using Socket.io.
- Initialize
io(input/output) instance by passing server (HTTP) object - Need to sendFile and not EJS for web sockets
<!--/public/index.html-->
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" /><button>Send</button>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
</script>
</body>That’s all it takes to load the socket.io-client, which exposes an io global (and the endpoint GET /socket.io/socket.io.js), and then connect.
- Each socket fires a special
connectionanddisconnectevent. - Open different tabs and check console.
Emit
The main idea behind Socket.IO is that you can send and receive any events you want, with any data you want
- Take input from user
// index.html
const socket = io();
const form = document.getElementById('form');
const input = document.getElementById('input');
form.addEventListener("submit", (e) => {
e.preventDefault();
if (input.value) {
socket.emit("chat message", input.value)
input.value = '';
}
})- and console it
// app.js
io.on("connection", (socket) => {
console.log("a user connected");
socket.on("chat message", (msg) => {
console.log("user msg: ", msg);
});
});Taadaa!! Now we can access messages from user, no probeleemo
emit() to send a message to all the connected clients. This code will notify when a user connects to the server. socket.
Here,
- We first emit chat message from client to server using
socket.emit() - Then we console message on server using
socket.on()insideio.on()
Now, we need to emit event from server to rest of the users
- app.js
io.on("connection", (socket) => {
socket.on("chat message", (msg) => {
io.emit("chat message", msg);
});
});For that simply use io.emit() on server to broadcast message to everyone including sender.
- index.html
const socket = io();
const form = document.getElementById('form');
const input = document.getElementById('input');
const messages = document.getElementById('messages');
form.addEventListener("submit", (e) => {
e.preventDefault();
if (input.value) {
socket.emit("chat message", input.value)
input.value = '';
}
})
socket.on('chat message', (msg) => {
const item = document.createElement('li');
item.textContent = msg;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});Here first the client is sending message from this browser to the server. Which is then broadcasted to all the users on the socket. And then that message is received and displayed on browser.
And with that, we have a working real-time chat application!
We can send anything and everything inside socket.emit(). Whether it be client to server or vice-versa
Basic Emit
Client to
socket.emit('hello', 'world', 'also');Server
io.on('connection', (socket) => {
socket.on('hello', (arg1, arg2) => {
console.log(arg1); // 'world'
console.log(arg2); // 'also'
});
});OR
Server to
io.on('connection', (socket) => {
socket.emit('hello', {1, 'x'});
});Client
socket.on('hello', (arg) => {
console.log(arg); // {1, 'x'}
});- We can also send callback/promises
- Can create arbitrary channel for sockets to join and leave. called rooms
Cheat sheet
socket.emit('message', "this is a test"); //sending to sender-client only
socket.broadcast.emit('message', "this is a test"); //sending to all clients except sender
socket.broadcast.to('game').emit('message', 'nice game'); //sending to all clients in 'game' room(channel) except sender
socket.to('game').emit('message', 'enjoy the game'); //sending to sender client, only if they are in 'game' room(channel)
socket.broadcast.to(socketid).emit('message', 'for your eyes only'); //sending to individual socketid
io.emit('message', "this is a test"); //sending to all clients, include sender
io.in('game').emit('message', 'cool game'); //sending to all clients in 'game' room(channel), include sender
io.of('myNamespace').emit('message', 'gg'); //sending to all clients in namespace 'myNamespace', include sender
socket.emit(); //send to all connected clients
socket.broadcast.emit(); //send to all connected clients except the one that sent the message
socket.on(); //event listener, can be called on client to execute on server
io.sockets.socket(); //for emiting to specific clients
io.sockets.emit(); //send to all connected clients (same as socket.emit)
io.sockets.on() ; //initial connection from a client.Good to know
- One Server, One Memory Space: All clients connect to one server (NodeJS) instance, which holds the user data in its memory.
- Socket IDs as Unique Identifiers: Socket.io automatically generates and manages these IDs for tracking connected clients. Both client and server have access to their own socket IDs.
Optional Approach to simple chatting application
index.html (public)
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>Socket.IO Chat</title>
<style>
body {
margin: 0;
padding-bottom: 3rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif;
}
<span style={{ color: 'rgb(116,62,228)' }}>#form</span> {
background: rgba(0, 0, 0, 0.15);
padding: 0.25rem;
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
height: 3rem;
box-sizing: border-box;
backdrop-filter: blur(10px);
}
<span style={{ color: 'rgb(116,62,228)' }}>#input</span> {
border: none;
padding: 0 1rem;
flex-grow: 1;
border-radius: 2rem;
margin: 0.25rem;
}
<span style={{ color: 'rgb(116,62,228)' }}>#input</span>:focus {
outline: none;
}
<span style={{ color: 'rgb(116,62,228)' }}>#form</span> > button {
background: <span style={{ color: 'rgb(116,62,228)' }}>#333</span>;
border: none;
padding: 0 1rem;
margin: 0.25rem;
border-radius: 3px;
outline: none;
color: <span style={{ color: 'rgb(116,62,228)' }}>#fff</span>;
}
<span style={{ color: 'rgb(116,62,228)' }}>#messages</span> {
list-style-type: none;
margin: 0;
padding: 0;
}
<span style={{ color: 'rgb(116,62,228)' }}>#messages</span> > li {
padding: 0.5rem 1rem;
}
<span style={{ color: 'rgb(116,62,228)' }}>#messages</span> > li:nth-child(odd) {
background: <span style={{ color: 'rgb(116,62,228)' }}>#efefef</span>;
}
</style>
</head>
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" /><button>Send</button>
</form>
<script src="/socket.io/socket.io.js"></script>
<script src="script.js"></script>
</body>
</html>script.js (client)
const socket = io();
const form = document.getElementById("form");
const input = document.getElementById("input");
const messages = document.getElementById("messages");
const appendMessage = (text) => {
const item = document.createElement("li");
item.textContent = text;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
};
// when client joins
const userName = prompt("WusYaName");
socket.emit("new user", userName);
// when client sends a message
form.addEventListener("submit", (e) => {
e.preventDefault();
if (!input.value) return;
socket.emit("new message", socket.id, input.value);
input.value = "";
input.focus();
});
// whenever someone joins
socket.on("user connected", (socketId, name) => {
if (socketId == socket.id) appendMessage("You connected");
else appendMessage(` $${name} connected`);
});
// when someone messages
socket.on("chat message", (socketId, name, msg) => {
if (socketId == socket.id) appendMessage(`You:$$ {msg}`);
else appendMessage(` $${name}:$$ {msg}`);
});
// when someone disconnects
socket.on("user disconnect", (socketId, name) => {
if (socketId == socket.id) appendMessage("You disconnected");
else appendMessage(`${name} disconnected`);
});server.js (server)
const express = require("express");
const { createServer } = require("node:http");
const { Server } = require("socket.io");
const app = express();
const server = createServer(app);
const io = new Server(server);
const users = {}; // shared map[socketID] -> name
app.use(express.static(__dirname + "/public"));
app.get("/", (req, res) => {
res.sendFile(__dirname + "/public/index.html");
});
io.on("connection", (socket) => {
socket.on("new user", (name) => {
users[socket.id] = name;
io.emit("user connected", socket.id, name);
});
socket.on("new message", (socketId, msg) => {
io.emit("chat message", socketId, users[socketId], msg);
});
socket.on("disconnect", () => {
io.emit("user disconnect", socket.id, users[socket.id]);
delete users[socket.id];
});
});
server.listen(3000);