什么是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. SMBv1 is not installed by default in Windows 10 Fall Creators Update 2017 and Windows Server, Semi-annual Channel

    windows 10 rs3 release enable SMBv1 windows 10 rs3 release file sharing https://support.microsoft.co ...

  2. [工作相关] 一个虚拟机上面的SAP4HANA的简单使用维护

    1.公司组织竞品分析, 选择了SAP的 SAP4HANA作为竞品 这边协助同事搭建了SAP4HANA的测试环境: 备注 这个环境 应该是同事通过一些渠道获取到的. 里面是基于这个虚拟机进行的说明:: ...

  3. 属性动画总结(Property Animation)

    一.概述 属性动画可以作用在View的属性上,对属性进行修改,而且不要求对应的属性一定是有显示效果的. 二.属性动画的实现方式 1.基础的类Animator Animator是一个抽象类,是属性动画的 ...

  4. 美化centos7

    在美化前,我们先安装一个扩展源.yum install -y epel-release然后安装字体包yum -y install liberation-mono-fonts 安装gnome-menis ...

  5. 热修改 MySQL 数据库 pt-online-schema-change 的使用详解

    由于周五公司团建的关系所以此篇推迟了抱歉. 首先不得不在该篇里面梳理一个数据库热增加删除字段表的工具 pt-online-schema-change 这个工具在前面我的博文 <关于utf8mb4 ...

  6. Highcharts之3D柱状图

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...

  7. CSS等高布局的7种方式

    前面的话 等高布局是指子元素在父元素中高度相等的布局方式.等高布局的实现包括伪等高和真等高,伪等高只是看上去等高而已,真等高是实实在在的等高.本文将介绍边框模拟.负margin这两种伪等高以及tabl ...

  8. 荣耀实锤Magic2或将助力AI,再次带动成长?

    临近年底,热闹了一年的手机圈纷纷偃旗息鼓,准备为明年3月的新品发力.然而今天(12月7日),恰逢节气大雪,@荣耀手机 在微博发布了一张预热海报,随后荣耀总裁赵明转发这条微博表示「关于技术,真的有很多话 ...

  9. Android DownloadManager 的使用

    分类: android 技巧2013-05-28 10:32 3278人阅读 评论(1) 收藏 举报   目录(?)[+]   从Android 2.3(API level 9)开始Android用系 ...

  10. C#/.NET转Java学习笔记

    大学研究了三年的.Net,由于偶然的机会,拿到IBM的Java web实习offer,所以决定转行搞Java(综合了校招情况.发展前景和其他各种因素). 下面是我在学习Java web的一些学习笔记( ...