Socket.io是什么

Socket.io是一个用于在浏览器和服务器之间进行实时,双向和基于事件的通信库。
Socket.io包装了websocket,在浏览器支持的情况下优先使用websocket进行连接,否则回退到HTTP长轮询的方式,解决了部分浏览器暂不支持websocket的问题。

安装方法

在node.js服务端使用时,通过npm安装

npm install socket.io

在浏览器中使用时可通过两种方式

第一种

// socket.io在服务端的程序会自动将客户端的文件在该路径下暴露出来,
// 因此直接通过src标签引入即可
<script src="/socket.io/socket.io.js"></script>

第二种

// 在服务端...
// 如果不想通过服务端暴露的文件引用,则可以禁用该功能
const io = require('socket.io')({
  // 不启用
  serveClient: false
});
// 在客户端
// 然后通过cdn等方式引入(减轻自己服务器的压力)
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>

与Express一起使用

const app = require('express')();
const server = require('http').createServer(app);
const options = { /* ... */ };
const io = require('socket.io')(server, options);

io.on('connection', socket => { /* ... */ });

server.listen(3000);

基本概念

命名空间(namespace)

命令空间是一个通信的通道,它便于分割应用的逻辑

例如:将所有客户端分割成两个空间,一个是Default空间,一个是Admin空间。对于Default空间我们可以做一些通用的操作,而对于Admin空间,我们用于做一些专用的操作。
Socket.io学习笔记_中间件

// 在服务端...
// 建立admin命名空间
const adminNamespace = io.of('/admin');

// 对admin命名空间中的所有socket使用该中间件
adminNamespace.use((socket, next) => {
  // 例如做一些验证操作
  // ensure the user has sufficient rights
  next();
});

// 监听admin命名空间中所有客户端的connection事件
adminNamespace.on('connection', socket => {
	...
});
// 在客户端...
// 连接admin命名空间
const socket = io('/admin');
// 监听connection事件
socket.on('connection', socket => {
	...
});

默认命名空间

默认命名空间 /, 所有的客户端都会默认的连接到这个命名空间

房间(room)

在每个命名空间内,可以定义任意的通道,称为“ Room”,套接字可以加入和退出

// 在服务端...
io.on('connection', socket => {
  // 客户端建立连接之后,将其加入房间
  socket.join('some room');
  // 使用同样的方式离开一个房间
  socket.leave('some room')
});

默认房间

每一个socket都有一个随机分配的标识符Socket#id,每一个socket都会自动加入以其ID标识的房间

使用中间件

// 为默认命名空间注册一个中间件
io.use((socket, next) => {
  if (isValid(socket.request)) {
    next();
  } else {
    next(new Error('invalid'));
  }
});

// 为自定义的命名空间制定一个中间件
io.of('/admin').use(async (socket, next) => {
  const user = await fetchUser(socket.handshake.query);
  if (user.isAdmin) {
    socket.user = user;
    next();
  } else {
    next(new Error('forbidden'));
  }
});

================================================
// 可以为一个命名空间注册多个中间件,他们会依次执行
io.use((socket, next) => {
  next();
});

io.use((socket, next) => {
  next(new Error('thou shall not pass'));
});

io.use((socket, next) => {
  // 不会执行,因为前一个中间件返回了错误
  next();
});

// 如果next方法返回了Error,那么客户端会收到error事件

====================================================
// 使用express中间件
// 大多数express的中间件都需要与socket.io兼容,你只需要将这些中间件,用一个函数包起来
const wrap = middleware => (socket, next) => middleware(socket.request, {}, next);

const session = require('express-session');

io.use(wrap(session({ secret: 'cats' })));

io.on('connect', (socket) => {
  const session = socket.request.session;
});

发送消息

现在已经知道了命名空间和房间,然后就该往这些命名空间和房间发送消息了

// 在服务端...
// 监听连接事件
io.on('connect', onConnect);

// 连接成功后使用的函数
function onConnect(socket){

  // 发送到这个刚刚连接成功的客户端
  socket.emit('hello', 'can you hear me?', 1, 2, 'abc');

  // 发送给所有客户端,不包括发送者
  socket.broadcast.emit('broadcast', 'hello friends!');
  // 发送到所有在game房间的客户端,不包括发送者
  socket.to('game').emit('nice game', "let's play a game");
  // 发送到所有在game1或game2的客户端,不包括发送者
  socket.to('game1').to('game2').emit('nice game', "let's play a game (too)");
  // 发送到所有的客户端,包括发送者
  io.in('game').emit('big-announcement', 'the game will start soon');
  // 发送到所有在myNamespace命名空间的客户端,包括发送者
  io.of('myNamespace').emit('bigger-announcement', 'the tournament will start soon');

  // 发送到在myNamespace命名空间中的room房间中的客户端,包括发送者
  io.of('myNamespace').to('room').emit('event', 'message');
  // 发送到指定socketId的客户端(私聊)
  io.to(socketId).emit('hey', 'I just met you');

  // 注意: `socket.to(socket.id).emit()` 不会如预期那样,
  // 发送消息给指定socket, 而是会把socket.id当作room的名称
  // 将消息发给名为socket.id的room的房间中的每个socket(除开发送者)

  // 发送消息,并且带有确认回调
  socket.emit('question', 'do you think so?', function (answer) {});

  // 发送消息,但不使用压缩
  socket.compress(false).emit('uncompressed', "that's rough");

  // sending a message that might be dropped if the client is not ready to receive messages
  // 发送一条不可靠消息,该消息可能由于客户端未准备好而丢失
  socket.volatile.emit('maybe', 'do you really need it?');

  // 指定要发送的数据是否具有二进制数据
  socket.binary(false).emit('what', 'I have no binaries!');

  // 发送到node上的所有客户端(使用多个node时)
  io.local.emit('hi', 'my lovely babies');

  // 发送到所有客户端

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

};

连接生命周期

Socket.io学习笔记_javascript_02