Thursday, June 27, 2019

WebRTC tutorial - socket.io Study Notes


1.   Namespace

Represents a pool of sockets connected under a given scope identified by a pathname (eg: /chat).
A client always connects to / (the main namespace), then potentially connect to other namespaces (while using the same underlying connection).
Default namespace
We call the default namespace / and it’s the one Socket.IO clients connect to by default, and the one the server listens to by default.
This namespace is identified by io.sockets or simply io:
// the following two will emit to all the sockets connected to `/`
io.sockets.emit(
'hi', 'everyone');
io.emit(
'hi', 'everyone'); // short form
Each namespace emits a connection event that receives each Socket instance as a parameter
io.on('connection', function(socket){
  socket.on(
'disconnect', function(){
  });
});
Custom namespaces
To set up a custom namespace, you can call the of function on the server-side:
const nsp = io.of('/my-namespace');
nsp.on(
'connection', function(socket){
 
console.log('someone connected');
});
nsp.emit(
'hi', 'everyone!');
On the client side, you tell Socket.IO client to connect to that namespace:
const socket = io('/my-namespace');

Event: ‘connect’ or ‘connection’

  • socket (Socket) socket connection with client
Fired upon a connection from client.
io.on('connect', (socket) => {

    // ...

  });

  

  io.of('/admin').on('connect', (socket) => {

    // ...

  });

Flag: ‘volatile’
Sets a modifier for a subsequent event emission that the event data may be lost if the clients are not ready to receive messages (because of network slowness or other issues, or because they’re connected through long polling and is in the middle of a request-response cycle).
io.volatile.emit('an event', { some: 'data' }); // the clients may or may not receive it

Flag: ‘binary’
Specifies whether there is binary data in the emitted data. Increases performance when specified. Can be true or false.
io.binary(false).emit('an event', { some: 'data' });

namespace.to(room) or namespace.in(toom)

  • room (String)
  • Returns Namespace for chaining
Sets a modifier for a subsequent event emission that the event will only be broadcasted to clients that have joined the given room.
To emit to multiple rooms, you can call to several times.
const io = require('socket.io')();

  const adminNamespace = io.of('/admin');

  

  adminNamespace.to('level1').emit('an event', { some: 'data' });

 

namespace.emit(eventName[, …args])

  • eventName (String)
  • args
Emits an event to all connected clients. The following two are equivalent:
const io = require('socket.io')();

  io.emit('an event sent to all connected clients'); // main namespace

  

  const chat = io.of('/chat');

  chat.emit('an event sent to all connected clients in chat namespace');
Note: acknowledgements are not supported when emitting from namespace.

2.   Rooms

Within each namespace, you can also define arbitrary channels that sockets can join and leave.

Joining and leaving

You can call join to subscribe the socket to a given channel:
io.on('connection', function(socket){

    socket.join('some room');

  });
And then simply use to or in (they are the same) when broadcasting or emitting:
io.to('some room').emit('some event');
To leave a channel you call leave in the same fashion as join.

3.   Socket

Socket is the fundamental class for interacting with browser clients. A Socket belongs to a certain Namespace (by default /) and uses an underlying Client to communicate.

socket.id

A unique identifier for the session, that comes from the underlying Client.

socket.rooms

A hash of strings identifying the rooms this client is in, indexed by room name.
io.on('connection', (socket) => {

    socket.join('room 237', () => {

      let rooms = Object.keys(socket.rooms);

      console.log(rooms); // [ <socket.id>, 'room 237' ]

    });

  });

socket.client A reference to the underlying Client object.

socket.emit(eventName[, …args][, ack])

  • eventName (String)
  • args
  • ack (Function)
  • Returns Socket
Emits an event to the socket identified by the string name. Any other parameters can be included. All serializable datastructures are supported, including Buffer.
socket.emit('hello', 'world');

  socket.emit('with-binary', 1, '2', { 3: '4', 5: new Buffer(6) });
The ack argument is optional and will be called with the client’s answer.
io.on('connection', (socket) => {

    socket.emit('an event', { some: 'data' });

  

    socket.emit('ferret', 'tobi', (data) => {

      console.log(data); // data will be 'woot'

    });

  

    // the client code

    // client.on('ferret', (name, fn) => {

    //   fn('woot');

    // });

  

  });

socket.send([…args][, ack])
  • args
  • ack (Function)
  • Returns Socket

socket.on(eventName, callback)

(inherited from EventEmitter)
  • eventName (String)
  • callback (Function)
  • Returns Socket
Register a new handler for the given event.
socket.on('news', (data) => {

    console.log(data);

  });

  // with several arguments

  socket.on('news', (arg1, arg2, arg3) => {

    // ...

  });

  // or with acknowledgement

  socket.on('news', (data, callback) => {

    callback(0);

  });

socket.join(room[, callback])
  • room (String)
  • callback (Function)
  • Returns Socket for chaining
Adds the client to the room, and fires optionally a callback with err signature (if any).
io.on('connection', (socket) => {

    socket.join('room 237', () => {

      let rooms = Object.keys(socket.rooms);

      console.log(rooms); // [ <socket.id>, 'room 237' ]

      io.to('room 237').emit('a new user has joined the room'); // broadcast to everyone in the room

    });

  });
