MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue网络请求 WebSocket在Vue项目中的应用案例

2021-03-185.8k 阅读

一、WebSocket 基础概念

在深入探讨 WebSocket 在 Vue 项目中的应用案例之前,我们先来回顾一下 WebSocket 的基本概念。

1.1 WebSocket 是什么

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。与传统的 HTTP 请求 - 响应模式不同,HTTP 是无状态的,每次请求都需要建立新的连接并发送完整的头部信息,而 WebSocket 一旦建立连接,客户端和服务器之间就可以相互主动发送消息,实现实时通信。这种特性使得 WebSocket 在很多场景下具有显著优势,比如实时聊天、在线游戏、股票行情实时推送等。

1.2 WebSocket 与 HTTP 的关系

WebSocket 协议在设计上与 HTTP 协议兼容,它的握手过程是基于 HTTP 协议的。WebSocket 的 URL 采用 ws://(非加密)或 wss://(加密,类似 HTTPS)前缀,例如 ws://example.com/socket。在握手阶段,客户端通过 HTTP 请求向服务器发起 WebSocket 连接请求,服务器响应一个特殊的 HTTP 响应来确认升级协议到 WebSocket。一旦握手成功,后续的数据传输就不再使用 HTTP 协议,而是通过 WebSocket 协议进行全双工通信。

1.3 WebSocket 协议的特点

  • 全双工通信:客户端和服务器可以同时向对方发送消息,而不像 HTTP 那样只能由客户端发起请求,服务器被动响应。
  • 低开销:WebSocket 协议头部相对简单,在建立连接后的数据传输中,不需要像 HTTP 那样每次都携带大量的头部信息,因此传输效率更高,适合实时性要求高的数据传输。
  • 实时性强:由于能够主动推送消息,WebSocket 非常适合需要实时更新数据的场景,如实时监控系统、实时报表等。

二、Vue 项目中引入 WebSocket

在 Vue 项目中使用 WebSocket,我们首先需要创建 WebSocket 实例并进行必要的配置。

2.1 创建 WebSocket 实例

在 Vue 组件中,我们可以在 created 生命周期钩子函数中创建 WebSocket 实例。假设我们的服务器端 WebSocket 地址为 ws://localhost:8080/socket,代码如下:

export default {
  name: 'WebSocketExample',
  data() {
    return {
      socket: null,
      message: ''
    };
  },
  created() {
    this.socket = new WebSocket('ws://localhost:8080/socket');
    this.socket.onopen = this.handleOpen;
    this.socket.onmessage = this.handleMessage;
    this.socket.onerror = this.handleError;
    this.socket.onclose = this.handleClose;
  },
  methods: {
    handleOpen() {
      console.log('WebSocket 连接已建立');
    },
    handleMessage(event) {
      this.message = JSON.parse(event.data);
      console.log('收到消息:', this.message);
    },
    handleError(error) {
      console.log('WebSocket 连接错误:', error);
    },
    handleClose() {
      console.log('WebSocket 连接已关闭');
    }
  }
};

在上述代码中,我们在 created 钩子函数中创建了一个 WebSocket 实例,并为 onopenonmessageonerroronclose 事件分别绑定了处理函数。onopen 事件在 WebSocket 连接成功建立时触发,onmessage 事件在接收到服务器发送的消息时触发,onerror 事件在连接过程中发生错误时触发,onclose 事件在连接关闭时触发。

2.2 发送消息

要向服务器发送消息,我们可以在 Vue 组件中定义一个方法来调用 WebSocket 的 send 方法。继续上面的代码,我们添加一个发送消息的方法:

export default {
  // 前面的代码不变
  methods: {
    // 前面的事件处理函数不变
    sendMessage() {
      const data = {
        type: 'chat',
        content: 'Hello, server!'
      };
      this.socket.send(JSON.stringify(data));
    }
  }
};

