Websocket

发布于:2025-06-27 ⋅ 阅读:(13) ⋅ 点赞:(0)

1. Websocket介绍

1. 概述

Websocket 是基于TCP的一种新的网络协议。允许在单个 TCP 连接上进行全双工通信。与传统的 HTTP 请求-响应模式不同,WebSocket 提供了持久的连接,服务器和客户端可以随时主动推送数据,无需反复建立连接,可以进行双向数据传输。客户端可以向服务器发送数据,服务器也可以向客户端发送消息。

HTTP协议与WebSocket协议对比

不同点

HTTP是短连接,WebStocket是长连接。

HTTP通信是单向的,WebSocket通信是持久双向的

相同点

二者的底层都是TCP连接

HTTP 连接响应 

客户端请求一次,服务端响应一次。

 

WebStocket 连接响应

客户端与服务端建立连接后,服务端可随时向客户端发送消息

 

应用场景

建立一次连接后,客户端与服务器持久连接,客户端可以向服务器发送数据,服务器也可以向客户端发送消息。

  • 视频弹窗,如视频弹幕
  • 网页聊天,如百度上的一对一客服
  • 股票基金报价实时更新

2. WebSocket 参数

WebSocket 事件

事件 事件处理程序 描述
open Socket.onopen 连接建立时触发
message Socket.onmessage 客户端接收服务端数据时触发
error Socket.onerror 通信发生错误时触发
close Socket.onclose 连接关闭时触发

 WebSocket 方法

方法 描述
Socket.send() 连接成功后,关闭前,发送消息
Socket.close() 关闭连接

3. WebSocket 心跳机制

为什么要心跳机制

在WebSocket使用心跳机制是为了保持 WebSocket 连接的活跃状态而设计的一种机制。在长时间没有数据传输的情况下,WebSocket 连接可能会被中间的网络设备(如路由器或防火墙)认为已经断开,从而导致连接失效。但是服务端和客户端并不知道连接失效了,还在发送数据,但这些数据都接收不到。心跳机制通过定期发送小的数据包来保持连接的活动状态,从而避免这种情况的发生。

心跳机制原理 

 心跳机制原理定期发送小的数据包,保证WebSocket 不被断开。

心跳机制的工作流程

初始化WebSocket 时开启心跳检测,就是定义一个定时器,定时向后端(服务端)发送一次信息,等待后端(服务端)给前端(客户端)一个响应消息,假设数据发到服务端没有响应消息,被视为断开连接,需要重新初始化websocket方法连接;假设数据发到服务端有响应信息,此时认为连接正常,重置定时器,重新开始计时。

<template>
  <!-- websocketceshi -->
  <div class="layout">
    <div class="msgBody">{{ msg }}</div>
    <input
      v-model="sendMsg"
      style="width: 200px; height: 30px; margin-top: 20px"
    />
    <button @click="websocketsend(sendMsg)" style="width: 100px; height: 30px">
      发送
    </button>
    <button @click="websocketclose" style="width: 100px; height: 30px">
      断开链接
    </button>
    <button @click="initWebSocket" style="width: 100px; height: 30px">
      建立链接
    </button>
  </div>
</template>

