developerasun / myCodeBox-web

Open source code box for web developers.
Apache License 2.0
5 stars 0 forks source link

NPM : Socket io #166

Open developerasun opened 2 years ago

developerasun commented 2 years ago

research : understanding socket.io

read this

origin

WebSocket was first referenced as TCPConnection in the HTML5 specification, as a placeholder for a TCP-based socket API.[9] In June 2008, a series of discussions were led by Michael Carter that resulted in the first version of the protocol known as WebSocket.

advantages

How does socket io work?

The client will try to establish a WebSocket connection if possible, and will fall back on HTTP long polling if not.

WebSocket is a communication protocol which provides a full-duplex and low-latency channel between the server and the browser. More information can be found here.

you can consider the Socket.IO client as a "slight" wrapper around the WebSocket API.

reference

developerasun commented 2 years ago

Clinet API

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.

developerasun commented 2 years ago

Socket

image-removebg-preview

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) => {
  // ...
});
developerasun commented 2 years ago

Room

A room is an arbitrary channel that sockets can join and leave. It can be used to broadcast events to a subset of clients:

image

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).

Implementation details#

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> rooms: Map<Room, Set> Calling socket.join("the-room") will result in:

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).

Room event

Starting with socket.io@3.1.0, the underlying Adapter will emit the following events:

  1. create-room (argument: room)
  2. delete-room (argument: room)
  3. join-room (argument: room, id)
  4. leave-room (argument: room, id)
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}`);
});
developerasun commented 2 years ago

Emitting events

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.

Basic emit#

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
});

Acknowledgements#

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
});
developerasun commented 2 years ago

Emit cheatsheet

server-side

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
    }
  });
});

client-side

// 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
  }
});
developerasun commented 2 years ago

Reserved event

On each side, the following events are reserved and should not be used as event names by your application:

  1. connect
  2. connect_error
  3. disconnect
  4. disconnecting
  5. newListener
  6. removeListener

connect connect_error disconnect disconnecting newListener removeListener

// BAD, will throw an error
socket.emit("disconnecting");
developerasun commented 2 years ago

Adapter

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.

developerasun commented 2 years ago

Listening to events

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.

Node js Event emitter

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');

reference