sendMessage 方法中,我们构造了一个包含消息类型和内容的对象,然后使用 JSON.stringify 将其转换为字符串并通过 WebSocket 发送给服务器。

三、Vuex 与 WebSocket 结合应用

在大型 Vue 项目中,状态管理是非常重要的。Vuex 是 Vue.js 官方的状态管理库,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。将 Vuex 与 WebSocket 结合,可以更好地管理 WebSocket 相关的状态和逻辑。

3.1 创建 Vuex 模块

首先,我们创建一个专门用于 WebSocket 相关状态管理的 Vuex 模块。在 store/modules/websocket.js 文件中编写如下代码:

const state = {
  isConnected: false,
  messages: [],
  error: null
};

const mutations = {
  SET_CONNECTION_STATUS(state, status) {
    state.isConnected = status;
  },
  ADD_MESSAGE(state, message) {
    state.messages.push(message);
  },
  SET_ERROR(state, error) {
    state.error = error;
  }
};

const actions = {
  connect({ commit }) {
    return new Promise((resolve, reject) => {
      const socket = new WebSocket('ws://localhost:8080/socket');
      socket.onopen = () => {
        commit('SET_CONNECTION_STATUS', true);
        resolve();
      };
      socket.onmessage = (event) => {
        const message = JSON.parse(event.data);
        commit('ADD_MESSAGE', message);
      };
      socket.onerror = (error) => {
        commit('SET_ERROR', error);
        reject(error);
      };
      socket.onclose = () => {
        commit('SET_CONNECTION_STATUS', false);
      };
    });
  },
  sendMessage({ state }, data) {
    if (state.isConnected) {
      const socket = new WebSocket('ws://localhost:8080/socket');
      socket.send(JSON.stringify(data));
    } else {
      console.error('WebSocket 未连接,无法发送消息');
    }
  }
};

export default {
  namespaced: true,
  state,
  mutations,
  actions
};

在上述代码中,我们定义了 state 来存储 WebSocket 的连接状态、接收到的消息以及错误信息。mutations 用于修改 stateactions 用于处理异步操作,如连接 WebSocket 和发送消息。

3.2 在 Vue 组件中使用 Vuex 模块

在 Vue 组件中,我们可以通过 mapStatemapActions 辅助函数来使用 Vuex 模块中的状态和动作。假设我们有一个 Chat.vue 组件,代码如下:

<template>
  <div>
    <h2>WebSocket 聊天</h2>
    <p v-if="isConnected">已连接</p>
    <p v-else>未连接</p>
    <button @click="connect">连接</button>
    <button @click="sendMessage({ type: 'chat', content: 'Hello from Vuex' })">发送消息</button>
    <ul>
      <li v-for="(message, index) in messages" :key="index">{{ message.content }}</li>
    </ul>
    <p v-if="error">{{ error.message }}</p>
  </div>
</template>

<script>
import { mapState, mapActions } from 'vuex';

export default {
  name: 'Chat',
  computed: {
   ...mapState('websocket', ['isConnected','messages', 'error'])
  },
  methods: {
   ...mapActions('websocket', ['connect','sendMessage'])
  }
};
</script>

在这个组件中,我们通过 mapState 计算属性获取 WebSocket 的连接状态、消息列表和错误信息,并通过 mapActions 定义了连接和发送消息的方法。这样,通过 Vuex,我们可以更好地在多个组件之间共享 WebSocket 的状态和逻辑。

四、WebSocket 在实时聊天应用中的案例

实时聊天是 WebSocket 最典型的应用场景之一。下面我们以一个简单的实时聊天应用为例,详细说明 WebSocket 在 Vue 项目中的应用。

4.1 项目结构

我们的项目结构如下:

src/
├── assets/
├── components/
│   ├── ChatMessage.vue
│   └── ChatInput.vue
├── store/
│   ├── modules/
│   │   └── websocket.js
│   └── index.js
├── App.vue
└── main.js