<script>
export default {
  name: "LayOut",
  data() {
    return {
      websock: null, //建立的连接
      lockReconnect: false, //是否真正建立连接
      timeout: 20 * 1000, //20秒一次心跳
      timeoutObj: null, //心跳心跳倒计时
      serverTimeoutObj: null, //心跳倒计时
      timeoutnum: null, //断开 重连倒计时
      msg: "", //显示的值
      sendMsg: "", //输入框的值
    };
  },
  created() {
    // //页面刚进入时开启长连接
    this.initWebSocket();
  },
  destroyed() {
    //页面销毁时关闭长连接
    this.websocketclose();
  },
  methods: {
          //建立连接,初始化weosocket
    initWebSocket() {
      //后台地址,前面的ws不动,后面是后台地址,我是本地运行的所以填的本地,自行更改。再后面ws是后端的接口地址,admin是参数
      const wsuri = "ws://localhost:8001/ws/admin";
      //建立连接
      this.websock = new WebSocket(wsuri);
      //连接成功
      this.websock.onopen = this.websocketonopen;
      //连接错误
      this.websock.onerror = this.websocketonerror;
      //接收信息
      this.websock.onmessage = this.websocketonmessage;
      //连接关闭
      this.websock.onclose = this.websocketclose;
    },
    reconnect() {
      //重新连接
      var that = this;
      //判断链接状态,true就是链接,false是断开,这里如果是链接状态就不继续执行了,跳出来。
      if (that.lockReconnect) {
        return;
      }
      //把链接状态改为true
      that.lockReconnect = true;
      //没连接上会一直重连,设置延迟避免请求过多
      that.timeoutnum && clearTimeout(that.timeoutnum);
      that.timeoutnum = setTimeout(function () {
        //初始化新连接
        that.initWebSocket();
        //把链接状态改为false
        that.lockReconnect = false;
      }, 5000);
    },
    reset() {
      //重置心跳
      var that = this;
      //清除时间
      clearTimeout(that.timeoutObj);
      clearTimeout(that.serverTimeoutObj);
      //重启心跳
      that.start();
    },
    start() {
      //开启心跳
      var self = this;
      //有延迟时间的就清除掉
      self.timeoutObj && clearTimeout(self.timeoutObj);
      self.serverTimeoutObj && clearTimeout(self.serverTimeoutObj);
      //从新创建计时器
      self.timeoutObj = setTimeout(function () {
        //这里发送一个心跳,后端收到后,返回一个心跳消息
        if (self.websock.readyState == 1) {
          //如果连接正常发送信息到后台
          self.websock.send("ping");
        } else {
          //否则重连
          self.reconnect();
        }
        self.serverTimeoutObj = setTimeout(function () {
          //超时关闭
          self.websock.close();
        }, self.timeout);
      }, self.timeout);
    },
    //链接成功时执行的方法
    websocketonopen() {
      //连接成功事件
      this.websocketsend("发送数据");
      //提示成功
      console.log("连接成功", 3);
      //开启心跳
      this.start();
    },
    //连接失败事件
    websocketonerror(e) {
      //错误
      console.log("WebSocket连接发生错误");
      //重连
      this.reconnect();
    },
    //连接关闭事件
    websocketclose(e) {
      this.websock.close();
      //提示关闭
      console.log("连接已关闭");
      //重连
      this.reconnect();
    },
    //接收服务器推送的信息
    websocketonmessage(event) {
      //打印收到服务器的内容
      console.log("收到服务器信息", event.data);
      this.msg = event.data;
      //收到服务器信息,心跳重置
      this.reset();
    },
    websocketsend(msg) {
      //向服务器发送信息
      this.websock.send(msg);
    },
  },
};
</script>
<style scoped>
.layout {
  position: relative;
  width: 100%;
  height: 100%;
}
.msgBody {
  width: 500px;
  height: 300px;
  border: 1px solid rgb(95, 79, 79);
}
</style>

 

 2. SpringBoot整合WebStocket

1). 使用页面作为WebSocket客户端

2). 导入WebSocket的maven坐标

3). 新建WebSocket服务端组件WebSocketServer,用于和客户端通信

4). 新建配置类WebSocketConfiguration,注册WebSocket的服务端组件

5). 新建定时任务类WebSocketTask,定时向客户端推送数据

6). SpringBoot启动类开启定时任务注解

1. 前端页面

<template>
<!-- websocketceshi -->
  <div class="layout">
    <div class="msgBody">{{ msg }}</div>
    <input v-model="sendMsg" style="width:200px;height:30px;margin-top:20px"/>
    <button @click="sendMessage" style="width:100px;height:30px;">发送</button>
    <button @click="close" style="width:100px;height:30px;">断开链接</button>
    <button @click="init" style="width:100px;height:30px;">建立链接</button>
  </div>
</template>

