通信之 WebSocket 和 Socket.IO 案例实践

本文详细介绍了如何使用Socket.IO在Node.js服务器和客户端(包括Vue应用)之间建立实时通信。首先,阐述了WebSocket和Socket.IO的基本概念,然后展示了如何创建一个简单的实时聊天应用,包括服务端和客户端的实现。接着,讨论了在Vue应用中集成Socket.IO的过程,包括用户登录、身份验证和路由权限控制。最后,提到了Socket.IO的身份认证中间件和数据库集成,确保了通信的安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

本文使用 WebSocket 实现实时通信案例。包括:

  • 使用 Socket.IO 实现官方实时聊天应用示例。
  • 在 Vue 中使用 Socket.IO

技术铺垫

WebSocket

参阅:WebSocket 教程 - 阮一峰的网络日志

Socket.IO

官网:https://socket.io/

HTML5 引入了很多新的特性,WebSocket 就是其中之一,它为浏览器端和服务端提供了一个基于 TCP 链接的双向通道,这样 Web 开发人员可以使用 WebSocket 构建真实的实时 Web 应用。

但是并不是所有的浏览器都支持 WebSocket 特性,在不支持 WebSocket 的浏览器中,我们可以使用一些其它的方法来实现实时通信,例如:轮询、长轮询、基于流或者 Flash Socket 的实现。

Socket.IO 的出现就是为了磨平浏览器的差异,为开发者提供一个统一的接口,在不支持 WebSocket 的浏览器中,Socket.IO 可以降级为其它通信方式来实现实时通信。

Socket.IO 在 WebSocket 之上还封装了一些服务,例如连接断开自动重连。

**注意:**Socket.IO 是包装了 WebSocket 实时通信的第三方框架,不仅提供了客户端的实现方案,还提供了服务端的实现方案。客户端要用 Socket.IO,服务端也要用 Socket.IO。

Socket.IO 官方聊天室示例

使用 Socket.IO 实现官方实时聊天应用示例。

文档地址:Get started | Socket.IO
示例地址:Socket.IO Chat Application Example
当前 Socket.IO 版本:4.x

服务端初始化

初始化项目

# 创建目录
mkdir socket-chat-server
cd socket-chat-server
# 初始化npm
npm init -y
# 安装依赖
npm i express socket.io

创建 index.html

粘贴官方文档中的 html 内容:

<!DOCTYPE html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      body { margin: 0; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }

      #form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); }
      #input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; }
      #input:focus { outline: none; }
      #form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; }

      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages > li { padding: 0.5rem 1rem; }
      #messages > li:nth-child(odd) { background: #efefef; }
    </style>
  </head>
  <body>
    <!-- 消息列表 -->
    <ul id="messages"></ul>

    <!-- 发送消息的表单 -->
    <form id="form" action="">
      <input id="input" autocomplete="off" /><button>Send</button>
    </form>
  </body>
</html>

创建 app.js

初始化 express 应用实例,响应 html 页面:

// app.js
const express = require('express')
const app = express()

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html')
})

app.listen(3000, () => {
  console.log('listening on *:3000')
})

运行 app.js,访问 http://localhost:3000 查看效果

建立Socket 通信连接

创建 socket.io 服务端实例

一般很少单独使用 WebSocket 服务(单独提供一个处理 WebSocket 的后台服务),通常情况下会在一个 HTTP 服务里提供 WebSocket 协议请求处理。

Socket.IO 已经把这个事情封装好了,它可以和 express 这类 HTTP 的服务框架结合在一起来使用,从而在 HTTP 服务中提供 WebSocket 协议通信。

// app.js
const express = require('express')
const app = express()
const http = require('http')
const server = http.createServer(app)
const { Server } = require('socket.io')
const io = new Server(server)

// 处理 HTTP 协议的使用 Express 的 app 实例
app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html')
})

