Bootstrap

【开源宝藏】Jeepay VUE和React构建WebSocket通用模板

WebSocket 服务实现:Spring Boot 示例

在现代应用程序中,WebSocket 是实现双向实时通信的重要技术。本文将介绍如何使用 Spring Boot 创建一个简单的 WebSocket 服务,并提供相关的代码示例。

1. WebSocket 简介

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。与传统的 HTTP 请求-响应模式相比,WebSocket 允许服务器主动向客户端推送消息,适用于实时应用,如在线聊天、实时通知和游戏等。

2. 项目结构

在开始之前,确保你的项目中包含必要的依赖。在 pom.xml 中添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
</dependency>

3. WebSocket 配置类

为了启用 WebSocket 支持,我们需要创建一个配置类 WebSocketConfig,如下所示:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * 开启WebSocket支持
 * 
 * @author terrfly
 * @site https://www.jeequan.com
 * @date 2021/6/22 12:57
 */
@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

3.1 配置类解析

  • @Configuration: 表示该类是一个配置类,Spring 会在启动时加载它。
  • ServerEndpointExporter: 这个 Bean 会自动注册所有带有 @ServerEndpoint 注解的 WebSocket 端点。

4. WebSocket 服务类

以下是一个简单的 WebSocket 服务类 WsChannelUserIdServer 的实现。该类负责处理客户端的连接、消息接收和发送。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

@ServerEndpoint("/api/anon/ws/channelUserId/{appId}/{cid}")
@Component
public class WsChannelUserIdServer {

    private final static Logger logger = LoggerFactory.getLogger(WsChannelUserIdServer.class);

    private static int onlineClientSize = 0;
    private static Map<String, Set<WsChannelUserIdServer>> wsAppIdMap = new ConcurrentHashMap<>();

    private Session session;
    private String cid = "";
    private String appId = "";

    @OnOpen
    public void onOpen(Session session, @PathParam("appId") String appId, @PathParam("cid") String cid) {
        try {
            this.cid = cid;
            this.appId = appId;
            this.session = session;

            Set<WsChannelUserIdServer> wsServerSet = wsAppIdMap.get(appId);
            if (wsServerSet == null) {
                wsServerSet = new CopyOnWriteArraySet<>();
            }
            wsServerSet.add(this);
            wsAppIdMap.put(appId, wsServerSet);

            addOnlineCount();
            logger.info("cid[{}], appId[{}] 连接开启监听!当前在线人数为 {}", cid, appId, onlineClientSize);

        } catch (Exception e) {
            logger.error("ws监听异常 cid[{}], appId[{}]", cid, appId, e);
        }
    }

    @OnClose
    public void onClose() {
        Set<WsChannelUserIdServer> wsSet = wsAppIdMap.get(this.appId);
        wsSet.remove(this);
        if (wsSet.isEmpty()) {
            wsAppIdMap.remove(this.appId);
        }

        subOnlineCount();
        logger.info("cid[{}], appId[{}] 连接关闭!当前在线人数为 {}", cid, appId, onlineClientSize);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        logger.error("ws发生错误", error);
    }

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    public static void sendMsgByAppAndCid(String appId, String cid, String msg) {
        try {
            logger.info("推送ws消息到浏览器, appId={}, cid={}, msg={}", appId, cid, msg);

            Set<WsChannelUserIdServer> wsSet = wsAppIdMap.get(appId);
            if (wsSet == null || wsSet.isEmpty()) {
                logger.info("appId[{}] 无ws监听客户端", appId);
                return;
            }

            for (WsChannelUserIdServer item : wsSet) {
                if (!cid.equals(item.cid)) {
                    continue;
                }
                try {
                    item.sendMessage(msg);
                } catch (Exception e) {
                    logger.info("推送设备消息时异常,appId={}, cid={}", appId, item.cid, e);
                }
            }
        } catch (Exception e) {
            logger.info("推送消息时异常,appId={}", appId, e);
        }
    }

    public static synchronized int getOnlineClientSize() {
        return onlineClientSize;
    }

    public static synchronized void addOnlineCount() {
        onlineClientSize++;
    }

    public static synchronized void subOnlineCount() {
        onlineClientSize--;
    }
}

5. 代码解析

5.1 连接管理

  • @OnOpen: 当客户端连接成功时调用此方法。可以在此方法中获取客户端的 appIdcid,并将当前连接的会话存储到 wsAppIdMap 中。
  • @OnClose: 当客户端连接关闭时调用此方法,从 wsAppIdMap 中移除该连接。
  • @OnError: 处理连接错误。

5.2 消息发送

  • sendMessage(String message): 通过当前会话向客户端发送消息。
  • sendMsgByAppAndCid(String appId, String cid, String msg): 根据 appIdcid 向特定客户端推送消息。

5.3 在线人数管理

使用 onlineClientSize 变量记录当前在线的客户端数量,并提供相应的增减方法。

好的!下面我将为你提供一个简单的前端实现示例,使用 Vue.jsReact 来连接我们之前创建的 WebSocket 服务。这样,你可以看到如何在前端与后端进行实时通信。

