Open developerasun opened 2 years ago
The protocol defines the format of the packets exchanged between the client and the server. Both the client and the server must use the same revision in order to understand each other.
In conjunction with Express Starting with 3.0, express applications have become request handler functions that you pass to http or http Server instances. You need to pass the Server to socket.io, and not the express application function. Also make sure to call .listen on the server, not the app.
Socket in the class diagram for the client A Socket is the fundamental class for interacting with the server. A Socket belongs to a certain Namespace (by default /) and uses an underlying Manager to communicate.
A Socket is basically an EventEmitter which sends events to — and receive events from — the server over the network.
socket.emit("hello", { a: "b", c: [] });
socket.on("hey", (...args) => {
// ...
});
A room is an arbitrary channel that sockets can join and leave. It can be used to broadcast events to a subset of clients:
Please note that rooms are a server-only concept (i.e. the client does not have access to the list of rooms it has joined).
The "room" feature is implemented by what we call an Adapter. This Adapter is a server-side component which is responsible for:
storing the relationships between the Socket instances and the rooms broadcasting events to all (or a subset of) clients You can find the code of the default in-memory adapter here.
Basically, it consists in two ES6 Maps:
sids: Map<SocketId, Set
in the ̀sids Map, adding "the-room" to the Set identified by the socket ID in the rooms Map, adding the socket ID in the Set identified by the string "the-room" Those two maps are then used when broadcasting:
a broadcast to all sockets (io.emit()) loops through the sids Map, and send the packet to all sockets a broadcast to a given room (io.to("room21").emit()) loops through the Set in the rooms Map, and sends the packet to all matching sockets
// main namespace
const rooms = io.of("/").adapter.rooms;
const sids = io.of("/").adapter.sids;
// custom namespace
const rooms = io.of("/my-namespace").adapter.rooms;
const sids = io.of("/my-namespace").adapter.sids;
those objects are not meant to be directly modified, you should always use socket.join(...) and socket.leave(...) instead. in a multi-server setup, the rooms and sids objects are not shared between the Socket.IO servers (a room may only "exist" on one server and not on another).
Starting with socket.io@3.1.0, the underlying Adapter will emit the following events:
io.of("/").adapter.on("create-room", (room) => {
console.log(`room ${room} was created`);
});
io.of("/").adapter.on("join-room", (room, id) => {
console.log(`socket ${id} has joined room ${room}`);
});
The main idea behind Socket.IO is that you can send and receive any events you want, with any data you want. Any objects that can be encoded as JSON will do, and binary data is supported too.
There are several ways to send events between the server and the client.
The Socket.IO API is inspired from the Node.js EventEmitter, which means you can emit events on one side and register listeners on the other:
// server-side
io.on("connection", (socket) => {
socket.emit("hello", "world");
});
// client-side
socket.on("hello", (arg) => {
console.log(arg); // world
});
Events are great, but in some cases you may want a more classic request-response API. In Socket.IO, this feature is named acknowledgements.
You can add a callback as the last argument of the emit(), and this callback will be called once the other side acknowledges the event:
// server-side
io.on("connection", (socket) => {
socket.on("update item", (arg1, arg2, callback) => {
console.log(arg1); // 1
console.log(arg2); // { name: "updated" }
callback({
status: "ok"
});
});
});
// client-side
socket.emit("update item", "1", { name: "updated" }, (response) => {
console.log(response.status); // ok
});
io.on("connection", (socket) => {
// basic emit
socket.emit(/* ... */);
// to all clients in the current namespace except the sender
socket.broadcast.emit(/* ... */);
// to all clients in room1 except the sender
socket.to("room1").emit(/* ... */);
// to all clients in room1 and/or room2 except the sender
socket.to(["room1", "room2"]).emit(/* ... */);
// to all clients in room1
io.in("room1").emit(/* ... */);
// to all clients in room1 and/or room2 except those in room3
io.to(["room1", "room2"]).except("room3").emit(/* ... */);
// to all clients in namespace "myNamespace"
io.of("myNamespace").emit(/* ... */);
// to all clients in room1 in namespace "myNamespace"
io.of("myNamespace").to("room1").emit(/* ... */);
// to individual socketid (private message)
io.to(socketId).emit(/* ... */);
// to all clients on this node (when using multiple nodes)
io.local.emit(/* ... */);
// to all connected clients
io.emit(/* ... */);
// WARNING: `socket.to(socket.id).emit()` will NOT work, as it will send to everyone in the room
// named `socket.id` but the sender. Please use the classic `socket.emit()` instead.
// with acknowledgement
socket.emit("question", (answer) => {
// ...
});
// without compression
socket.compress(false).emit(/* ... */);
// a message that might be dropped if the low-level transport is not writable
socket.volatile.emit(/* ... */);
// with timeout
socket.timeout(5000).emit("my-event", (err) => {
if (err) {
// the other side did not acknowledge the event in the given delay
}
});
});
// basic emit
socket.emit(/* ... */);
// with acknowledgement
socket.emit("question", (answer) => {
// ...
});
// without compression
socket.compress(false).emit(/* ... */);
// a message that might be dropped if the low-level transport is not writable
socket.volatile.emit(/* ... */);
// with timeout
socket.timeout(5000).emit("my-event", (err) => {
if (err) {
// the other side did not acknowledge the event in the given delay
}
});
On each side, the following events are reserved and should not be used as event names by your application:
connect connect_error disconnect disconnecting newListener removeListener
// BAD, will throw an error
socket.emit("disconnecting");
An Adapter is a server-side component which is responsible for broadcasting events to all or a subset of clients.
When scaling to multiple Socket.IO servers, you will need to replace the default in-memory adapter by another implementation, so the events are properly routed to all clients.
// main namespace
const mainAdapter = io.of("/").adapter; // WARNING! io.adapter() will not work
// custom namespace
const adminAdapter = io.of("/admin").adapter;
Starting with socket.io@3.1.0, each Adapter instance emits the following events:
create-room (argument: room) delete-room (argument: room) join-room (argument: room, id) leave-room (argument: room, id)
Most adapter implementations come with their associated emitter package, which allows communicating to the group of Socket.IO servers from another Node.js process.
There are several ways to handle events that are transmitted between the server and the client.
EventEmitter methods# On the server-side, the Socket instance extends the Node.js EventEmitter class.
On the client-side, the Socket instance uses the event emitter provided by the component-emitter library, which exposes a subset of the EventEmitter methods.
Much of the Node.js core API is built around an idiomatic asynchronous event-driven architecture in which certain kinds of objects (called "emitters") emit named events that cause Function objects ("listeners") to be called.
For instance: a net.Server object emits an event each time a peer connects to it; a fs.ReadStream emits an event when the file is opened; a stream emits an event whenever data is available to be read.
All objects that emit events are instances of the EventEmitter class. These objects expose an eventEmitter.on() function that allows one or more functions to be attached to named events emitted by the object. Typically, event names are camel-cased strings but any valid JavaScript property key can be used.
When the EventEmitter object emits an event, all of the functions attached to that specific event are called synchronously. Any values returned by the called listeners are ignored and discarded.
The following example shows a simple EventEmitter instance with a single listener. The eventEmitter.on() method is used to register listeners, while the eventEmitter.emit() method is used to trigger the event.
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('an event occurred!');
});
myEmitter.emit('event');
research : understanding socket.io
read this
origin
advantages
How does socket io work?
reference