// 处理 WebSocket 协议的使用 socket.io 的 io 实例
// 当使用 WebSocket 协议通信和服务端建立连接成功后触发 connection 事件
io.on('connection', socket => {
  console.log('有一个用户连接成功')
  // 监听连接断开事件(刷新页面可测试)
  socket.on('disconnect', () => {
    console.log('用户已断开连接')
  })
})

server.listen(3000, () => {
  console.log('listening on *:3000')
})

创建 socket.io 客户端实例

index.html (只列出了body部分):

<!-- 消息列表 -->
<ul id="messages"></ul>

<!-- 发送消息的表单 -->
<form id="form" action="">
  <input id="input" autocomplete="off" />
  <button>Send</button>
</form>

<!-- socket.io 的客户端实现 -->
<script src="/socket.io/socket.io.js"></script>
<script>
  // 调用 io 建立连接
  // 默认和当前网页地址(http://localhost:3000)建立连接
  const socket = io()

  socket.on('connect', () => {
    console.log('客户端连接成功')
  })
  socket.on('disconnect', reason => {
    if (reason === 'io server disconnect') {
      console.log('服务器断开连接,手动尝试重连')
      socket.connect()
    } else {
      console.log('连接断开')
    }
  })
  socket.on('connect_error', () => {
    console.log('连接失败')
  })
</script>

socket.io 默认实现了 /socket.io/socket.io.js 的文件加载,目标指向 node_modules/socket.io/client-dist/socket.io.js,可拷贝出来作为本地文件加载。

在这里插入图片描述

从请求上看,首先加载了 html 和 js 文件,红框的部分是 Socket.IO 建立连接用的。

请求的地址都是 [http/ws]://localhost:3000/socket.io,这个地址是 Socket.IO 实现的。

可以看到建立连接使用的 HTTP 协议,连接成功后收发消息使用的 WebSocket 协议。

可以在面板中查看通信记录,绿色是发出去的,红色是接收到的:

在这里插入图片描述

其中定时发送的 23 之类的消息,是 Socket.IO 实现的心跳检测和断线重连机制,也就是定期进行一次通信确认连接状态。

收发消息

客户端和服务端建立连接后都会获得一个 socket 对象,socket 翻译过来是 “套接字”,它里面保存着对方的通信地址(例如 IP 地址),通过它可以和对方通信。

  • 接收消息:socket.on(消息类型, 数据 => { })
  • 向当前 socket 用户发送消息:socket.emit(消息类型, 数据)
  • 向当前 socket 以外的在线用户发送消息:socket.broadcast.emit(消息类型, 数据)
  • 向所有用户发送消息:io.emit('消息类型', 数据)

index.html

<!-- 消息列表 -->
<ul id="messages"></ul>

<!-- 发送消息的表单 -->
<form id="form" action="">
  <input id="input" autocomplete="off" />
  <button>Send</button>
</form>

<!-- socket.io 的客户端实现 -->
<script src="/socket.io/socket.io.js"></script>
<script>
  // 调用 io 建立连接
  // 默认和当前网页地址(http://localhost:3000)建立连接
  const socket = io()

  socket.on('connect', () => {
    console.log('客户端连接成功')
  })
  socket.on('disconnect', reason => {
    if (reason === 'io server disconnect') {
      console.log('服务器断开连接,手动尝试重连')
      socket.connect()
    } else {
      console.log('连接断开')
    }
  })
  socket.on('connect_error', () => {
    console.log('连接失败')
  })

  // 收发消息
  const messages = document.getElementById('messages')
  const form = document.getElementById('form')
  const input = document.getElementById('input')

  form.addEventListener('submit', function (e) {
    e.preventDefault()
    if (input.value) {
      // 发送消息
      socket.emit('chat message', input.value)
      input.value = ''
    }
  })

  // 接收消息
  socket.on('chat message', data => {
    const message = document.createElement('li')
    message.innerHTML = data
    messages.appendChild(message)
  })
</script>
// app.js
const express = require('express')
const app = express()
const http = require('http')
const server = http.createServer(app)
const { Server } = require('socket.io')
const io = new Server(server)

// 处理 HTTP 协议的使用 Express 的 app 实例
app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html')
})