其中,ChatMessage.vue 用于显示聊天消息,ChatInput.vue 用于输入和发送聊天消息。

4.2 ChatMessage.vue 组件

ChatMessage.vue 组件代码如下:

<template>
  <li>{{ message.content }}</li>
</template>

<script>
export default {
  name: 'ChatMessage',
  props: {
    message: {
      type: Object,
      required: true
    }
  }
};
</script>

这个组件接收一个 message 属性,用于显示聊天消息的内容。

4.3 ChatInput.vue 组件

ChatInput.vue 组件代码如下:

<template>
  <div>
    <input v-model="inputValue" placeholder="输入消息">
    <button @click="sendMessage">发送</button>
  </div>
</template>

<script>
import { mapActions } from 'vuex';

export default {
  name: 'ChatInput',
  data() {
    return {
      inputValue: ''
    };
  },
  methods: {
   ...mapActions('websocket', ['sendMessage']),
    sendMessage() {
      if (this.inputValue) {
        const message = {
          type: 'chat',
          content: this.inputValue
        };
        this.sendMessage(message);
        this.inputValue = '';
      }
    }
  }
};
</script>

在这个组件中,我们通过 v - model 双向绑定输入框的值到 inputValue 数据属性。当点击发送按钮时,如果输入框不为空,我们构造一个聊天消息对象并调用 Vuex 中的 sendMessage 动作来发送消息。

4.4 App.vue 组件

App.vue 组件负责整合聊天消息显示和输入功能,代码如下:

<template>
  <div id="app">
    <h1>实时聊天应用</h1>
    <button @click="connect">连接</button>
    <ul>
      <ChatMessage v - for="(message, index) in messages" :key="index" :message="message" />
    </ul>
    <ChatInput />
  </div>
</template>

<script>
import ChatMessage from './components/ChatMessage.vue';
import ChatInput from './components/ChatInput.vue';
import { mapState, mapActions } from 'vuex';

export default {
  name: 'App',
  components: {
    ChatMessage,
    ChatInput
  },
  computed: {
   ...mapState('websocket', ['messages'])
  },
  methods: {
   ...mapActions('websocket', ['connect'])
  }
};
</script>

App.vue 组件中,我们引入了 ChatMessageChatInput 组件。通过 mapState 获取聊天消息列表,通过 mapActions 定义连接 WebSocket 的方法。

4.5 服务器端实现(简单示例)

为了完整演示实时聊天应用,我们还需要一个简单的服务器端实现。这里以 Node.js 和 ws 库为例,创建一个简单的 WebSocket 服务器。在项目根目录下创建 server.js 文件,代码如下:

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
  ws.on('message', (message) => {
    const data = JSON.parse(message);
    console.log('收到消息:', data);
    // 简单广播消息给所有连接的客户端
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    });
  });

  ws.on('close', () => {
    console.log('客户端连接已关闭');
  });

  ws.on('error', (error) => {
    console.log('WebSocket 服务器错误:', error);
  });
});

console.log('WebSocket 服务器已启动,监听端口 8080');

在上述代码中,我们创建了一个简单的 WebSocket 服务器,当接收到客户端发送的消息时,将其广播给所有连接的客户端。

通过以上步骤,我们实现了一个简单的基于 WebSocket 的实时聊天应用,展示了 WebSocket 在 Vue 项目中的实际应用。

五、WebSocket 在实时数据监控中的应用案例

实时数据监控是另一个适合使用 WebSocket 的重要场景。例如,我们要监控服务器的 CPU 使用率、内存使用率等实时数据,并在前端实时展示。

5.1 前端实现

在 Vue 项目中,我们同样需要创建 WebSocket 连接并处理接收到的数据。首先,在 created 生命周期钩子函数中建立连接:

