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 //开启定时任务调度