// 处理 WebSocket 协议的使用 socket.io 的 io 实例
// 当使用 WebSocket 协议通信和服务端建立连接成功后触发 connection 事件
io.on('connection', socket => {
  console.log('有一个用户连接成功')
  // 监听连接断开事件(刷新页面可测试)
  socket.on('disconnect', () => {
    console.log('用户已断开连接')
  })

  // 接收消息
  socket.on('chat message', data => {
    console.log('message => ', data)
    // 发送消息
    // 发送给当前所有已连接的在线用户(可以打开多个页面建立多个连接)
    io.emit('chat message', data)

    // 发送给消息来源的客户端
    // socket.emit('chat message', data + ' => 消息来源用户')

    // 发送给除消息来源以外的所有用户
    // socket.broadcast.emit('chat message', data + ' => 消息来源以外的所有用户')
  })
})

server.listen(3000, () => {
  console.log('listening on *:3000')
})

Vue 应用中使用 Socket.IO

在之前的聊天室基础上增加用户功能。

创建应用

使用 vue 脚手架创建 vue3.0 客户端应用:

vue create socket-chat-client
# ? Please pick a preset: Manually select features
# ? Check the features needed for your project: Choose Vue version, Babel, Router, Vuex, Linter
# ? Choose a version of Vue.js that you want to start the project with 3.x
# ? Use history mode for router? (Requires proper server setup for index fallback in production) No
# ? Pick a linter / formatter config: Standard
# ? Pick additional lint features: Lint on save, Lint and fix on commit
# ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
# ? Save this as a preset for future projects? (y/N) No

cd socket-chat-client

# 安装 Socket.IO 客户端 npm 包 和 其它工具
npm i socket.io-client axios

# 运行
npm run serve

编写页面组件

App.vue:

<template>
  <!-- 路由出口 -->
  <router-view/>
</template>

Home.vue:

<template>
  <!-- 消息列表 -->
  <ul id="messages"></ul>

  <!-- 发送消息的表单 -->
  <form id="form" action="">
    <input id="input" autocomplete="off" />
    <button>Send</button>
  </form>
</template>

<script>
import { defineComponent } from 'vue'

export default defineComponent({
  setup () {
    return {}
  }
})
</script>

<style>
body { margin: 0; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }

#form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); }
#input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; }
#input:focus { outline: none; }
#form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; }

#messages { list-style-type: none; margin: 0; padding: 0; }
#messages > li { padding: 0.5rem 1rem; }
#messages > li:nth-child(odd) { background: #efefef; }
</style>

创建登录页 Login.vue:

<template>
  <h1>用户登录</h1>
</template>

<script>
import { defineComponent } from 'vue'

export default defineComponent({
  setup () {
    return {}
  }
})
</script>

<style>

</style>

添加路由:

// src/router/index.js
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '../views/Home.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('../views/Login.vue')
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

export default router

引入 Socket.IO

与上面的服务端示例建立连接。

Home.vue:

<template>
  <!-- 消息列表 -->
  <ul id="messages"></ul>

  <!-- 发送消息的表单 -->
  <form id="form" action>
    <input id="input" autocomplete="off" />
    <button>Send</button>
  </form>
</template>

<script>
import { defineComponent } from 'vue'
import { io } from 'socket.io-client'

export default defineComponent({
  setup () {
    const socket = io('http://localhost:3000', {
      // 重新连接的最大延迟事件(秒)
      reconnectionDelayMax: 10000,
      // 传递身份信息
      auth: {
        token: '123'
      },
      // 自定义请求查询参数
      query: {
        'my-key': 'my-value'
      },
      // 添加请求头
      extraHeaders: {}
    })

    socket.on('connect', () => {
      console.log('连接建立成功')
    })

    socket.on('disconnect', () => {
      console.log('连接断开')
    })

    socket.on('connect_error', () => {
      console.log('连接失败')
    })

    return {}
  }
})
</script>

<style>...</style>

配置 socket CORS