<script>
export default {
  name: "LayOut",
  data() {
    return {
      msg: "",
      sendMsg: "",
      //后台的地址,只需要动localhost:8089部分,改成你后端的地址。
      //后面webSocket是后台设定的接口地址,uuid_test001是你前台的客户端唯一id(可以使用uuid生成)。
      //用于区分不同的客户端,比如你多个客户端连接后台,后台推送数据的时候需要根据这个id不同,给对应的人推送,不然就推送到所有建立链接的网页上了
      path: "ws://localhost:8089/qf/webSocket/uuid_test001",
      //存websocket实例化的
      socket: "",
    };
  },
  methods: {
    //用于前台发送数据到后台,调用websocket中的send方法把数据发过去。
    sendMessage() {
      this.socket.send(this.sendMsg);
    },
    //初始化建立前后台链接
    init() {
      if (typeof WebSocket === "undefined") {
        alert("您的浏览器不支持socket");
      } else {
        // 实例化socket
        this.socket = new WebSocket(this.path);
        // 监听socket连接
        this.socket.onopen = this.open;
        // 监听socket错误信息
        this.socket.onerror = this.error;
        // 监听socket消息
        this.socket.onmessage = this.getMessage;
        this.socket.onclose = this.close;
      }
    },
    //链接成功时的回调函数
    open() {
      console.log("socket连接成功");
    },
    //链接错误时的回调
    error(err) {
      console.log("连接错误" + err);
    },
    //后台消息推送过来,接收的函数,参数为后台推过来的数据。
    getMessage(msg) {
      this.msg = msg.data;
    },
    //链接关闭的回调
    close(event) {
      //socket是链接的实例,close就是关闭链接
      this.socket.close()
      console.log("断开链接成功");
    },
  },
  created() {
    //开局初始化建立链接
    this.init();
  },
};
</script>
<style scoped>
.layout {
  position: relative;
  width: 100%;
  height: 100%;
}
.msgBody {
  width: 500px;
  height: 300px;
  border: 1px solid rgb(95, 79, 79);
}
</style>

2. 在pom.xml文件中导入webStocket依赖

<!--  WebSocket -->
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

3.  新建一个类名为WebSocketConfig 的配置类,用于注册WebSocket的服务端组件。

package com.example.config;

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

/**
 * WebSocket配置类,用于注册WebSocket的Bean,
 * 确保它们在应用程序启动时被正确初始化和注册,以便能够处理WebSocket连接和通信。
 */

@Configuration
public class WebSocketConfig {

    /**
     * 该方法用来创建并返回一个ServerEndpointExporter实例。
     * 这个实例的作用是扫描并自动配置所有使用@ServerEndpoint注解标记的WebSocket端点
     *
     * @return ServerEndpointExporter:这是一个用于自动检测和管理WebSocket端点的类。
     *          通过将其实例化并配置为Spring管理的Bean,可以确保所有WebSocket端点在应用程序启动时被自动初始化和注册。
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

4. WebSocket 服务类如下,与controller同级目录新建一个包,在该包下新建一个WebSocketServer 服务类。用于和客户端通信。

package com.example.websocket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;


/**
 * 该类负责监听客户端的连接、断开连接、接收消息、发送消息等操作。
 * */
@Slf4j
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {

    //存放会话对象
    private static Map<String, Session> sessionMap = new HashMap();

    /**
     * 连接建立成功调用的方法
     * @param session 第一个参数必须是session
     * @param sid   代表客户端的唯一标识
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        log.info("客户端:" + sid + "建立连接");
        sessionMap.put(sid, session);
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, @PathParam("sid") String sid) {
        log.info("收到来自客户端:" + sid + "的信息:" + message);
    }

    /**
     * 连接关闭调用的方法
     *
     * @param sid
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid) {
        log.info("连接断开:" + sid);
        sessionMap.remove(sid);
    }

    /**
     * 群发
     *
     * @param message
     */
    public void sendToAllClient(String message) {
        Collection<Session> sessions = sessionMap.values();
        for (Session session : sessions) {
            try {
                //服务器向客户端发送消息 , 发送文本信息有两种方式,一种是getBasicRemote,一种是getAsyncRemote
                //区别:getAsyncRemote是异步的,发送消息后立即返回,不阻塞当前线程.
                // 而getBasicRemote是同步的,发送消息时会阻塞当前线程,直到消息发送完成
                session.getBasicRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

5. 新建一个定时任务类WebSocketTask,将 WebSocketServer 服务类注入进来。

package com.example.task;

import com.example.websocket.WebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Component
public class WebSocketTask {
    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * 通过WebSocket每隔5秒向客户端发送消息
     */
    //@Scheduled(cron = "0/5 * * * * ?")
    public void sendMessageToClient() {
        webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
    }
}

6). 在SpringBoot启动类开启定时任务调度。

@EnableScheduling //开启定时任务调度

 


网站公告

今日签到

点亮在社区的每一天
去签到