当前位置: 首页 > article >正文

对WebSocket做一点简单的理解

1.概念

WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输。

HTTP协议和WebSocket协议对比:

  • HTTP是短连接

  • WebSocket是长连接

  • HTTP通信是单向的,基于请求响应模式

  • WebSocket支持双向通信

  • HTTP和WebSocket底层都是TCP连接

  • WebSocket缺点:

    服务器长期维护长连接需要一定的成本 各个浏览器支持程度不一 WebSocket 是长连接,受网络限制比较大,需要处理好重连

    结论:WebSocket并不能完全取代HTTP,它只适合在特定的场景下使用

    WebSocket的使用场景:视频弹幕,网页聊天,股票基金报价实时更新, 体育实况更新

2.示例

2.1 基础配置

导入Maven坐标

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
/**
 * WebSocket配置类,用于注册WebSocket的Bean
 * @Author GuihaoLv
 */
@Configuration
public class WebSocketConfig {
/**
 * 创建并返回一个 ServerEndpointExporter 实例。
 *
 * ServerEndpointExporter 是 Spring 提供的一个工具类,用于自动注册使用了 @ServerEndpoint 注解的 WebSocket 端点。
 * 这样可以避免手动注册每个 WebSocket 端点。
 *
 * @return ServerEndpointExporter 实例
 */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}
WebSocket服务器端组件:
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
* WebSocket服务器端组件
* @Author GuihaoLv
*/
@Component
//标记这个类是一个WebSocket端点,可以接收来自客户端的WebSocket连接请求。/ws/{sid}表示WebSocket的URL路径模式,其中{sid}是路径参数,代表会话ID(Session ID)。
@ServerEndpoint("/ws/{userId}")
public class WebSocketServer {
    //这里声明了一个静态的Map,用来存储每个连接的Session对象,键是sid,值是对应的Session。通过这种方式,服务器可以追踪每一个连接的客户端。
    private static Map<String, Session> sessionMap = new ConcurrentHashMap();
    //一个线程安全的集合,用来存储所有活动的WebSocketServer实例。
    public static CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>();

