什么是WebSocket

WebSocket是一种在单个TCP连接上进行全双工通信的协议 …

为什么要实现握手监控管理

如果说,连接随意创建,不管的话,会存在错误,broken pipe

表面看单纯报错,并没什么功能缺陷等,但实际,请求数增加,容易导致系统奔溃。这边画重点。

出现原因有很多种,目前我这边出现的原因,是因为客户端已关闭连接,服务端还持续推送导致。

如何使用

下面将使用springboot集成的webSocket

导入Maven

首先SpringBoot版本

 <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
</parent>

集成websocket

        // 加个web集成吧
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

Java代码

Config配置

首先,我们需要重写WebSocketHandlerDecoratorFactory

主要用来监控客户端握手连接进来以及挥手关闭连接

代码

需要一个管理Socket的类

package com.li.manager;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.WebSocketSession; import java.util.concurrent.ConcurrentHashMap; /**
* socket管理器
*/
@Slf4j
public class SocketManager {
private static ConcurrentHashMap<String, WebSocketSession> manager = new ConcurrentHashMap<String, WebSocketSession>(); public static void add(String key, WebSocketSession webSocketSession) {
log.info("新添加webSocket连接 {} ", key);
manager.put(key, webSocketSession);
} public static void remove(String key) {
log.info("移除webSocket连接 {} ", key);
manager.remove(key);
} public static WebSocketSession get(String key) {
log.info("获取webSocket连接 {}", key);
return manager.get(key);
} }
package com.li.factory;

import com.li.manager.SocketManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.WebSocketHandlerDecorator;
import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory; import java.security.Principal; /**
* 服务端和客户端在进行握手挥手时会被执行
*/
@Component
@Slf4j
public class WebSocketDecoratorFactory implements WebSocketHandlerDecoratorFactory {
@Override
public WebSocketHandler decorate(WebSocketHandler handler) {
return new WebSocketHandlerDecorator(handler) {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
log.info("有人连接啦 sessionId = {}", session.getId());
Principal principal = session.getPrincipal();
if (principal != null) {
log.info("key = {} 存入", principal.getName());
// 身份校验成功,缓存socket连接
SocketManager.add(principal.getName(), session);
} super.afterConnectionEstablished(session);
} @Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
log.info("有人退出连接啦 sessionId = {}", session.getId());
Principal principal = session.getPrincipal();
if (principal != null) {
// 身份校验成功,移除socket连接
SocketManager.remove(principal.getName());
}
super.afterConnectionClosed(session, closeStatus);
}
};
}
}
注意:以上session变量,需要注意两点,一个是getId(),一个是getPrincipal().getName()

getId() : 返回的是唯一的会话标识符。

getPrincipal() : 经过身份验证,返回Principal实例,未经过身份验证,返回null

Principal: 委托人的抽象概念,可以是公司id,名字,用户唯一识别token等

当你按上面代码使用,你会发现getPrincipal()返回null,为什么?这边还需要重写一个DefaultHandshakeHandler

代码
package com.li.handler;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler; import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
import java.util.Map; /**
* 我们可以通过请求信息,比如token、或者session判用户是否可以连接,这样就能够防范非法用户
*/
@Slf4j
@Component
public class PrincipalHandshakeHandler extends DefaultHandshakeHandler {
@Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
/**
* 这边可以按你的需求,如何获取唯一的值,既unicode
* 得到的值,会在监听处理连接的属性中,既WebSocketSession.getPrincipal().getName()
* 也可以自己实现Principal()
*/
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
HttpServletRequest httpRequest = servletServerHttpRequest.getServletRequest();
/**
* 这边就获取你最熟悉的陌生人,携带参数,你可以cookie,请求头,或者url携带,这边我采用url携带
*/
final String token = httpRequest.getParameter("token");
if (StringUtils.isEmpty(token)) {
return null;
}
return new Principal() {
@Override
public String getName() {
return token;
}
};
}
return null;
}
}

需要的东西都有了,那准备装载吧

代码
package com.li.config;

import com.li.factory.WebSocketDecoratorFactory;
import com.li.handler.PrincipalHandshakeHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration; /**
* WebSocketConfig配置
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Autowired
private WebSocketDecoratorFactory webSocketDecoratorFactory;
@Autowired
private PrincipalHandshakeHandler principalHandshakeHandler; @Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
/**
* myUrl表示 你前端到时要对应url映射
*/
registry.addEndpoint("/myUrl")
.setAllowedOrigins("*")
.setHandshakeHandler(principalHandshakeHandler)
.withSockJS();
} @Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
/**
* queue 点对点
* topic 广播
* user 点对点前缀
*/
registry.enableSimpleBroker("/queue", "/topic");
registry.setUserDestinationPrefix("/user");
} @Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.addDecoratorFactory(webSocketDecoratorFactory);
super.configureWebSocketTransport(registration);
}
}

终于完成了