WebSocket 通信不受同源限制,但是建立 WebSocket 连接使用的 HTTP 协议,而 HTTP 受同源策略限制,跨域请求会失败。

当前示例建立连接是跨域请求所以会失败。

在这里插入图片描述

Socket.IO 配置 CORS:Handling CORS

修改服务端 IO 实例配置:

// app.js
const express = require('express')
const app = express()
const http = require('http')
const server = http.createServer(app)
const { Server } = require('socket.io')
// 配置 Socket cors
const io = new Server(server, {
  cors: {
    orgiin: '*'
  }
})

...

修改客户端 Home.vue:

<template>
  <!-- 消息列表 -->
  <ul id="messages">
    <li v-for="(message, index) in messages" :key="index">{{ message }}</li>
  </ul>

  <!-- 发送消息的表单 -->
  <form id="form" @submit.prevent="sendMessage">
    <input v-model="inputMessage" id="input" autocomplete="off" />
    <button>Send</button>
  </form>
</template>

<script>
import { defineComponent, ref } from 'vue'
import { io } from 'socket.io-client'

export default defineComponent({
  setup () {
    const socket = io('http://localhost:3000', {
      // 重新连接的最大延迟事件(秒)
      reconnectionDelayMax: 10000,
      // 传递身份信息
      auth: {
        token: '123'
      },
      // 自定义请求查询参数
      query: {
        'my-key': 'my-value'
      },
      // 添加请求头
      extraHeaders: {}
    })

    socket.on('connect', () => {
      console.log('连接建立成功')
    })

    socket.on('disconnect', () => {
      console.log('连接断开')
    })

    socket.on('connect_error', () => {
      console.log('连接失败')
    })

    const inputMessage = ref('')
    const messages = ref([])

    // 发送消息
    const sendMessage = () => {
      if (inputMessage.value) {
        socket.emit('chat message', inputMessage.value)
        inputMessage.value = ''
      }
    }

    // 接收消息
    socket.on('chat message', message => {
      messages.value.push(message)
    })

    return {
      inputMessage,
      messages,
      sendMessage
    }
  }
})
</script>

<style>...</style>

用户登录注册

登录页面

Login.vue:

<template>
  <h1>用户登录</h1>
  <form @submit.prevent="handleSubmit">
    <div>
      <label for="">用户名</label>
      <input v-model="user.username" type="text">
    </div>
    <div>
      <label for="">密码</label>
      <input v-model="user.password" type="text">
    </div>
    <div>
      <button>登录/注册</button>
    </div>
  </form>
</template>

<script>
import { defineComponent, reactive } from 'vue'
import axios from 'axios'
import { useRouter } from 'vue-router'

export default defineComponent({
  setup () {
    const router = useRouter()
    const user = reactive({
      username: '',
      password: ''
    })
    const handleSubmit = async () => {
      try {
        const { data } = await axios.post('http://localhost:3000/api/login', user)

        // 跳转首页
        router.push('/')
        console.log(data)
      } catch (err) {
        window.alert(err.message)
      }
    }
    return {
      user,
      handleSubmit
    }
  }
})
</script>

<style>

</style>

数据库安装

本例使用 MongoDB 数据库,安装参考 《MongoDB v5.0.1 和 Robo 3T v1.4.3 安装》

服务端连接数据库和配置模型

在 socket-chat-server 目录下安装 mongoose 工具 npm i mongoose

mongoose 官网:Mongoose v6.0.13: Getting Started

创建 models 文件夹,在文件夹下创建 index.js:

// models/index.js
const mongoose = require('mongoose')

// 连接 MongoDB 数据库
mongoose
  .connect('mongodb://localhost/chat')
  .then(() => {
    console.log('连接成功')
  })
  .catch(err => {
    console.log('连接失败', err)
  })


module.exports = {
  User: mongoose.model('User', require('./user'))
}

在文件夹下创建 user.js:

// models/user.js
/**
 * 用户模型
 */
const mongoose = require('mongoose')

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true
  },
  password: {
    type: String,
    required: true
  }
})