export default {
  name: 'DataMonitor',
  data() {
    return {
      socket: null,
      cpuUsage: 0,
      memoryUsage: 0
    };
  },
  created() {
    this.socket = new WebSocket('ws://localhost:8080/monitor');
    this.socket.onopen = this.handleOpen;
    this.socket.onmessage = this.handleMessage;
    this.socket.onerror = this.handleError;
    this.socket.onclose = this.handleClose;
  },
  methods: {
    handleOpen() {
      console.log('WebSocket 连接已建立');
    },
    handleMessage(event) {
      const data = JSON.parse(event.data);
      this.cpuUsage = data.cpuUsage;
      this.memoryUsage = data.memoryUsage;
    },
    handleError(error) {
      console.log('WebSocket 连接错误:', error);
    },
    handleClose() {
      console.log('WebSocket 连接已关闭');
    }
  }
};

handleMessage 方法中,我们解析接收到的 JSON 数据,并更新组件的 cpuUsagememoryUsage 数据属性。

5.2 服务器端实现

服务器端我们可以使用 Node.js 和 os 模块来获取系统信息,并通过 WebSocket 发送给前端。创建 monitorServer.js 文件,代码如下:

const WebSocket = require('ws');
const os = require('os');

const wss = new WebSocket.Server({ port: 8080 });

function getSystemInfo() {
  const cpuUsage = os.loadavg()[0];
  const memoryUsage = (os.totalmem() - os.freemem()) / os.totalmem() * 100;
  return {
    cpuUsage,
    memoryUsage
  };
}

setInterval(() => {
  const systemInfo = getSystemInfo();
  wss.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(JSON.stringify(systemInfo));
    }
  });
}, 5000);

在上述代码中,我们使用 setInterval 每 5 秒获取一次系统的 CPU 和内存使用率,并通过 WebSocket 发送给所有连接的客户端。

5.3 展示数据

在 Vue 组件的模板中,我们可以展示这些实时数据:

<template>
  <div>
    <h2>实时数据监控</h2>
    <p>CPU 使用率: {{ cpuUsage }}%</p>
    <p>内存使用率: {{ memoryUsage }}%</p>
  </div>
</template>

这样,通过 WebSocket,我们实现了实时数据监控功能,前端能够实时显示服务器的系统信息。

六、处理 WebSocket 连接的稳定性

在实际应用中,WebSocket 连接可能会因为网络波动、服务器故障等原因断开。为了保证应用的稳定性,我们需要处理连接的重连机制。

6.1 简单重连机制

一种简单的重连机制是在连接关闭时,等待一定时间后尝试重新连接。我们可以在 Vuex 的 WebSocket 模块中实现这个机制。修改 websocket.js 文件中的 actions

const actions = {
  connect({ commit }) {
    return new Promise((resolve, reject) => {
      const socket = new WebSocket('ws://localhost:8080/socket');
      socket.onopen = () => {
        commit('SET_CONNECTION_STATUS', true);
        resolve();
      };
      socket.onmessage = (event) => {
        const message = JSON.parse(event.data);
        commit('ADD_MESSAGE', message);
      };
      socket.onerror = (error) => {
        commit('SET_ERROR', error);
        reject(error);
      };
      socket.onclose = () => {
        commit('SET_CONNECTION_STATUS', false);
        setTimeout(() => {
          this.dispatch('connect');
        }, 5000);
      };
    });
  },
  sendMessage({ state }, data) {
    if (state.isConnected) {
      const socket = new WebSocket('ws://localhost:8080/socket');
      socket.send(JSON.stringify(data));
    } else {
      console.error('WebSocket 未连接,无法发送消息');
    }
  }
};

socket.onclose 事件处理函数中,我们使用 setTimeout 在连接关闭 5 秒后调用 this.dispatch('connect') 重新连接 WebSocket。

6.2 指数退避重连机制

简单重连机制在网络不稳定的情况下可能会频繁尝试连接,消耗过多资源。指数退避重连机制可以在每次重连失败后,增加等待时间,避免过度请求。修改后的代码如下:

