什么是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. 二维数组转化为一维数组 contact 与apply 的结合

    将多维数组(尤其是二维数组)转化为一维数组是业务开发中的常用逻辑,除了使用朴素的循环转换以外,我们还可以利用Javascript的语言特性实现更为简洁优雅的转换.本文将从朴素的循环转换开始,逐一介绍三 ...

  2. Linux环境(CentOS)安装维护过程中用到的常见命令

    1. yum 安装时需要选择仓库 一般的路径 /etc/repos.d/ 2. 查看安装了哪些软件的 yum list |grep docker installed 的就是已经安装的软件. 3. 卸载 ...

  3. Angular require(抄别的)

    require参数的值可以用下面的前缀进行修饰,这会改变查找控制器时的行为:?如果在当前指令中没有找到所需要的控制器,会将null作为传给link函数的第四个参数.^如果添加了^前缀,指令会在上游的指 ...

  4. [转]能用HTML/CSS解决的问题就不要使用JS

    原文链接:http://www.codeceo.com/article/html-css-not-js.html 为什么说能使用html/css解决的问题就不要使用JS呢?两个字,因为简单.简单就意味 ...

  5. 使用ssh tunnel 来做代理或跳板

    接前文 http://www.cnblogs.com/piperck/p/6188984.html  使用ssh config配置文件来管理ssh连接 前文说了如何配置自己的ssh config 来方 ...

  6. idea编译器光标变为insert状态

    idea鼠标变成inset状态,不能复制.粘贴使用快捷键 1.打开设置 点击 plugins 输入ideavim  把 这个勾去掉!这个是插件的配置问题. 2.如果上面的不管用,那么检查editor- ...

  7. Dapper 介绍

    转载:http://***/html/itweb/20130918/125194_125199_125210.htm .NET 轻量级 ORM 框架 - Dapper 介绍 Dapper简单介绍: D ...

  8. hdu 1074 (状压dp)

    题意: 给出几个学科的作业.每个作业剩余的时间.完成每个学科作业的时间.如果在剩余时间内不能完成相应作业 就要扣分 延迟一天扣一分 求最小扣分 解析: 把这些作业进行全排列  求出最小扣分即可 但A( ...

  9. MT【212】合作共赢

    一次会议有1990位数学家参加,每人至少有过1327位合作者,求证:可以找到4位数学家,他们中每一个都合作过. 解答:记与$v_i$合作过的数学家构成集合$A_i(i=1,2,\cdots,1990) ...

  10. MT【64】2017联赛一试不等式的一个加强练习

    已知$x_1,x_2,x_3\ge0,x_1+x_2+x_3=1$求 $$(x_1+3x_2+5x_3)(x_1+\frac{1}{3}x_2+\frac{1}{5}x_3)(x_1+x_3+3x_2 ...