module.exports = userSchema

编写路由接口

在根目录下创建 controllers/user.js:

// controllers/user.js
const { User } = require('../models')

exports.login = async (req, res, next) => {
  try {
    const { username, password } = req.body
    // 查询用户
    let user = await User.findOne({ username })
    if (user) {
      // 用户存在
      if (user.password !== password) {
        return res.status(400).json({
          error: '密码不正确'
        })
      }
    } else {
      // 用户不存在,注册
      user = await new User(req.body).save()
    }

    // 登录
    res.status(200).json({
      user: {
        username: user.username,
        token: 'xxx' // 用户身份
      }
    })
  } catch (err) {
    next(err)
  }
}

在根目录下创建 router.js

// router.js
const express = require('express')
const router = express.Router()
const userController = require('./controllers/user')

router.post('/login', userController.login)

module.exports = router

配置接口

安装 cors 依赖:npm i cors

修改 app.js:

// app.js
const express = require('express')
const app = express()

// 配置解析 POST 请求体,会将解析的数据挂载到 req.body 上
app.use(express.json())

// 配置 express HTTP cors
const cors = require('cors')
app.use(cors())

// 配置路由
const router = require('./router')
app.use('/api', router)

const http = require('http')
const server = http.createServer(app)
const { Server } = require('socket.io')
// 配置 Socket cors
const io = new Server(server, {
  cors: {
    orgiin: '*'
  }
})

...

身份认证

服务端生成 token

生成 token 一般使用 JWT(json web token),安装依赖 npm i jsonwebtoken

生成 token:

// controllers/user.js
const { User } = require('../models')
const jwt = require('jsonwebtoken')

exports.login = async (req, res, next) => {
  try {
    const { username, password } = req.body
    // 查询用户
    let user = await User.findOne({ username })
    if (user) {
      // 用户存在
      if (user.password !== password) {
        return res.status(400).json({
          error: '密码不正确'
        })
      }
    } else {
      // 用户不存在,注册
      user = await new User(req.body).save()
    }

    // 登录
    res.status(200).json({
      user: {
        username: user.username,
        // 用户身份(生成 token)
        token: jwt.sign(
          // 自定义数据
          {
            userId: user._id
          },
          // 私钥 (可以使用UUID,网上很多在线生成)
          '5c0717dc-2996-4d9e-b166-619d0241abde',
          // 过期设置
          {
            expiresIn: '24h'
          }
        )
      }
    })
  } catch (err) {
    next(err)
  }
}

客户端存储用户信息

将用户信息存储到本地缓存和 Store 中。

Login.vue:

<template>
  <h1>用户登录</h1>
  <form @submit.prevent="handleSubmit">
    <div>
      <label for="">用户名</label>
      <input v-model="user.username" type="text">
    </div>
    <div>
      <label for="">密码</label>
      <input v-model="user.password" type="text">
    </div>
    <div>
      <button>登录/注册</button>
    </div>
  </form>
</template>

<script>
import { defineComponent, reactive } from 'vue'
import axios from 'axios'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'

export default defineComponent({
  setup () {
    const router = useRouter()
    const store = useStore()
    const user = reactive({
      username: '',
      password: ''
    })
    const handleSubmit = async () => {
      try {
        const { data } = await axios.post('http://localhost:3000/api/login', user)

        store.commit('setUser', data)

        // 跳转首页
        router.push('/')
      } catch (err) {
        window.alert(err.message)
      }
    }
    return {
      user,
      handleSubmit
    }
  }
})
</script>

<style>

</style>

// src/store/index.js
import { createStore } from 'vuex'

export default createStore({
  state: {
    user: JSON.parse(window.localStorage.getItem('user'))
  },
  mutations: {
    setUser (state, payload) {
      state.user = payload.user
      window.localStorage.setItem('user', JSON.stringify(payload.user))
    }
  },
  actions: {
  },
  modules: {
  }
})

路由鉴权

