文章目录
- *Springboot整合WebSocket,实现服务端主动向客户端推送数据*
- 1.对于WebSocket、Socket、Http三者的理解
- 2.开始整合
- 总结
Springboot整合WebSocket,实现服务端主动向客户端推送数据
1.对于WebSocket、Socket、Http三者的理解
Socket(长连接,一直连接,资源耗费大):
所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象
一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制
从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口
Http:
超文本传输协议(Hypertext Transfer Protocol,HTTP)是一个简单的请求-响应协议,它通常运行在TCP之上
它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应
一次请求,一次响应(轮询操作)
WebSocket:
WebSocket是一种在单个TCP连接上进行全双工通信的协议
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据
WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
工作原理:
(1)先握手(Http请求)
(2)进行长连接(Socket连接)
(3)最后分手(Http请求,服务端断开)
(4)长连接断开
2.开始整合
引入Websocket、StringUtils、fastjson相关依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.41</version>
</dependency>
yml相关配置:
# 日志级别设置
logging:
level:
root: info # 最基础的日志输出级别
com.kd.opt: debug # 指定包下的日志输出级别
org.springframework.web: debug # 指定类下的日志输出级别
file:
name: D:\\Java\\idea workspace\\websocket_demo1\\log.txt # 服务器中,日志打印位置
后端相关代码(WebSocketConfig.java):
package com.kd.opt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
后端相关代码(CorsConfig.java):
package com.kd.opt.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class CorsConfig extends WebMvcConfigurationSupport {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowCredentials(true)
.allowedMethods("*")
.maxAge(3600);
}
}
后端相关代码(WebSocketServer.java):
package com.kd.opt.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint("/websocket/{userId}")
@Component
public class WebSocketServer {
private static int onlineCount = 0;
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
private Session session;
private String userId = "";
private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
webSocketMap.put(userId, this);
} else {
webSocketMap.put(userId, this);
addOnlineCount();
}
LOGGER.debug("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount());
try {
sendMessage("服务端主动向您推送消息$$$连接成功");
} catch (IOException e) {
LOGGER.error("用户连接:" + userId + ",网络异常");
}
}
@OnClose
public void onClose() {
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
subOnlineCount();
}
LOGGER.debug("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
}
@OnMessage
public void onMessage(String message, Session session) {
LOGGER.debug("当前用户:" + userId + ",报文:" + message);
if (StringUtils.isNotBlank(message)) {
try {
JSONObject jsonObject = JSON.parseObject(message);
jsonObject.put("fromUserId", this.userId);
String toUserId = jsonObject.getString("toUserId");
String info = jsonObject.getString("info");
if (StringUtils.isNotBlank(toUserId) && webSocketMap.containsKey(toUserId)) {
webSocketMap.get(toUserId).sendMessage("发送消息$$$" + jsonObject.getString("contentText"));
} else {
if ("心跳包".equals(info)) {
LOGGER.debug("心跳包检测中,当前在线人数为:" + getOnlineCount());
} else {
LOGGER.error("请求的userId:" + toUserId + "不在该服务器上");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
@OnError
public void onError(Session session, Throwable error) {
LOGGER.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
error.printStackTrace();
}
public static void sendInfo(String message, @PathParam("userId") String userId) throws IOException {
LOGGER.info("发送消息到:" + userId + ",报文:" + message);
if (StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).sendMessage(message);
} else {
LOGGER.error("用户" + userId + ",不在线!");
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
后端相关代码(WebsocketController.java):
package com.kd.opt.controller;
import com.kd.opt.config.WebSocketServer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
public class WebsocketController {
@RequestMapping(value = "/serverSendMessage",method = RequestMethod.GET)
public String serverSendMessage() throws IOException {
WebSocketServer.sendInfo("我喜欢你$$$小辰哥哥","521");
return "success";
}
}
Vue相关代码(WebSocketUtil.js):
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
websock: null,
eventlist: [],
lockReconnect: false
},
getters: {
onEvent(state) {
return function (a) {
a = state.eventlist;
return a;
}
}
},
mutations: {
WebSocketInit(state, url) {
var that = this;
state.websock = new WebSocket(url);
state.websock.onopen = function () {
console.log("WebSocket连接成功");
heartCheck.reset().start();
};
state.websock.onclose = function () {
console.log("WebSocket断开连接");
};
state.websock.onmessage = function (callBack) {
heartCheck.reset().start();
let datas = callBack.data;
console.log("WebSocket服务端向客户端推送:" + datas);
let datasList = [];
datasList = datas.split('$$$');
state.eventlist = datasList;
};
state.websock.onerror = function () {
console.log("WebSocket断开连接,网络连接异常");
state.websock.close();
reconnect(url);
};
function reconnect(url) {
if (state.lockReconnect) {
return;
}
state.lockReconnect = true;
setTimeout(function () {
console.info("WebSocket尝试重连中..." + new Date());
state.lockReconnect = false;
that.commit('WebSocketInit', url);
}, 5000);
}
var heartCheck = {
timeout: 300000,
serverTimeoutObj: null,
reset: function () {
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
state.websocket_connected_count = 0;
return this;
},
start: function () {
var self = this;
this.serverTimeoutObj = setInterval(function () {
if (state.websock.readyState == 1) {
console.log("WebSocket连接正常,发送消息保持连接");
state.websock.send('{"info":"心跳包"}');
heartCheck.reset().start();
} else {
console.log("WebSocket断开连接,尝试重连");
reconnect(url);
}
}, this.timeout)
}
};
},
WebSocketSend(state, p) {
console.log("WebSocket客户端向服务端发送:" + JSON.stringify(p.jsonData.msg));
state.websock.send(JSON.stringify(p.jsonData.msg));
},
WebSocketClose(state) {
console.log("WebSocket主动关闭连接");
state.websock.close();
}
},
actions: {
WebSocketInit({commit}, url) {
commit('WebSocketInit', url)
},
WebSocketSend({commit}, p) {
p.type = 3;
commit('WebSocketSend', p)
},
WebSocketClose({commit}) {
commit('WebSocketClose')
}
}
})
Vue相关代码(在src目录下,main.js):
import Vue from 'vue';
import websocket from './Base/WebSocketUtil.js';
Vue.prototype.$websocket = websocket;
Vue相关代码(路由,index.js):
import user1 from "@/components/user1";
import user2 from "@/components/user2";
{
path: '/user1',
name: 'user1',
component: user1,
},
{
path: '/user2',
name: 'user2',
component: user2,
},
Vue相关代码(user1.vue):
<template>
<div>
<div style="width: 500px">
<p style="text-align: center">用户1</p>
<el-input placeholder="请输入内容" v-model="text"></el-input>
<el-button type="primary" @click="send">发送</el-button>
<el-button type="primary" @click="flush">发送2</el-button>
<el-button type="primary" @click="exit">退出</el-button>
</div>
</div>
</template>
<script>
export default {
name: 'user1',
created() {
this.$websocket.dispatch("WebSocketInit", "ws://localhost:8080/websocket/521");
},
computed: {
alertCont() {
return this.$websocket.getters.onEvent('Websocket');
}
},
watch: {
alertCont: function (a) {
console.log("服务端推送消息(用户1):" + a[0] + "," + a[1]);
},
},
data() {
return {
text: ''
}
},
methods: {
exit() {
this.$websocket.state.websock.close();
},
flush() {
this.$axios({
url: '/serverSendMessage',
method: 'GET'
}).then(data => {
console.log(data.data);
}).catch(error => {
console.log(error);
})
},
send() {
this.$websocket.state.websock.send('{"toUserId":"520","contentText":"' + this.text + '"}');
}
}
}
</script>
<style scoped>
</style>
Vue相关代码(user2.vue):
<template>
<div>
<div style="width: 500px">
<p style="text-align: center">用户2</p>
<el-input placeholder="请输入内容" v-model="text"></el-input>
<el-button type="primary" @click="send">发送</el-button>
<el-button type="primary" @click="exit">退出</el-button>
</div>
</div>
</template>
<script>
export default {
name: "user2",
created() {
this.$websocket.dispatch("WebSocketInit", "ws://localhost:8080/websocket/520");
},
computed: {
alertCont() {
return this.$websocket.getters.onEvent('WebSocket');
}
},
watch: {
alertCont: function (a) {
console.log("服务端推送消息(用户2):" + a[0] + "," + a[1]);
},
},
data() {
return {
text: ''
}
},
methods: {
exit() {
this.$websocket.state.websock.close();
},
send() {
this.$websocket.state.websock.send('{"toUserId":"521","contentText":"' + this.text + '"}');
}
}
}
</script>
<style scoped>
</style>
<style>
</style>
开始测试(建立连接):
开始测试(发送消息):
开始测试(服务器推送消息):
开始测试(断开连接):
总结
每天一个提升小技巧!!!
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)