连续复制
一键复制

Socket.io是什么

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

安装方法

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

复制代码
  1. npm install socket.io

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

第一种

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

第二种

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

与Express一起使用

复制代码
  1. const app = require('express')();
  2. const server = require('http').createServer(app);
  3. const options = { /* ... */ };
  4. const io = require('socket.io')(server, options);
  5. io.on('connection', socket => { /* ... */ });
  6. server.listen(3000);

基本概念

命名空间(namespace)

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

例如:将所有客户端分割成两个空间,一个是Default空间,一个是Admin空间。对于Default空间我们可以做一些通用的操作,而对于Admin空间,我们用于做一些专用的操作。

复制代码
  1. // 在服务端...
  2. // 建立admin命名空间
  3. const adminNamespace = io.of('/admin');
  4. // 对admin命名空间中的所有socket使用该中间件
  5. adminNamespace.use((socket, next) => {
  6. // 例如做一些验证操作
  7. // ensure the user has sufficient rights
  8. next();
  9. });
  10. // 监听admin命名空间中所有客户端的connection事件
  11. adminNamespace.on('connection', socket => {
  12. ...
  13. });
  14. // 在客户端...
  15. // 连接admin命名空间
  16. const socket = io('/admin');
  17. // 监听connection事件
  18. socket.on('connection', socket => {
  19. ...
  20. });

默认命名空间

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

房间(room)

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

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

默认房间

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

使用中间件

复制代码
  1. // 为默认命名空间注册一个中间件
  2. io.use((socket, next) => {
  3. if (isValid(socket.request)) {
  4. next();
  5. } else {
  6. next(new Error('invalid'));
  7. }
  8. });
  9. // 为自定义的命名空间制定一个中间件
  10. io.of('/admin').use(async (socket, next) => {
  11. const user = await fetchUser(socket.handshake.query);
  12. if (user.isAdmin) {
  13. socket.user = user;
  14. next();
  15. } else {
  16. next(new Error('forbidden'));
  17. }
  18. });
  19. ================================================
  20. // 可以为一个命名空间注册多个中间件,他们会依次执行
  21. io.use((socket, next) => {
  22. next();
  23. });
  24. io.use((socket, next) => {
  25. next(new Error('thou shall not pass'));
  26. });
  27. io.use((socket, next) => {
  28. // 不会执行,因为前一个中间件返回了错误
  29. next();
  30. });
  31. // 如果next方法返回了Error,那么客户端会收到error事件
  32. ====================================================
  33. // 使用express中间件
  34. // 大多数express的中间件都需要与socket.io兼容,你只需要将这些中间件,用一个函数包起来
  35. const wrap = middleware => (socket, next) => middleware(socket.request, {}, next);
  36. const session = require('express-session');
  37. io.use(wrap(session({ secret: 'cats' })));
  38. io.on('connect', (socket) => {
  39. const session = socket.request.session;
  40. });

发送消息

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

复制代码
  1. // 在服务端...
  2. // 监听连接事件
  3. io.on('connect', onConnect);
  4. // 连接成功后使用的函数
  5. function onConnect(socket){
  6. // 发送到这个刚刚连接成功的客户端
  7. socket.emit('hello', 'can you hear me?', 1, 2, 'abc');
  8. // 发送给所有客户端,不包括发送者
  9. socket.broadcast.emit('broadcast', 'hello friends!');
  10. // 发送到所有在game房间的客户端,不包括发送者
  11. socket.to('game').emit('nice game', "let's play a game");
  12. // 发送到所有在game1或game2的客户端,不包括发送者
  13. socket.to('game1').to('game2').emit('nice game', "let's play a game (too)");
  14. // 发送到所有的客户端,包括发送者
  15. io.in('game').emit('big-announcement', 'the game will start soon');
  16. // 发送到所有在myNamespace命名空间的客户端,包括发送者
  17. io.of('myNamespace').emit('bigger-announcement', 'the tournament will start soon');
  18. // 发送到在myNamespace命名空间中的room房间中的客户端,包括发送者
  19. io.of('myNamespace').to('room').emit('event', 'message');
  20. // 发送到指定socketId的客户端(私聊)
  21. io.to(socketId).emit('hey', 'I just met you');
  22. // 注意: `socket.to(socket.id).emit()` 不会如预期那样,
  23. // 发送消息给指定socket, 而是会把socket.id当作room的名称
  24. // 将消息发给名为socket.id的room的房间中的每个socket(除开发送者)
  25. // 发送消息,并且带有确认回调
  26. socket.emit('question', 'do you think so?', function (answer) {});
  27. // 发送消息,但不使用压缩
  28. socket.compress(false).emit('uncompressed', "that's rough");
  29. // sending a message that might be dropped if the client is not ready to receive messages
  30. // 发送一条不可靠消息,该消息可能由于客户端未准备好而丢失
  31. socket.volatile.emit('maybe', 'do you really need it?');
  32. // 指定要发送的数据是否具有二进制数据
  33. socket.binary(false).emit('what', 'I have no binaries!');
  34. // 发送到node上的所有客户端(使用多个node时)
  35. io.local.emit('hi', 'my lovely babies');
  36. // 发送到所有客户端
  37. io.emit('an event sent to all connected clients');
  38. };