For your convenience, each socket automatically joins a room identified by its id (see Socket#id). This makes it easy to broadcast messages to other sockets:
io.on('connection', (socket) => {

    socket.on('say to someone', (id, msg) => {

      // send a private message to the socket with the given id

      socket.to(id).emit('my message', msg);

    });

  });

socket.join(rooms[, callback])
  • rooms (Array)
  • callback (Function)
  • Returns Socket for chaining
Adds the client to the list of room, and fires optionally a callback with err signature (if any).

socket.leave(room[, callback])

  • room (String)
  • callback (Function)
  • Returns Socket for chaining
Removes the client from room, and fires optionally a callback with err signature (if any).
Rooms are left automatically upon disconnection.

socket.to(room) or socket.in(room)

  • room (String)
  • Returns Socket for chaining
Sets a modifier for a subsequent event emission that the event will only be broadcasted to clients that have joined the given room (the socket itself being excluded).
To emit to multiple rooms, you can call to several times.
io.on('connection', (socket) => {

  

    // to one room

    socket.to('others').emit('an event', { some: 'data' });

  

    // to multiple rooms

    socket.to('room1').to('room2').emit('hello');

  

    // a private message to another socket

    socket.to(/* another socket id */).emit('hey');

  

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

  });
Note: acknowledgements are not supported when broadcasting.

socket.compress(value)

  • value (Boolean) whether to following packet will be compressed
  • Returns Socket for chaining
Sets a modifier for a subsequent event emission that the event data will only be compressed if the value is true. Defaults to true when you don’t call the method.
io.on('connection', (socket) => {

    socket.compress(false).emit('uncompressed', "that's rough");

  });

socket.disconnect(close)
  • close (Boolean) whether to close the underlying connection
  • Returns Socket
Disconnects this client. If value of close is true, closes the underlying connection. Otherwise, it just disconnects the namespace.
io.on('connection', (socket) => {

    setTimeout(() => socket.disconnect(true), 5000);

  });

Flag: ‘broadcast’
Sets a modifier for a subsequent event emission that the event data will only be broadcast to every sockets but the sender.
io.on('connection', (socket) => {

    socket.broadcast.emit('an event', { some: 'data' }); // everyone gets it but the sender

  });

Flag: ‘volatile’
Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to receive messages (because of network slowness or other issues, or because they’re connected through long polling and is in the middle of a request-response cycle).
io.on('connection', (socket) => {

    socket.volatile.emit('an event', { some: 'data' }); // the client may or may not receive it

  });

Flag: ‘binary’
Specifies whether there is binary data in the emitted data. Increases performance when specified. Can be true or false.
var io = require('socket.io')();

  io.on('connection', function(socket){

    socket.binary(false).emit('an event', { some: 'data' }); // The data to send has no binary data

  });

Event: ‘disconnect’
  • reason (String) the reason of the disconnection (either client or server-side)
Fired upon disconnection.
io.on('connection', (socket) => {

    socket.on('disconnect', (reason) => {

      // ...

    });

  });
Possible reasons:
Reason
Side
Description
transport error
Server Side
Transport error
server namespace disconnect
Server Side
Server performs a socket.disconnect()
client namespace disconnect
Client Side
Got disconnect packet from client
ping timeout
Client Side
Client stopped responding to pings in the allowed amount of time (per the pingTimeout config setting)
transport close
Client Side
Client stopped sending data

Event: ‘error’

  • error (Object) error object
Fired when an error occurs.
io.on('connection', (socket) => {

    socket.on('error', (error) => {

      // ...

    });

  });

Event: ‘disconnecting’
  • reason (String) the reason of the disconnection (either client or server-side)
Fired when the client is going to be disconnected (but hasn’t left its rooms yet).
io.on('connection', (socket) => {

    socket.on('disconnecting', (reason) => {

      let rooms = Object.keys(socket.rooms);

      // ...

    });

  });
These are reserved events (along with connectnewListener and removeListener) which cannot be used as event names.

4.   Client API

IO

Exposed as the io namespace in the standalone build, or the result of calling require('socket.io-client').
<script src="/socket.io/socket.io.js"></script>

  <script>

    const socket = io('http://localhost');

  </script>
 

const io = require('socket.io-client');

  // or with import syntax

  import io from 'socket.io-client';

 

io([url][, options])

  • url (String) (defaults to window.location)
  • options (Object)
    • forceNew (Boolean) whether to reuse an existing connection
  • Returns Socket
Creates a new Manager for the given URL, and attempts to reuse an existing Manager for subsequent calls, unless the multiplex option is passed with false. Passing this option is the equivalent of passing 'force new connection': true or forceNew: true.
A new Socket instance is returned for the namespace specified by the pathname in the URL, defaulting to /. For example, if the url is http://localhost/users, a transport connection will be established to http://localhost and a Socket.IO connection will be established to /users.
Query parameters can also be provided, either with the query option or directly in the url (example: http://localhost/users?token=abc).
See new Manager(url[, options]) for the the list of available options.

Initialization examples

With multiplexing

By default, a single connection is used when connecting to different namespaces (to minimize resources):
const socket = io();

  const adminSocket = io('/admin');

  // a single connection will be established
That behaviour can be disabled with the forceNew option:
const socket = io();

  const adminSocket = io('/admin', { forceNew: true });

  // will create two distinct connections
Note: reusing the same namespace will also create two connections
const socket = io();

  const socket2 = io();

  // will also create two distinct connections

Manager

manager.open([callback]) or manager.connect([callback])

  • callback (Function)
  • Returns Manager
If the manager was initiated with autoConnect to false, launch a new connection attempt.
The callback argument is optional and will be called once the attempt fails/succeeds.

5.   Setup WebRTC Signaling Server with socket.io

See the details below.

WebRTC 入门教程(一)| 搭建WebRTC信令服务器