6. Vue.js 前端实现

6.1 安装 Vue.js

如果你还没有创建 Vue 项目,可以使用 Vue CLI 创建一个新的项目:

npm install -g @vue/cli
vue create websocket-demo
cd websocket-demo

6.2 创建 WebSocket 组件

src/components 目录下创建一个名为 WebSocketComponent.vue 的文件,并添加以下代码:

<template>
  <div>
    <h1>WebSocket Demo</h1>
    <input v-model="message" placeholder="Type a message" />
    <button @click="sendMessage">Send</button>
    <div>
      <h2>Messages:</h2>
      <ul>
        <li v-for="(msg, index) in messages" :key="index">{{ msg }}</li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      socket: null,
      message: '',
      messages: [],
      appId: 'yourAppId', // 替换为你的 appId
      cid: 'yourClientId'  // 替换为你的客户端自定义ID
    };
  },
  created() {
    this.connect();
  },
  methods: {
    connect() {
      this.socket = new WebSocket(`ws://localhost:8080/api/anon/ws/channelUserId/${this.appId}/${this.cid}`);

      this.socket.onopen = () => {
        console.log('WebSocket connection established.');
      };

      this.socket.onmessage = (event) => {
        const data = JSON.parse(event.data);
        this.messages.push(data.message);
      };

      this.socket.onclose = () => {
        console.log('WebSocket connection closed.');
      };

      this.socket.onerror = (error) => {
        console.error('WebSocket error:', error);
      };
    },
    sendMessage() {
      if (this.socket && this.socket.readyState === WebSocket.OPEN) {
        this.socket.send(JSON.stringify({ message: this.message }));
        this.message = ''; // 清空输入框
      } else {
        console.error('WebSocket is not open.');
      }
    }
  }
};
</script>

<style scoped>
/* 添加样式 */
</style>

6.3 使用组件

src/App.vue 中使用这个组件:

<template>
  <div id="app">
    <WebSocketComponent />
  </div>
</template>

<script>
import WebSocketComponent from './components/WebSocketComponent.vue';

export default {
  components: {
    WebSocketComponent
  }
};
</script>

<style>
/* 添加样式 */
</style>

6.4 运行 Vue 应用

在项目根目录下运行以下命令启动 Vue 应用:

npm run serve

7. React 前端实现

7.1 安装 React

如果你还没有创建 React 项目,可以使用 Create React App 创建一个新的项目:

npx create-react-app websocket-demo
cd websocket-demo

7.2 创建 WebSocket 组件

src 目录下创建一个名为 WebSocketComponent.js 的文件,并添加以下代码:

import React, { useEffect, useState } from 'react';

const WebSocketComponent = () => {
  const [socket, setSocket] = useState(null);
  const [message, setMessage] = useState('');
  const [messages, setMessages] = useState([]);
  const appId = 'yourAppId'; // 替换为你的 appId
  const cid = 'yourClientId'; // 替换为你的客户端自定义ID

  useEffect(() => {
    const ws = new WebSocket(`ws://localhost:8080/api/anon/ws/channelUserId/${appId}/${cid}`);
    setSocket(ws);

    ws.onopen = () => {
      console.log('WebSocket connection established.');
    };

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      setMessages((prevMessages) => [...prevMessages, data.message]);
    };

    ws.onclose = () => {
      console.log('WebSocket connection closed.');
    };

    ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };

    return () => {
      ws.close();
    };
  }, [appId, cid]);

  const sendMessage = () => {
    if (socket && socket.readyState === WebSocket.OPEN) {
      socket.send(JSON.stringify({ message }));
      setMessage(''); // 清空输入框
    } else {
      console.error('WebSocket is not open.');
    }
  };

  return (
    <div>
      <h1>WebSocket Demo</h1>
      <input
        value={message}
        onChange={(e) => setMessage(e.target.value)}
        placeholder="Type a message"
      />
      <button onClick={sendMessage}>Send</button>
      <div>
        <h2>Messages:</h2>
        <ul>
          {messages.map((msg, index) => (
            <li key={index}>{msg}</li>
          ))}
        </ul>
      </div>
    </div>
  );
};

export default WebSocketComponent;

7.3 使用组件

src/App.js 中使用这个组件:

import React from 'react';
import WebSocketComponent from './WebSocketComponent';

function App() {
  return (
    <div className="App">
      <WebSocketComponent />
    </div>
  );
}

export default App;

7.4 运行 React 应用

在项目根目录下运行以下命令启动 React 应用:

npm start

8. 总结

通过以上步骤,我们实现了一个简单的 WebSocket 前端示例,分别使用了 Vue.js 和 React。用户可以通过输入框发送消息,接收来自 WebSocket 服务器的消息。

8.1 注意事项

  • 确保 WebSocket 服务器正在运行,并且前端应用能够访问到它。
  • 替换 yourAppIdyourClientId 为实际的应用 ID 和客户端 ID。

希望这能帮助你更好地理解如何在前端实现 WebSocket 通信!如有任何问题,请随时询问。

;