WebSocket 简介
WebSocket 是基于TCP协议的,在第一次请求时是http方式并在请求头中携带有协议升级的参数,服务器接收到请求后会返回101状态码
,表示协议升级。后续请求就走ws://
方式
WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算)
首先HTTP有1.1和1.0之说,也就是所谓的keep-alive,把多个HTTP请求合并为一个,但是Websocket其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是HTTP协议上的一种补充可以通过这样一张图理解
一、WebSocket 的应用场景
社交 / 订阅
比如微信朋友圈的实时更新提醒、点赞或评论的红点通知,比如qq的特别关注人的动态提醒,比如聊天信息的实时同步,比如新闻客户端的订阅通知等等。
多玩家游戏
对于在线实时的多人游戏,互动效率是非常重要的,你可不想在扣动扳机之后,你的对手却已经在10秒钟之前移动了位置。
协同办公 / 编辑
我们生活在分散式办公的时代,时常需要在不同地点同时编辑同一份文档,比如 office文档、编程文件等。
股市基金报价
金融界瞬息万变——几乎是每毫秒都在变化。过时的信息也只能导致损失,我们人类的大脑不能持续以那样的速度处理那么多的数据,需要一些算法来帮我们处理这些事情。当你有一个显示盘来跟踪你感兴趣的公司时,你肯定想要随时知道他们的价值,而不是10秒前的数据。使用WebSocket可以流式更新这些数据变化而不需要等待。
体育实况播放
在体育播报的体验中,减低时延是最重要的一点。
音视频聊天 / 视频会议 / 在线教育
用WebSockets getUserMedia API和HTML5音视频元素明显是个不错的选择。WebRTC的出现顺理成章的成为我刚才概括的组合体,它看起来很有希望,但其缺乏目前浏览器的支持。
基于位置的应用
越来越多的开发者借用移动设备的GPS功能来实现他们基于位置的网络应用。比如共享单车、共享汽车、地图GPS服务、疫情监控目标人的实时运动轨迹、运动员的轨迹分析。借用WebSocket TCP链接可以让数据飞起来。
介绍完上面这些,我们接下来进入代码实战
二、实战demo
Maven 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>web-socket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>web-socket-demo</name>
<description>webSocketDemo</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- websocket 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<!-- 定时任务依赖-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<!-- cron表达式依赖-->
<dependency>
<groupId>xin.altitude.cms</groupId>
<artifactId>ucode-cms-quartz</artifactId>
<version>1.5.4.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
三、服务端配置
1、配置
/**
* @apiNote 创建WebSokcet配置类
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
2、服务端配置
服务终端类:用java注解来监听连接@ServerEndpoint、连接成功@OnOpen、连接关闭@OnClose、收到消息等状态@OnMessage
/**
* @date 2023/2/5
* @apiNote 创建WebSokcet工具类
* @deprecated 测试地址:ws://127.0.0.1:8080/websocket
* 测试工具:http://www.jsons.cn/websocket/
*/
@Component
@ServerEndpoint(value = "/websocket")
@Deprecated
public class WebSocketServer {
private final static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
*/
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
//加入set中
webSocketSet.add(this);
//在线数加1
addOnlineCount();
log.info("有新连接加入!当前在线人数为" + getOnlineCount());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
//从set中删除
webSocketSet.remove(this);
//在线数减1
subOnlineCount();
log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("来自客户端的消息:" + message);
//群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
/**
* 发送消息的私有方法,需要调用配置中的session才能发送
* @param message 需要发送的消息
*/
private void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 群发自定义消息
*/
@SneakyThrows
public static void sendInfo(String message){
log.info(message);
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
3、实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Item {
private String name;
private Integer num;
}
4、定时任务
/**
* @date 2023/2/5
* @apiNote 定时任务。需要的依赖:ucode-cms-quartz、quartz
*/
@Component
@Slf4j
@DisallowConcurrentExecution
@CronExp(id = 1, cron = "0/3 * * * * ?") // 每3秒向客户端发送一次数据
public class ItemJob implements Job {
private static ArrayList<Item> items = new ArrayList<Item>();
static {
items.add(new Item("Mon", 12));
items.add(new Item("Tue", 8));
items.add(new Item("Wed", 16));
items.add(new Item("Thu", 2));
}
@SneakyThrows // 隐式处理异常
@Override
public void execute(JobExecutionContext jobExecutionContext) {
log.info("定时任务被执行了。。。");
items.forEach(e -> e.setNum((int) (Math.random() * 100)));
Map<String, Integer> map = EntityUtils.toMap(items, Item::getName, Item::getNum);
String result = JSON.toJSONString(map);
WebSocketServer.sendInfo(result);
}
}
5、启动类
package com.exmaple;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @date 2023/2/5
* @apiNote 启动类
*/
@SpringBootApplication
public class WebSocketDeomApp {
public static void main(String[] args) {
SpringApplication.run(WebSocketDeomApp.class, args);
}
}
四、客户端(前端)配置
客户端采用vue
框架和element-ui
,模拟大屏实时数据展示效果。
展示的样式效果有:线形进度条、仪表盘、环形进度条。此外点击按钮还能想服务的发送当前页面展示的第一条数据,需要发送所有数据可以自行配置。
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket 客户端</title>
<script src="https://cdn.bootcdn.net/ajax/libs/sockjs-client/0.3.4/sockjs.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.6.12/vue.min.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<link href="https://unpkg.com/element-ui/lib/theme-chalk/index.css" rel="stylesheet"/>
</head>
<body>
<div id="bart" style="width: 50%;text-align:center;margin:100px auto;">
<!-- 线形进度条 -->
<el-progress :text-inside="true" :stroke-width="26" :percentage=percentage :color="colors"></el-progress>
<el-progress :percentage=percentage1 :color="colors"></el-progress>
<el-progress :percentage=percentage2 :color="colors"></el-progress>
<el-progress :percentage=percentage3 :color="colors"></el-progress>
<br><br>
<!-- 仪表盘-->
<el-progress type="dashboard" :percentage="percentage" :color="colors"></el-progress>
<el-progress type="dashboard" :percentage=percentage1 :color="colors">></el-progress>
<!-- 环形进度条-->
<el-progress type="circle" :percentage=percentage2 :color="colors"></el-progress>
<el-progress type="circle" :percentage=percentage3></el-progress>
<el-result :icon=iconFlag title="提示" subTitle="请根据提示进行操作" style="padding: 10px 0px;">
</el-result>
<br>
<!-- 向服务端发送消息-->
<el-button type="primary" @click="send()" round>发送当前值</el-button>
</div>
<script>
new Vue({
el: '#bart',
data() {
return {
iconFlag: 'success',
percentage: 30,
percentage1: 30,
percentage2: 30,
percentage3: 30,
colors: [
{color: '#f56c6c', percentage: 20},
{color: '#e6a23c', percentage: 40},
{color: '#5cb87a', percentage: 60},
{color: '#1989fa', percentage: 80},
{color: '#6f7ad3', percentage: 100}
],
ws: ""
}
},
watch: {
percentage: {
handler(newName, oldName) {
if (newName > 70) {
this.iconFlag = 'success'
} else if (newName > 40) {
this.iconFlag = 'warning'
} else {
this.iconFlag = 'error'
}
}
}
},
created() {
this.init()
},
methods: {
format(percentage) {
console.log("percentage值", percentage)
return percentage > 100 ? '满' : `${percentage}%`;
},
// 向服务端发送消息
send() {
ws.send("当前值为:" + this.percentage)
},
async init() {
that = this
// 检测浏览器是否支持ws
if ('WebSocket' in window) {
console.log('创建websocket');
ws = new WebSocket("ws://localhost:8080/websocket");
this.ws = ws
} else {
alert("该浏览器不支持websocket!");
}
ws.onopen = function (event) {
console.log('建立链接');
ws.send("Hello WebSockets!");
that.ws = ws
}
ws.onclose = function (event) {
console.log('连接关闭');
ws.close();
}
ws.onmessage = function (event) {
var res = event.data
if (res.search("Thu") == -1) {
console.log('收到消息:', res, '类型:', typeof res);
} else {
var parse = JSON.parse(res);
that.percentage = parse['Thu']
that.percentage1 = parse['Tue']
that.percentage2 = parse['Wed']
that.percentage3 = parse['Mon']
}
}
}
}
})
</script>
</body>
</html>
五、展示效果
Tips:想要获取文章中可直接运行demo的源码,只需关注本公众号发送「WebSocket源码」即可获得。
原文始发于微信公众号(随笔闲谈):SpringBoot+WebSocket实战demo,搭建实时大屏展示功能,拿来即可用
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/251110.html