连续复制
一键复制
一键打包

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空间,我们用于做一些专用的操作。

    // 在服务端...
    // 建立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');
    
    };