Code前端首页关于Code前端联系我们

WebSocket 基础先搞懂?

terry 3小时前 阅读数 4 #Vue
文章标签 WebSocket 基础

想在 Vue3 项目里做实时聊天、股票行情推送这类功能,WebSocket 肯定绕不开,但刚接触时,不少同学会犯难:咋在 Vue3 里初始化连接?消息收发咋和响应式数据结合?断开重连咋处理?今天用问答方式,把 Vue3 + WebSocket 从基础到实战掰碎了讲,新手也能跟着做。

先别急着写代码,得先明白 WebSocket 是干啥的,它是 HTML5 提供的**全双工通信协议**,能让客户端和服务端随时双向发消息,和 HTTP 有啥不一样?HTTP 是“请求 - 响应”模式,服务端没法主动给客户端发消息;但 WebSocket 建立连接后,两边想发就发,特别适合实时性高的场景(比如聊天、实时数据监控)。 举个例子:你刷短视频 App 时,有人给你发评论通知,这背后可能就是 WebSocket 让服务端主动把“新评论”消息推给你,要是用 HTTP,得不停轮询(比如每隔几秒发请求问有没有新消息),既费流量又延迟高,WebSocket 就解决了这痛点。

Vue3 项目里怎么初始化 WebSocket 连接?

在 Vue3 里用 WebSocket,核心是创建 WebSocket 实例,再和 Vue 的生命周期、响应式数据结合,咱分步骤看:

创建 WebSocket 实例

WebSocket 是浏览器原生 API,直接 new 就行,假设服务端地址是 ws://xxx,在 Vue3 的组件里(用组合式 API 举例),可以这么写:

<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
const ws = ref(null) // 存WebSocket实例
onMounted(() => {
  // 初始化连接
  ws.value = new WebSocket('ws://你的服务端地址')
  // 监听连接成功
  ws.value.onopen = () => {
    console.log('WebSocket 连接成功')
    // 连接成功后可以发初始化消息,比如用户身份
    ws.value.send(JSON.stringify({ type: 'login', userId: 'xxx' }))
  }
  // 监听连接错误
  ws.value.onerror = (err) => {
    console.error('WebSocket 连接出错:', err)
  }
})
onUnmounted(() => {
  // 组件销毁时关闭连接,避免内存泄漏
  if (ws.value) {
    ws.value.close()
  }
})
</script>

这里用 onMounted 初始化(组件挂载后建立连接),onUnmounted 销毁时关闭连接,用 ref 存实例,是为了让 Vue 能追踪它的状态变化。

注意服务端兼容性