    //@OnOpen:当一个新的WebSocket连接成功建立时,此方法会被调用。
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        System.out.println("客户端:" + userId + "建立连接");
        webSockets.add(this);
        sessionMap.put(userId, session);//把新的Session对象存入sessionMap中。
    }


    //每当从客户端接收到消息时,该方法就会触发。这里只是简单地打印出收到的消息。
    @OnMessage
   public void onMessage(String message, @PathParam("userId") String userId) {
       System.out.println("收到来自客户端:" + userId + "的信息:" + message);
    }
 



    //当一个WebSocket连接关闭时,此方法会被调用。它会从sessionMap中移除相应的Session对象,并输出一条日志信息。
    @OnClose
    public void onClose(@PathParam("userId") String userId) {
        System.out.println("连接断开:" + userId);
        webSockets.remove(this);
        sessionMap.remove(userId);
    }

    //遍历所有现存的Session对象,并尝试向每个客户端发送文本消息。如果发送过程中遇到任何异常,就捕获异常并打印堆栈跟踪信息。
    public void sendToAllClient(String message) {
        Collection<Session> sessions = sessionMap.values();
        for (Session session : sessions) {
            try {
                session.getBasicRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
     /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.err.println("WebSocket 发生错误: " + error.getMessage());
        error.printStackTrace();
    }
}

前端工具类:

const url = "ws://127.0.0.1:8080/ws/{userId}"; // 注意:这里需要替换 {userId} 为实际的用户ID
//定义了 WebSocket 工具类的结构和方法签名。
interface Socket {
  websocket: WebSocket | null;  // WebSocket 连接实例
  init: (userId: string) => void; // 需要传入 userId 初始化连接
  send: (data: object) => void;   // 发送数据的方法
  onMessage: (callback: (msg) => void) => void; // 消息监听回调
  onClose: (callback: () => void) => void; // 关闭回调
  onError: (callback: (error: Event) => void) => void; // 错误回调
  onMessageCallback: ((msg) => void) | null; // 存储消息回调
  onCloseCallback: (() => void) | null; // 存储关闭回调
  onErrorCallback: ((error: Event) => void) | null; // 存储错误回调
}

const socket: Socket = {
  websocket: null,
  //用来存储事件回调函数。
  onMessageCallback: null,
  onCloseCallback: null,
  onErrorCallback: null,

  init: (userId: string) => {
    const fullUrl = url.replace("{userId}", userId); // 动态替换 userId
    if (socket.websocket) return; // 避免重复连接

    socket.websocket = new WebSocket(fullUrl);

    /**
     * 当收到消息时调用该回调函数。
     */
    socket.websocket.onopen = () => {
      console.log("WebSocket 连接成功");
    };

    /**
     * 当 WebSocket 连接关闭时调用该回调函数。
     * @param e
     */
    socket.websocket.onclose = (e) => {
      console.log("WebSocket 连接关闭", e);
      socket.websocket = null; // 连接关闭后重置 WebSocket
      if (socket.onCloseCallback) {
        socket.onCloseCallback();
      }
    };

     /**
     * 当 WebSocket 发生错误时调用该回调函数。
     * @param e
     */
    socket.websocket.onerror = (e) => {
      console.error("WebSocket 错误", e);
      if (socket.onErrorCallback) {
        socket.onErrorCallback(e);
      }
    };

    //收到 WebSocket 消息 时执行:
    // event.data 是接收到的字符串,先 JSON.parse() 解析成对象。
    // 调用 onMessageCallback 回调,如果外部监听了消息。
    socket.websocket.onmessage = (event) => {
      try {
        const message = JSON.parse(event.data);
        console.log("📩 收到 WebSocket 消息:", message);
        if (socket.onMessageCallback) {
          socket.onMessageCallback(message);
        }
      } catch (error) {
        console.error("解析消息失败:", error);
      }
    };
  },

  //消息发送 (send 方法)
  send: (data: object) => {
    if (socket.websocket && socket.websocket.readyState === WebSocket.OPEN) {
      socket.websocket.send(JSON.stringify(data));
    } else {
      console.log("WebSocket 未连接,尝试重连");
      setTimeout(() => socket.send(data), 1000); // 尝试重新发送消息
    }
  },

  //监听消息,onMessageCallback 存储消息回调函数,收到消息时触发。
  onMessage: (callback: (msg) => void) => {
    socket.onMessageCallback = callback;
  },

  //监听关闭,onCloseCallback 存储关闭回调函数,连接关闭时触发。
  onClose: (callback: () => void) => {
    socket.onCloseCallback = callback;
  },

  //监听错误,onErrorCallback 存储错误回调函数,连接错误时触发。
  onError: (callback: (error: Event) => void) => {
    socket.onErrorCallback = callback;
  },
};

export default socket;

2.2 客户端与服务端交互示例

服务端接收消息的方法:

@OnMessage
public void onMessage(String message, @PathParam("userId") String userId) {
    System.out.println("收到来自客户端:" + userId + "的信息:" + message);
}

客户端测试页面:

<script setup lang="ts">
import socket from '@/utils/webSocket0.ts';
import {ref} from "vue"; // 引入 WebSocket 工具类

const userId = ref('');

function connect() {
  if (!userId.value) {
    alert('Please enter a User ID');
    return;
  }

  //初始化 WebSocket 连接,传入用户ID作为参数。
  socket.init(userId.value);

  socket.onMessage((message) => {
    console.log('收到消息:', message);
    appendMessage(`收到消息: ${JSON.stringify(message)}`);
  });

  socket.onClose(() => {
    console.log('WebSocket 连接已关闭');
    appendMessage('WebSocket 连接已关闭');
  });

  socket.onError((error) => {
    console.error('WebSocket 错误:', error);
    appendMessage('WebSocket 错误');
  });
}

//造一条类型为 'chat' 的消息,内容为 'Hello, Server!'。
//使用 socket.send(message); 发送消息到服务器。
//通过 appendMessage 函数在页面上显示已发送的消息内容。
function sendMessage() {
  const message = { type: 'chat', content: 'Hello, Server!' };
  socket.send(message);
  appendMessage(`发送消息: ${JSON.stringify(message)}`);
}

//检查 socket.websocket 是否存在(即 WebSocket 连接是否已经建立)。
//如果存在,则调用 close() 方法关闭连接。
function disconnect() {
  if (socket.websocket) {
    socket.websocket.close();
  }
}

//获取页面上 ID 为 messages 的 div 元素。
//创建一个新的 div 元素,设置其文本内容为传入的消息,并将其追加到 messagesDiv 中
function appendMessage(message: string) {
  const messagesDiv = document.getElementById('messages');
  if (messagesDiv) {
    const messageElement = document.createElement('div');
    messageElement.textContent = message;
    messagesDiv.appendChild(messageElement);
  }
}
</script>

<template>
  <div>
    <h1>WebSocket Client Simulation</h1>
    <input v-model="userId" type="text" placeholder="Enter User ID"/>
    <button @click="connect">Connect</button>
    <button @click="sendMessage">Send Message</button>
    <button @click="disconnect">Disconnect</button>
    <div id="messages"></div>
  </div>
</template>

<style scoped>
/* 样式可以根据需要进行调整 */
input {
  margin-right: 10px;
}

button {
  margin-right: 10px;
}

#messages {
  margin-top: 20px;
  border: 1px solid #ccc;
  padding: 10px;
  width: 300px;
  height: 200px;
  overflow-y: scroll;
}
</style>