最后,来一个通过http请求,将其发送到客户端

代码
package com.li.controller;

import com.li.manager.SocketManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.socket.WebSocketSession; import java.util.Map; @RestController
@Slf4j
public class TestController { @Autowired
private SimpMessagingTemplate template; /**
* 服务器指定用户进行推送,需要前端开通 var socket = new SockJS(host+'/myUrl' + '?token=1234');
*/
@RequestMapping("/sendUser")
public void sendUser(String token) {
log.info("token = {} ,对其发送您好", token);
WebSocketSession webSocketSession = SocketManager.get(token);
if (webSocketSession != null) {
/**
* 主要防止broken pipe
*/
template.convertAndSendToUser(token, "/queue/sendUser", "您好");
} } /**
* 广播,服务器主动推给连接的客户端
*/
@RequestMapping("/sendTopic")
public void sendTopic() {
template.convertAndSend("/topic/sendTopic", "大家晚上好"); } /**
* 客户端发消息,服务端接收
*
* @param message
*/
// 相当于RequestMapping
@MessageMapping("/sendServer")
public void sendServer(String message) {
log.info("message:{}", message);
} /**
* 客户端发消息,大家都接收,相当于直播说话
*
* @param message
* @return
*/
@MessageMapping("/sendAllUser")
@SendTo("/topic/sendTopic")
public String sendAllUser(String message) {
// 也可以采用template方式
return message;
} /**
* 点对点用户聊天,这边需要注意,由于前端传过来json数据,所以使用@RequestBody
* 这边需要前端开通var socket = new SockJS(host+'/myUrl' + '?token=4567'); token为指定name
* @param map
*/
@MessageMapping("/sendMyUser")
public void sendMyUser(@RequestBody Map<String, String> map) {
log.info("map = {}", map);
WebSocketSession webSocketSession = SocketManager.get(map.get("name"));
if (webSocketSession != null) {
log.info("sessionId = {}", webSocketSession.getId());
template.convertAndSendToUser(map.get("name"), "/queue/sendUser", map.get("message"));
}
} }

前端代码

可以直接启动

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Spring Boot WebSocket+广播式</title>
</head>
<body>
<noscript>
<h2 style="color:#ff0000">貌似你的浏览器不支持websocket</h2>
</noscript>
<div>
<div>
<button id="connect" onclick="connect()">连接</button>
<button id="disconnect" onclick="disconnect();">断开连接</button>
</div>
<div id="conversationDiv">
<label>输入你的名字</label> <input type="text" id="name" />
<br>
<label>输入消息</label> <input type="text" id="messgae" />
<button id="send" onclick="send();">发送</button>
<p id="response"></p>
</div>
</div>
<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript">
var stompClient = null;
//gateway网关的地址
var host="http://127.0.0.1:8888";
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
$('#response').html();
}
// SendUser ***********************************************
function connect() {
//地址+端点路径,构建websocket链接地址,注意,对应config配置里的addEndpoint
var socket = new SockJS(host+'/myUrl' + '?token=4567');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected:' + frame);
//监听的路径以及回调
stompClient.subscribe('/user/queue/sendUser', function(response) {
showResponse(response.body);
});
});
}
/*
function connect() {
//地址+端点路径,构建websocket链接地址,注意,对应config配置里的addEndpoint
var socket = new SockJS(host+'/myUrl');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected:' + frame);
//监听的路径以及回调
stompClient.subscribe('/topic/sendTopic', function(response) {
showResponse(response.body);
});
});
}*/
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function send() {
var name = $('#name').val();
var message = $('#messgae').val();
/*//发送消息的路径,由客户端发送消息到服务端
stompClient.send("/sendServer", {}, message);
*/
/*// 发送给所有广播sendTopic的人,客户端发消息,大家都接收,相当于直播说话 注:连接需开启 /topic/sendTopic
stompClient.send("/sendAllUser", {}, message);
*/
/* 这边需要注意,需要启动不同的前端html进行测试,需要改不同token ,例如 token=1234,token=4567
* 然后可以通过写入name 为token 进行指定用户发送
*/
stompClient.send("/sendMyUser", {}, JSON.stringify({name:name,message:message}));
}
function showResponse(message) {
var response = $('#response');
response.html(message);
}
</script>
</body>
</html>

注意,记得要自己尝试,好记性不如烂笔头。

版权声明:本文为不会代码的小白原创文章,转载需添加小白地址 : https://www.ccode.live/bertonlee/list/8?from=art

源代码:https://github.com/bertonlee/webSocket-01 欢迎Star

欢迎关注

欢迎关注公众号“码上开发”,每天分享最新技术资讯

