WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。
HTTP 协议有一个缺陷:通信只能由客户端发起,HTTP 协议做不到服务器主动向客户端推送信息
WebSocket 一旦 WebSocket 连接建立后,后续数据都以帧序列的形式传输。只要打开一次连接就可以一直发送数据了
org.springframework.boot
spring-boot-starter-websocket
在这里插入代码片import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* @auther 高松
* @DATE 2019/7/26 23:19
* spring-redis
*/
@Configuration
//注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
//注册STOMP协议的节点(endpoint),并映射指定的url
public void registerStompEndpoints(StompEndpointRegistry registry) {
//注册一个STOMP的endpoint,并指定使用SockJS协议
registry.addEndpoint("/endpointOyzc").setAllowedOrigins("*").withSockJS();
}
@Override
//配置消息代理(Message Broker)
public void configureMessageBroker(MessageBrokerRegistry registry) {
//点对点应配置一个/user消息代理,广播式应配置一个/topic消息代理
registry.enableSimpleBroker("/topic", "/user");
//点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/
registry.setUserDestinationPrefix("/user");
}
}
import com.example.springredis.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.UUID;
import java.util.concurrent.ConcurrentSkipListSet;
/**
* @auther 高松
* @DATE 2019/7/22 0:07
* spring-redis
*/
@RestController
public class UserController {
@Autowired
private SimpMessagingTemplate template;
private ConcurrentSkipListSet concurrentSkipListSet = new ConcurrentSkipListSet();
@RequestMapping("/login")
public String login(HttpSession session, HttpServletResponse httpServletResponse) {
Cookie uid1 = new Cookie("uid", session.getId());
UUID uuid = UUID.randomUUID();
httpServletResponse.addCookie(uid1);
httpServletResponse.addHeader("token",uuid.toString());
session.setAttribute("token", uuid.toString());
concurrentSkipListSet.add(uuid.toString());
return uuid.toString();
}
@RequestMapping("test1")
public String trest1(HttpSession session,HttpServletRequest httpServletRequest){
System.out.println(httpServletRequest.getHeader("token"));
if(concurrentSkipListSet.contains(httpServletRequest.getHeader("token"))){
template.convertAndSendToUser( httpServletRequest.getHeader("token"),"/test1","我是点对点消息");
template.convertAndSend("/topic/test1","我是广播的消息");
return "发送成功";
}
return "请重新登录";
}
}
initWebSocket() {
this.connection();
let that = this; // 断开重连机制,尝试发送消息,捕获异常发生时重连
/*this.timer = setInterval(() => {
try {
that.stompClient.send("test");
} catch (err) {
console.log("断线了: " + err);
that.connection();
}
}, 5000);*/
},
connection() {
// 建立连接对象
let socket = new SockJS('http://127.0.0.1:8081/endpointOyzc');
// 获取STOMP子协议的客户端对象
this.stompClient = Stomp.over(socket);
// 定义客户端的认证信息,按需求配置
let headers = {Authorization: 'gsong'}
// 向服务器发起websocket连接
debugger
let token = localStorage.getItem("token");
this.stompClient.connect({}, () => {
this.stompClient.subscribe('/user/'+token+'/test1', (msg) => {
// 订阅服务端提供的某个topic
console.log('点对点订阅成功')
console.log(msg);
// msg.body存放的是服务端发送给我们的信息
}, headers);
this.stompClient.subscribe('/topic/test1', (msg) => {
// 订阅服务端提供的某个topic
console.log('广播成功')
console.log(msg);
// msg.body存放的是服务端发送给我们的信息
}, headers);
// this.stompClient.send("/app/chat.addUser", headers, JSON.stringify({sender: '', chatType: 'JOIN'}),)
//用户加入接口
}, (err) => {
// 连接发生错误时的处理函数
console.log('失败')
console.log(err);
});
},
//连接 后台
disconnect() {
if (this.stompClient) {
this.stompClient.disconnect();
}
}, // 断开连接
// 引入模块
import axios from "axios"
import qs from 'qs'
// 是否允许跨域
axios.defaults.withCredentials=true;
// axios初始化:延迟时间,主路由地址
let instance = axios.create({
baseURL: 'http://localhost:8081/',
timeout: 10,
/* headers :{
'Content-Type': ' Access-Control-Allow-Origin'
}*/
});
// 设置拦截器
instance.interceptors.request.use(function(config){
//在发送请求之前做某事
let token = localStorage.getItem("token");
config.headers.token=token;
return config;
},function(error){
//请求错误时做些事
return Promise.reject(error);
});
//响应拦截器
instance.interceptors.response.use(function(response){
//对响应数据做些事
return response;
},function(error){
//请求错误时做些事
return Promise.reject(error);
});
// 是否销毁拦截器
// 1.给拦截器起个名称 var myInterceptors = instance.interceptors.requesst.use();
// 2.instance.interceptors.request.eject(myInterceptor);
// 请求成功的回调
function checkStatus(res) {
//请求结束成功
if (res.status === 200 || res.status === 304) {
return res.data
}
return {
code: 0,
msg: res.data.msg || res.statusText,
data: res.statusText
}
return res
}
// 请求失败的回调
function checkCode(res) {
if (res.code === 0) {
throw new Error(res.msg)
}
return res
}
//模块化导出
export default {
get(url, params) {
if (!url) return;
return instance({
method: 'get',
url: url,
params,
timeout: 30000
}).then(checkStatus).then(checkCode)
},
post(url, data) {
if (!url) return;
return instance({
method: 'post',
url: url,
data: qs.stringify(data),
timeout: 30000
}).then(checkStatus).then(checkCode)
},
postFile(url, data) {
if (!url) return;
return instance({
method: 'post',
url: url,
data
}).then(checkStatus).then(checkCode)
}
}
test(){
this.$http.get("test1",{})
.then(res => {
console.log(res);//获取数据
})
.catch(error => {
console.log(error);
})
},
login(){
debugger
this.$http.get("login",{})
.then(res => {
console.log(res);//获取数据
localStorage.setItem("token",res)
this.initWebSocket();
})
.catch(error => {
console.log(error);
})
},
开了两个浏览器,下图是连接输出信息
下图中点击右边的获取信息按钮,这时后台会发送,广播类型和点对点类型,由于是右边点击的所以右图会受到两种类型消息,左边只能收到一种类型的消息
源码地址:
后端:
链接:https://pan.baidu.com/s/1NPE7ag5geqQqLX1TYEe_Fg
提取码:7ui6
前端:
链接:https://pan.baidu.com/s/1AKnAGbCLB8ZyU-Xud2Ss-w
提取码:j3wt