const actions = {
  connect({ commit }, { initialDelay = 1000, maxDelay = 30000 }) {
    let delay = initialDelay;
    const connectInner = () => {
      const socket = new WebSocket('ws://localhost:8080/socket');
      socket.onopen = () => {
        commit('SET_CONNECTION_STATUS', true);
        delay = initialDelay;
      };
      socket.onmessage = (event) => {
        const message = JSON.parse(event.data);
        commit('ADD_MESSAGE', message);
      };
      socket.onerror = (error) => {
        commit('SET_ERROR', error);
        delay = Math.min(delay * 2, maxDelay);
        setTimeout(connectInner, delay);
      };
      socket.onclose = () => {
        commit('SET_CONNECTION_STATUS', false);
        delay = Math.min(delay * 2, maxDelay);
        setTimeout(connectInner, delay);
      };
    };
    connectInner();
  },
  sendMessage({ state }, data) {
    if (state.isConnected) {
      const socket = new WebSocket('ws://localhost:8080/socket');
      socket.send(JSON.stringify(data));
    } else {
      console.error('WebSocket 未连接,无法发送消息');
    }
  }
};

在上述代码中,我们定义了 connect 动作接受初始延迟时间 initialDelay 和最大延迟时间 maxDelay。每次连接失败或关闭时,延迟时间 delay 会翻倍,但不会超过 maxDelay。这样可以更合理地处理重连,提高连接的稳定性。

七、安全考虑

在使用 WebSocket 时,安全是至关重要的。以下是一些常见的安全考虑因素。

7.1 使用 wss:// 协议

与 HTTP 和 HTTPS 的关系类似,WebSocket 也有 ws://(非加密)和 wss://(加密)两种协议。在生产环境中,特别是涉及敏感数据传输时,一定要使用 wss:// 协议,以防止数据被中间人截取和篡改。

7.2 身份验证

在建立 WebSocket 连接之前,需要进行身份验证,确保只有合法的用户能够连接到服务器。可以在 HTTP 握手阶段通过发送认证令牌等方式进行身份验证。例如,在客户端发起 WebSocket 连接请求时,将 JWT(JSON Web Token)包含在请求头部中:

const socket = new WebSocket('wss://example.com/socket?token=' + localStorage.getItem('jwtToken'));

在服务器端,验证令牌的有效性后再建立 WebSocket 连接。

7.3 防止跨站WebSocket劫持(CSWSH)

CSWSH 是一种类似于跨站请求伪造(CSRF)的攻击方式,攻击者利用用户已登录的会话,在用户不知情的情况下发起 WebSocket 连接。为了防止这种攻击,可以在 WebSocket 握手请求中添加额外的验证信息,如 CSRF 令牌,并在服务器端进行验证。

八、性能优化

虽然 WebSocket 本身具有较高的性能,但在实际应用中,仍然可以进行一些性能优化。

8.1 减少消息发送频率

在实时数据监控等场景中,如果数据变化不频繁,不需要每次数据有微小变化就发送消息。可以通过设置一个阈值,当数据变化超过一定阈值时才发送消息,这样可以减少网络流量。

8.2 批量处理消息

在客户端接收到大量消息时,可以采用批量处理的方式,而不是逐条处理。例如,将多条消息缓存起来,每隔一段时间统一处理,这样可以减少 DOM 操作的次数,提高性能。

8.3 合理使用心跳机制

心跳机制用于保持 WebSocket 连接的活跃状态。但如果心跳频率过高,会增加网络开销。需要根据实际情况合理设置心跳频率,一般可以设置为每隔 30 秒到 1 分钟发送一次心跳消息。

通过以上对 WebSocket 在 Vue 项目中的应用案例、连接稳定性处理、安全考虑和性能优化等方面的介绍,希望能帮助开发者更好地在 Vue 项目中使用 WebSocket,实现高效、稳定且安全的实时通信功能。