页面原型:

连接客户端:

客户端向服务端发送消息:

断开连接:

 2.3 客户端与客户端的交互监听

服务端接收消息处理:

@OnMessage
public void onMessage(String message, @PathParam("userId") String userId) {
    System.out.println("收到来自客户端:" + userId + "的信息:" + message);
    try {
        // 使用 FastJSON 解析 JSON 字符串
        JSONObject jsonMessage = JSON.parseObject(message);
        String toUserId = jsonMessage.getString("toUserId");

        // 获取目标用户的会话
        Session targetSession = sessionMap.get(toUserId);

        if (targetSession != null && targetSession.isOpen()) {
            System.out.println("正在向用户:" + toUserId + "发送消息");
            // 将消息转发给目标用户
            targetSession.getBasicRemote().sendText(jsonMessage.toJSONString());
        } else {
            System.out.println("无法找到目标用户或连接已关闭:" + toUserId);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

用户A:

<template>
  <div>
    <h1>用户A</h1>
    <input v-model="message" placeholder="输入消息" />
    <button @click="sendMessage">发送消息</button>
    <div id="messages">
      <div v-for="(msg, index) in messages" :key="index">{{ msg }}</div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import socket from '@/utils/webSocket0.ts'; // 引入 WebSocket 工具类

const userId = 'userA'; // 用户A的ID
const message = ref('');
const messages = ref<string[]>([]);

socket.init(userId);

socket.onMessage((msg) => {
  console.log('收到消息:', msg);
  appendMessage(`收到消息: ${JSON.stringify(msg)}`);
});

socket.onError((error) => {
  console.error('WebSocket 错误:', error);
  appendMessage('WebSocket 错误');
});

function sendMessage() {
  const msgContent = { type: 'chat', content: message.value, toUserId: 'userB' };
  socket.send(msgContent);
  appendMessage(`发送消息: ${JSON.stringify(msgContent)}`);
  message.value = ''; // 清空输入框
}

function appendMessage(messageText: string) {
  messages.value.push(messageText);
}
</script>

<style scoped>
/* 样式可以根据需要进行调整 */
input {
  margin-right: 10px;
}

button {
  margin-right: 10px;
}

#messages {
  margin-top: 20px;
  border: 1px solid #ccc;
  padding: 10px;
  width: 300px;
  height: 200px;
  overflow-y: scroll;
}
</style>

用户B:

<template>
  <div>
    <h1>用户B</h1>
    <div id="messages">
      <div v-for="(msg, index) in messages" :key="index">{{ msg }}</div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import socket from '@/utils/webSocket0.ts'; // 引入 WebSocket 工具类

const userId = 'userB'; // 用户B的ID
const messages = ref<string[]>([]);

// 初始化 WebSocket 连接
function initSocket() {
  socket.init(userId);

  socket.onMessage((msg) => {
    if (msg.toUserId === userId) {
      console.log('收到消息:', msg);
      appendMessage(`收到消息: ${JSON.stringify(msg)}`);
    }
  });

  socket.onError((error) => {
    console.error('WebSocket 错误:', error);
    appendMessage('WebSocket 错误');
  });
}

// 将新消息添加到消息列表中
function appendMessage(messageText: string) {
  messages.value.push(messageText);
}

// 在组件挂载时初始化 WebSocket
onMounted(() => {
  initSocket();
});
</script>

<style scoped>
/* 样式可以根据需要进行调整 */
#messages {
  margin-top: 20px;
  border: 1px solid #ccc;
  padding: 10px;
  width: 300px;
  height: 200px;
  overflow-y: scroll;
}
</style>

用户A发消息给用户B:

2.4: 一个实际的消息推送场景:

服务端消息推送处理:

//如果是回复评论
blogCommentsSaveDto.setAnswerUserId(userMapper.getUserByUsername(blogCommentsSaveDto.getAnswerUserName()));
//将回复推送给接收者
webSocketServer.sendToClient(blogCommentsSaveDto.getAnswerUserId().toString(),getUser().getId().toString(),blogCommentsSaveDto.getContent());

/**
 * 向指定客户端发送文本消息
 * @param userId 客户端的会话ID
 * @param message 要发送的消息
 */
public void sendToClient(String userId, String fromId,String message) {
    try {
        // 创建标准消息结构
        JSONObject messageJson = new JSONObject();
        messageJson.put("content", message); // 原始内容放在content字段
        messageJson.put("timestamp", System.currentTimeMillis());
        messageJson.put("status", "success");
        messageJson.put("from",fromId);
        Session session = sessionMap.get(userId);
        if (session != null && session.isOpen()) {
            // 发送序列化的JSON字符串
            session.getBasicRemote().sendText(messageJson.toJSONString());
        }
    } catch (Exception e) {
       e.printStackTrace();
    }
}

客户端与服务端建立连接并且实时监听消息:

// 新增导入
import { onUnmounted } from 'vue'
import socket from '@/utils/webSocket0.ts' // 假设socket工具类路径
import { useUserStore } from '@/stores/userStore.ts'

// 新增消息通知状态
const notifications = ref<string[]>([])
const userStore = useUserStore()

// 初始化WebSocket
const initWebSocket = () => {
  if (!userStore.userInfo?.userId) {
    console.error('用户未登录,无法建立WebSocket连接')
    return
  }

  // 初始化连接
  socket.init(userStore.userInfo.userId.toString())

  // 消息监听
  socket.onMessage((msg) => {
    console.log('收到新回复通知:', msg)
    notifications.value.push(msg.content)

    // 自动刷新评论(带1秒延迟避免请求冲突)
    setTimeout(loadComments, 1000)
  })

  // 错误处理
  socket.onError((err) => {
    console.error('WebSocket错误:', err)
  })
}

// 组件挂载时
onMounted(() => {
  loadComments()
  initWebSocket()
})

<h1>评论区</h1>
<!-- 在顶部添加通知栏 -->
<div v-if="notifications.length" class="notifications">
  <div
    v-for="(msg, index) in notifications"
    :key="index"
    class="notification-item"
  >
    🆕 您收到新回复:{{ msg }}
  </div>
</div>


http://www.kler.cn/a/579488.html

相关文章:

  • 小程序 wxml 语法 —— 39 简单双向数据绑定
  • navicat导出postgresql的数据库结构、字段名、备注等等
  • SpringBoot项目的五种搭建方式
  • Docker 运行 GPUStack 的详细教程
  • 微软程序的打包格式MSIX
  • 人类的学习既有强化学习也有弱化学习
  • Java后端高频面经——Spring、SpringBoot、MyBatis
  • tcc编译器教程2 编译lua解释器
  • DeepSeek教我写词典爬虫获取单词的音标和拼写
  • 非常重要的动态内存错误和柔性数组1
  • Vue 的 render 函数如何与 JSX 结合使用
  • P9421 [蓝桥杯 2023 国 B] 班级活动--数学题(配对问题)
  • 基于遗传算法的IEEE33节点配电网重构程序
  • leetcode77.组合
  • 基于STC89C52的8x8点阵贪吃蛇游戏
  • Vue 3 实现富文本内容导出 Word 文档:前端直出方案与优化实践
  • 【SpringBoot】深入解析 Maven 的操作与配置
  • 计算机网络:电路交换,报文交换,分组交换
  • golang学习笔记——go语言安装及系统环境变量设置
  • 2025.3.9机器学习笔记:文献阅读