// src/router/index.js
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '../views/Home.vue'
import store from '@/store'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    meta: { requiresAuth: true }
  },
  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('../views/Login.vue')
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    const { user } = store.state
    if (!user) {
      next({
        path: '/login',
        query: {
          redirect: to.fullPath
        }
      })
    }
  }
  next()
})

export default router

socket 建立连接时传递 token

Home.vue

<template>
  <!-- 消息列表 -->
  <ul id="messages">
    <li v-for="(message, index) in messages" :key="index">{{ message }}</li>
  </ul>

  <!-- 发送消息的表单 -->
  <form id="form" @submit.prevent="sendMessage">
    <input v-model="inputMessage" id="input" autocomplete="off" />
    <button>Send</button>
  </form>
</template>

<script>
import { defineComponent, ref } from 'vue'
import { io } from 'socket.io-client'
import { useStore } from 'vuex'

export default defineComponent({
  setup () {
    const store = useStore()

    const socket = io('http://localhost:3000', {
      // 重新连接的最大延迟事件(秒)
      reconnectionDelayMax: 10000,
      // 传递身份信息
      auth: {
        token: store.state.user.token
      },
      // 自定义请求查询参数
      query: {
        'my-key': 'my-value'
      },
      // 添加请求头
      extraHeaders: {}
    })

    socket.on('connect', () => {
      console.log('连接建立成功')
    })

    socket.on('disconnect', () => {
      console.log('连接断开')
    })

    socket.on('connect_error', () => {
      console.log('连接失败')
    })

    const inputMessage = ref('')
    const messages = ref([])

    // 发送消息
    const sendMessage = () => {
      if (inputMessage.value) {
        socket.emit('chat message', inputMessage.value)
        inputMessage.value = ''
      }
    }

    // 接收消息
    socket.on('chat message', message => {
      messages.value.push(message)
    })

    return {
      inputMessage,
      messages,
      sendMessage
    }
  }
})
</script>

<style>...</style>

socket 身份认证

Socket.IO 提供了中间件机制统一处理登录状态:Middlewares | Socket.IO

// app.js
const express = require('express')
const app = express()

// 配置解析 POST 请求体,会将解析的数据挂载到 req.body 上
app.use(express.json())

// 配置 express HTTP cors
const cors = require('cors')
app.use(cors())

// 配置路由
const router = require('./router')
app.use('/api', router)

const http = require('http')
const server = http.createServer(app)
const { Server } = require('socket.io')
// 配置 Socket cors
const io = new Server(server, {
  cors: {
    orgiin: '*'
  }
})

const jwt = require('jsonwebtoken')
const { User } = require('./models')
// socket 通信中间件
// 每个连接只执行一次此函数(即使连接包含多个HTTP请求)
io.use((socket, next) => {
  const token = socket.handshake.auth.token
  // 验证token(注意私钥要一致)
  jwt.verify(token, '5c0717dc-2996-4d9e-b166-619d0241abde', async (err, data) => {
    if (err) {
      return next(new Error('身份认证失败'))
    }

    // 验证成功
    const user = await User.findById(data.userId)
    console.log('认证通过')
    // 将查到的用户挂载到 request 请求对象中,给后面的处理使用
    socket.request.user = user

    next()
  })
})

// 处理 HTTP 协议的使用 Express 的 app 实例
app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html')
})

// 处理 WebSocket 协议的使用 socket.io 的 io 实例
// 当使用 WebSocket 协议通信和服务端建立连接成功后触发 connection 事件
io.on('connection', socket => {
  console.log('有一个用户连接成功')
  // 监听连接断开事件(刷新页面可测试)
  socket.on('disconnect', () => {
    console.log('用户已断开连接')
  })

  // 接收消息
  socket.on('chat message', data => {
    console.log('message => ', data)
    // 发送消息
    // 发送给当前所有已连接的在线用户(可以打开多个页面建立多个连接)
    io.emit('chat message', {
      nickname: socket.request.user.username,
      message: data
    })
  })
})

server.listen(3000, () => {
  console.log('listening on *:3000')
})

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值