前端能连,得服务端也支持 WebSocket 协议,Node.js 用 ws 库、Java 用 Netty 这类框架都能搭 WebSocket 服务,要是你本地测试,也能找些公开的 WebSocket 测试地址(ws://echo.websocket.org 是经典的回声测试服务,发消息会原样返回)。

怎么处理消息收发?

连接建好了,接下来是发消息收消息,还要把收到的消息同步到 Vue 的响应式数据里,让界面自动更新。

发消息:主动给服务端发数据

WebSocket 实例的 send() 方法负责发消息,消息可以是字符串或二进制数据,实际开发常用 JSON 字符串(方便结构化数据),比如做聊天功能,用户输入内容后发消息:

<template>
  <input v-model="inputMsg" placeholder="请输入消息" />
  <button @click="sendMsg">发送</button>
</template>
<script setup>
import { ref } from 'vue'
const inputMsg = ref('')
const ws = ref(null)
const sendMsg = () => {
  if (ws.value && ws.value.readyState === WebSocket.OPEN) { // 确保连接已打开
    ws.value.send(JSON.stringify({ 
      type: 'chat', 
      content: inputMsg.value 
    }))
    inputMsg.value = '' // 清空输入框
  } else {
    alert('连接未就绪,请稍等')
  }
}
</script>

收消息:服务端推消息到前端

onmessage 监听服务端发来的消息,收到后,把消息存到响应式数据里(messages 列表),界面就会自动更新:

<script setup>
import { ref, onMounted } from 'vue'
const messages = ref([]) // 存所有聊天消息
const ws = ref(null)
onMounted(() => {
  ws.value = new WebSocket('ws://xxx')
  ws.value.onmessage = (event) => {
    // event.data 是服务端发来的消息
    const msg = JSON.parse(event.data)
    messages.value.push(msg) // 响应式数据更新,界面自动渲染
  }
})
</script>
<template>
  <div v-for="(msg, index) in messages" :key="index">
    {{ msg.content }}
  </div>
</template>

这里要注意,onmessage 里的操作要简洁,别做太耗时的事,不然影响性能,如果消息处理复杂,可以拆成函数调用。

连接状态管理有啥技巧?

WebSocket 连接很“脆弱”——网络波动、服务端重启都会导致断开,所以得做心跳保活自动重连,让连接更稳定。

心跳机制:防止连接被网关断开

有些服务器或中间网关(比如公司内网、nginx)会把长时间没动静的连接断开,心跳就是定时给服务端发个“我还活着”的消息,保持连接。

在 Vue3 里可以用 setInterval 实现:

<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
const ws = ref(null)
let heartBeatTimer = null
onMounted(() => {
  ws.value = new WebSocket('ws://xxx')
  // 连接成功后启动心跳
  ws.value.onopen = () => {
    heartBeatTimer = setInterval(() => {
      if (ws.value.readyState === WebSocket.OPEN) {
        ws.value.send(JSON.stringify({ type: 'heartbeat' }))
      }
    }, 30 * 1000) // 每30秒发一次心跳
  }
})
onUnmounted(() => {
  clearInterval(heartBeatTimer) // 组件销毁时清除定时器
  if (ws.value) ws.value.close()
})
</script>

自动重连:断网后自动恢复连接

当连接意外断开(比如用户切到弱网环境),得自动尝试重连,可以监听 onclose 事件,结合延迟重连策略:

<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
const ws = ref(null)
let reconnectTimer = null
const reconnect = () => {
  reconnectTimer = setTimeout(() => {
    ws.value = new WebSocket('ws://xxx')
    // 重连后要重新绑定事件(比如onmessage、onopen)
    ws.value.onopen = () => {
      clearTimeout(reconnectTimer) // 重连成功,清除重连定时器
      console.log('WebSocket 重连成功')
    }
    ws.value.onclose = () => {
      reconnect() // 连接又断了,继续重连
    }
  }, 5 * 1000) // 5秒后重试
}
onMounted(() => {
  ws.value = new WebSocket('ws://xxx')
  ws.value.onclose = () => {
    console.log('WebSocket 连接断开,开始重连')
    reconnect()
  }
})
onUnmounted(() => {
  clearTimeout(reconnectTimer)
  if (ws.value) ws.value.close()
})
</script>

这样即使网络波动,也能自动恢复连接,用户几乎没感知。

实战:做个简易实时聊天组件

光讲理论不够,咱动手做个能收发消息的聊天组件,把前面的知识点串起来。

步骤1:搭建界面结构

<template>
  <div class="chat-container">
    <!-- 消息列表 -->
    <div class="messages">
      <div 
        v-for="(msg, index) in messages" 
        :key="index" 
        :class="['msg-item', { self: msg.isSelf }]"
      >
        <span class="sender">{{ msg.sender }}:</span>
        <span class="content">{{ msg.content }}</span>
      </div>
    </div>
    <!-- 输入区域 -->
    <div class="input-area">
      <input 
        v-model="inputMsg" 
        placeholder="输入消息后按回车发送" 
        @keyup.enter="sendMsg"
      />
      <button @click="sendMsg">发送</button>
    </div>
  </div>
</template>

步骤2:编写逻辑代码

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// 响应式数据
const messages = ref([]) // 消息列表
const inputMsg = ref('') // 输入框内容
const ws = ref(null)    // WebSocket实例
// 初始化WebSocket
onMounted(() => {
  // 这里用公开测试服务,实际替换成自己的服务端地址
  ws.value = new WebSocket('ws://echo.websocket.org') 
  // 连接成功
  ws.value.onopen = () => {
    console.log('连接成功,可发送消息')
    // 模拟发送登录消息(实际项目传用户ID等)
    ws.value.send(JSON.stringify({ 
      type: 'login', 
      userId: 'vue3_user' 
    }))
  }
  // 接收消息
  ws.value.onmessage = (event) => {
    const res = JSON.parse(event.data)
    // 测试服务会原样返回,这里模拟区分自己和对方消息
    messages.value.push({
      sender: res.isSelf ? '我' : '对方',
      content: res.content,
      isSelf: res.isSelf
    })
  }
  // 连接错误
  ws.value.onerror = (err) => {
    console.error('WebSocket出错:', err)
    alert('连接失败,请检查网络')
  }
  // 连接关闭(自动重连逻辑可参考前面的方法添加)
  ws.value.onclose = () => {
    console.log('连接已关闭')
  }
})
// 发送消息
const sendMsg = () => {
  if (!inputMsg.value.trim()) return alert('请输入内容')
  if (ws.value && ws.value.readyState === WebSocket.OPEN) {
    const msg = {
      type: 'chat',
      content: inputMsg.value,
      isSelf: true // 标记是自己发的消息
    }
    ws.value.send(JSON.stringify(msg))
    inputMsg.value = '' // 清空输入框
  } else {
    alert('连接未就绪,请稍等')
  }
}
// 组件销毁时关闭连接
onUnmounted(() => {
  if (ws.value) {
    ws.value.close()
  }
})
</script>

步骤3:加简单样式(可选)

<style scoped>
.chat-container {
  width: 300px;
  margin: 20px auto;
  border: 1px solid #eee;
  border-radius: 8px;
  padding: 10px;
}
.messages {
  height: 300px;
  overflow-y: auto;
  margin-bottom: 10px;
}
.msg-item {
  margin: 5px 0;
  padding: 4px 8px;
  border-radius: 4px;
}
.msg-item.self {
  background: #e3f2fd;
  margin-left: auto;
}
.input-area {
  display: flex;
}
.input-area input {
  flex: 1;
  padding: 6px;
  border: 1px solid #ddd;
  border-radius: 4px 0 0 4px;
}
.input-area button {
  padding: 0 16px;
  border: none;
  background: #42b983;
  color: #fff;
  border-radius: 0 4px 4px 0;
  cursor: pointer;
}
</style>

现在运行组件,输入消息按回车,服务端会原样返回,消息列表里能看到自己和“对方”的消息(因为测试服务会回声,这里用isSelf标记区分),实际项目里,服务端会把消息转发给其他用户,逻辑类似。

性能和兼容性要注意啥?

代码跑通了,还要考虑生产环境的坑:

浏览器兼容性

WebSocket 是 HTML5 特性,现代浏览器(Chrome、Edge、Firefox、Safari)都支持,但老旧浏览器(IE)完全不支持,如果要兼容旧版,得用 Socket.IO 这类带 fallback 的库(但 Vue3 项目里用的话,要注意和框架的结合)。

连接数和消息大小

  • 单个客户端一般别同时连太多 WebSocket 服务(浏览器对同域名的连接数有限制,Chrome 是 6 个左右)。
  • 消息别太大!WebSocket 虽然后台是 TCP,但消息过大容易触发延迟或断开,建议拆分大消息,或者用二进制格式压缩。

Vue3 响应式优化

如果消息量极大(比如实时行情每秒几十条),直接往 refreactive 里 push 消息会触发频繁更新,影响性能,可以用 shallowRef 或者批量更新(比如攒一批消息再渲染)。

import { shallowRef, nextTick } from 'vue'
const messages = shallowRef([])
let tempMessages = [] // 临时数组存消息
ws.value.onmessage = (event) => {
  tempMessages.push(JSON.parse(event.data))
  // 每100ms批量更新一次
  setTimeout(() => {
    messages.value = [...tempMessages]
    tempMessages = []
  }, 100)
}

这样能减少响应式更新次数,提升性能。

现在再回头看,Vue3 里用 WebSocket 其实是“浏览器原生 API + Vue 响应式 + 业务逻辑”的结合,从初始化连接、收发消息,到心跳保活、自动重连,每个环节都要兼顾功能和稳定性,把今天的知识点落地到项目里,不管是做聊天、IM 还是实时数据看板,思路都通了,要是你在实践中碰到服务端配置、跨域这些问题,下次可以专门聊聊 WebSocket 服务端搭建和前端跨域处理~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门