springboot websocket 一篇足够了的更多相关文章

  1. SpringBoot WebSocket STOMP 广播配置

    目录 1. 前言 2. STOMP协议 3. SpringBoot WebSocket集成 3.1 导入websocket包 3.2 配置WebSocket 3.3 对外暴露接口 4. 前端对接测试 ...

  2. 认识WebSocket理论篇

    本文转自http://www.ibm.com/developerworks/cn/java/j-lo-WebSocket/ HTML5作为下一代WEB标准,拥有许多引人注目的新特性,如Canvas.本 ...

  3. SpringBoot+WebSocket

    SpringBoot+WebSocket 只需三个步骤 导入依赖 <dependency> <groupId>org.springframework.boot</grou ...

  4. springboot+websocket+sockjs进行消息推送【基于STOMP协议】

    springboot+websocket+sockjs进行消息推送[基于STOMP协议] WebSocket是在HTML5基础上单个TCP连接上进行全双工通讯的协议,只要浏览器和服务器进行一次握手,就 ...

  5. SpringBoot第六篇:整合通用Mapper

    作者:追梦1819 原文:https://www.cnblogs.com/yanfei1819/p/10876339.html 版权声明:本文为博主原创文章,转载请附上博文链接! 引言   在以往的项 ...

  6. SpringBoot第七篇:整合Mybatis-Plus

    作者:追梦1819 原文:https://www.cnblogs.com/yanfei1819/p/10881666.html 版权声明:本文为博主原创文章,转载请附上博文链接! 引言   一看这个名 ...

  7. Springboot快速入门篇,图文并茂

    Springboot快速入门篇,图文并茂 文章已托管到GitHub,大家可以去GitHub查看阅读,欢迎老板们前来Star!搜索关注微信公众号 [码出Offer] 领取各种学习资料! image-20 ...

  8. Java Springboot webSocket简单实现,调接口推送消息到客户端socket

    Java Springboot webSocket简单实现,调接口推送消息到客户端socket 后台一般作为webSocket服务器,前台作为client.真实场景可能是后台程序在运行时(满足一定条件 ...

  9. Springboot+Websocket+JWT实现的即时通讯模块

    场景 目前做了一个接口:邀请用户成为某课程的管理员,于是我感觉有能在用户被邀请之后能有个立马通知他本人的机(类似微博.朋友圈被点赞后就有立马能收到通知一样),于是就闲来没事搞了一套. ​ 涉及技术栈 ...

随机推荐

  1. ECSHOP后台登陆后一段时间不操作就超时的解决方法

    ECSHOP后台登陆后一段时间不操作就超时的解决方法 ECSHOP教程/ ecshop教程网(www.ecshop119.com) 2012-05-27   客户生意比较好,因此比较忙,常常不在电脑前 ...

  2. [日常工作]非Windows Server 系统远程经常断以及提高性能的方法

    1. 公司内有不少windows xp windows 7 这样的操作系统的机器在机房里面用来跑自动化脚本或者是其他用处. 经常有人反馈机器过一段时间连不上, 其实这一点是一个非常小的地方 很多机器上 ...

  3. jq 事件取消绑定与重新绑定

    前端有时会碰到这样的需求: 点击某个元素发送ajax请求, 发送期间此元素的鼠标相关事件(比如点击)无效, 发送完成鼠标点击事件功能恢复, 对于这种需求, 我们会遇到两种情况, 一种是点击的按钮为fo ...

  4. 洛谷 P4294 [WC2008]游览计划

    题目链接 不是很会呢,但似乎抄了题解后有点明白了 sol:状态DP显然,其实是要构建一棵最小生成树一样的东西,我自己的理解(可能不是很对哦希望多多指教)f[x][y][zt]就是到x,y这个点,状态为 ...

  5. Nginx REWRITE阶段

    rewrite_log on ;#每次rewrite都会记录一条记录在errorlog里 error_log logs/xxx.log notice; location /first { rewrit ...

  6. HDU-1686-KMP-水题

    纯KMP #include <cstdio> #include <algorithm> #include <cstring> #include <ctype. ...

  7. codeforces 777C

    C.Alyona and Spreadsheet During the lesson small girl Alyona works with one famous spreadsheet compu ...

  8. MT【213】二次曲线系方程

    (2013北大夏令营)函数$y=x^2+ax+b$与坐标轴交于三个不同的点$A,B,C$,已知$\Delta ABC$的外心$P$在$y=x$上,求$a+b$的值. 解:由二次曲线系知识知三角形的外接 ...

  9. MT【36】反函数有关的一道题

    解答:$\frac{7}{2}$ 做适当的变换,再令$x-1=t$容易划归到我们熟悉的题型,$2^t=\frac{3}{2}-t,log_2t=\frac{3}{2}-t$作图或者利用函数单调性可得$ ...

  10. 基于Spring Security和 JWT的权限系统设计

    写在前面 关于 Spring Security Web系统的认证和权限模块也算是一个系统的基础设施了,几乎任何的互联网服务都会涉及到这方面的要求.在Java EE领域,成熟的安全框架解决方案一般有 A ...