Spring boot 集成netty实现websocket通信
一、netty介绍
Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议(包括FTP、SMTP、HTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性
二、工程搭建
实验目标:实现推送消息给指定的用户
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>springboot-demo</artifactId><groupId>com.et</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>netty</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.87.Final</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.6.1</version></dependency></dependencies>
</project> 属性文件
server:port: 8088 netty server
package com.et.netty.server;import com.et.netty.config.ProjectInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;/*** @author dongliang7* @projectName websocket-parent* @ClassName NettyServer.java* @description: TODO* @createTime 2023年02月06日 16:41:00*/
@Component
public class NettyServer {static final Logger log = LoggerFactory.getLogger(NettyServer.class);/*** 端口号*/@Value("${webSocket.netty.port:8889}")int port;EventLoopGroup bossGroup;EventLoopGroup workGroup;@AutowiredProjectInitializer nettyInitializer;@PostConstructpublic void start() throws InterruptedException {new Thread(() -> {bossGroup = new NioEventLoopGroup();workGroup = new NioEventLoopGroup();ServerBootstrap bootstrap = new ServerBootstrap();// bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作bootstrap.group(bossGroup, workGroup);// 设置NIO类型的channelbootstrap.channel(NioServerSocketChannel.class);// 设置监听端口bootstrap.localAddress(new InetSocketAddress(port));// 设置管道bootstrap.childHandler(nettyInitializer);// 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功ChannelFuture channelFuture = null;try {channelFuture = bootstrap.bind().sync();log.info("Server started and listen on:{}", channelFuture.channel().localAddress());// 对关闭通道进行监听channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {e.printStackTrace();}}).start();}/*** 释放资源*/@PreDestroypublic void destroy() throws InterruptedException {if (bossGroup != null) {bossGroup.shutdownGracefully().sync();}if (workGroup != null) {workGroup.shutdownGracefully().sync();}}
} ProjectInitializer
初始化,设置websocket handler
package com.et.netty.config;import com.et.netty.handler.WebSocketHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** @author dongliang7* @projectName websocket-parent* @ClassName ProjectInitializer.java* @description: 管道配置* @createTime 2023年02月06日 16:43:00*/
@Component
public class ProjectInitializer extends ChannelInitializer<SocketChannel> {/*** webSocket协议名*/static final String WEBSOCKET_PROTOCOL = "WebSocket";/*** webSocket路径*/@Value("${webSocket.netty.path:/webSocket}")String webSocketPath;@AutowiredWebSocketHandler webSocketHandler;@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {// 设置管道ChannelPipeline pipeline = socketChannel.pipeline();// 流水线管理通道中的处理程序(Handler),用来处理业务// webSocket协议本身是基于http协议的,所以这边也要使用http编解码器pipeline.addLast(new HttpServerCodec());pipeline.addLast(new ObjectEncoder());// 以块的方式来写的处理器pipeline.addLast(new ChunkedWriteHandler());pipeline.addLast(new HttpObjectAggregator(8192));pipeline.addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));// 自定义的handler,处理业务逻辑pipeline.addLast(webSocketHandler);}
} WebSocketHandler
package com.et.netty.handler;import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.et.netty.config.NettyConfig;
import com.et.netty.server.NettyServer;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;/*** @author dongliang7* @projectName websocket-parent* @ClassName WebSocketHandler.java* @description: TODO* @createTime 2023年02月06日 16:44:00*/
@Component
@ChannelHandler.Sharable
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {private static final Logger log = LoggerFactory.getLogger(NettyServer.class);/*** 一旦连接,第一个被执行*/@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {log.info("有新的客户端链接:[{}]", ctx.channel().id().asLongText());// 添加到channelGroup 通道组NettyConfig.getChannelGroup().add(ctx.channel());}/*** 读取数据*/@Overrideprotected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {log.info("服务器收到消息:{}", msg.text());// 获取用户ID,关联channelJSONObject jsonObject = JSONUtil.parseObj(msg.text());String uid = jsonObject.getStr("uid");NettyConfig.getChannelMap().put(uid, ctx.channel());// 将用户ID作为自定义属性加入到channel中,方便随时channel中获取用户IDAttributeKey<String> key = AttributeKey.valueOf("userId");ctx.channel().attr(key).setIfAbsent(uid);// 回复消息ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器收到消息啦"));}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {log.info("用户下线了:{}", ctx.channel().id().asLongText());// 删除通道NettyConfig.getChannelGroup().remove(ctx.channel());removeUserId(ctx);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {log.info("异常:{}", cause.getMessage());// 删除通道NettyConfig.getChannelGroup().remove(ctx.channel());removeUserId(ctx);ctx.close();}/*** 删除用户与channel的对应关系*/private void removeUserId(ChannelHandlerContext ctx) {AttributeKey<String> key = AttributeKey.valueOf("userId");String userId = ctx.channel().attr(key).get();NettyConfig.getChannelMap().remove(userId);}
} NettyConfig
package com.et.netty.config;import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;import java.util.concurrent.ConcurrentHashMap;/*** @author dongliang7* @projectName websocket-parent* @ClassName NettyConfig.java* @description: 管理全局Channel以及用户对应的channel(推送消息)* @createTime 2023年02月06日 16:43:00*/
public class NettyConfig {/*** 定义全局单利channel组 管理所有channel*/private static volatile ChannelGroup channelGroup = null;/*** 存放请求ID与channel的对应关系*/private static volatile ConcurrentHashMap<String, Channel> channelMap = null;/*** 定义两把锁*/private static final Object lock1 = new Object();private static final Object lock2 = new Object();public static ChannelGroup getChannelGroup() {if (null == channelGroup) {synchronized (lock1) {if (null == channelGroup) {channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);}}}return channelGroup;}public static ConcurrentHashMap<String, Channel> getChannelMap() {if (null == channelMap) {synchronized (lock2) {if (null == channelMap) {channelMap = new ConcurrentHashMap<>();}}}return channelMap;}public static Channel getChannel(String userId) {if (null == channelMap) {return getChannelMap().get(userId);}return channelMap.get(userId);}
} controller
package com.et.netty.controller;import com.et.netty.service.PushMsgService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @author dongliang7* @projectName* @ClassName TestController.java* @description: TODO* @createTime 2023年02月06日 17:48:00*/
@RestController
@RequestMapping("/push")
public class TestController {@AutowiredPushMsgService pushMsgService;/*** 推送消息到具体客户端* @param uid*/@GetMapping("/{uid}")public void pushOne(@PathVariable String uid) {pushMsgService.pushMsgToOne(uid, "hello-------------------------");}/*** 推送消息到所有客户端*/@GetMapping("/pushAll")public void pushAll() {pushMsgService.pushMsgToAll("hello all-------------------------");}
} PushMsgService
package com.et.netty.service;/*** @author dongliang7* @projectName websocket-parent* @ClassName PushMsgService.java* @description: 推送消息接口* @createTime 2023年02月06日 16:44:00*/
public interface PushMsgService {/*** 推送给指定用户*/void pushMsgToOne(String userId, String msg);/*** 推送给所有用户*/void pushMsgToAll(String msg);
} PushMsgServiceImpl
package com.et.netty.service;import com.et.netty.config.NettyConfig;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.springframework.stereotype.Service;import java.util.Objects;/*** @author dongliang7* @projectName websocket-parent* @ClassName PushMsgServiceImpl.java* @description: 推送消息实现类* @createTime 2023年02月06日 16:45:00*/
@Service
public class PushMsgServiceImpl implements PushMsgService {@Overridepublic void pushMsgToOne(String userId, String msg) {Channel channel = NettyConfig.getChannel(userId);if (Objects.isNull(channel)) {throw new RuntimeException("未连接socket服务器");}channel.writeAndFlush(new TextWebSocketFrame(msg));}@Overridepublic void pushMsgToAll(String msg) {NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));}
} 文章值贴出部分关键代码,具体的详情代码参加代码仓库的netty模块
代码仓库
https://github.com/Harries/springboot-demo
三、测试
启动springboot工程
2024-03-08 11:21:32.975 INFO 10348 --- [ Thread-2] com.et.netty.server.NettyServer : Server started and listen on:/0:0:0:0:0:0:0:0:8889 postman创建websocket连接 ws://127.0.0.1:8889/webSocket,并发送消息{'uid':'sss'}给服务端
打开浏览器,给用户sss推送消息 http://127.0.0.1:8088/push/sss
四、引用
https://www.cnblogs.com/dongl961230/p/17099057.html
http://www.liuhaihua.cn/archives/710299.html
相关文章:
Spring boot 集成netty实现websocket通信
一、netty介绍 Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和U…...
数码管的动态显示(二)
1.原理 这个十六进制是右边的dp为高位。 数码管的动态显示,在第一个计数周期显示个位,在第二个周期显示十位,在第三个周期显示百位由于人眼的视觉和数码管的特性,感觉就是显示了234,每个数码管的显示需要从输入的数据里…...
【JavaScript】数据类型转换 ① ( 隐式转换 和 显式转换 | 常用的 数据类型转换 | 转为 字符串类型 方法 )
文章目录 一、 JavaScript 数据类型转换1、数据类型转换2、隐式转换 和 显式转换3、常用的 数据类型转换4、转为 字符串类型 方法 一、 JavaScript 数据类型转换 1、数据类型转换 在 网页端 使用 HTML 表单 和 浏览器输入框 prompt 函数 , 接收的数据 是 字符串类型 变量 , 该…...
git学习(创建项目提交代码)
操作步骤如下 git init //初始化git remote add origin https://gitee.com/aydvvs.git //建立连接git remote -v //查看git add . //添加到暂存区git push 返送到暂存区git status // 查看提交代码git commit -m初次提交git push -u origin "master"//提交远程分支 …...
Day36:安全开发-JavaEE应用第三方组件Log4j日志FastJson序列化JNDI注入
目录 Java-项目管理-工具配置 Java-三方组件-Log4J&JNDI Java-三方组件-FastJson&反射 思维导图 Java知识点: 功能:数据库操作,文件操作,序列化数据,身份验证,框架开发,第三方库使用…...
HTML5+CSS3+JS小实例:全屏范围滑块
实例:全屏范围滑块 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale…...
ctf杂项总结
1.文件无法打开 1.1.文件拓展名损坏/错误导致 方法: 1.使用kali当中的file命令查看,之后修改为正确的后缀即可 2.通过16进制编辑器打开查看文件头 3.文件头残缺/错误,可以先使用kail当中的file命令查看它的类型,之后再通过 16…...
openAI key 与ChatGPTPlus的关系,如何升级ChatGPTPLus
一、前言 先详细介绍一下Plus会员和Open API之间的区别: 实际上,这两者是相互独立的。举例来说,虽然您开通了Plus会员,并不意味着您就可以使用4.0版本的API。尽管这两个账户可以是同一个,但它们是完全独立的平台。 …...
KB5034441 0x80070643 reagentc.exe 无法更新引导配置数据
微软2024年1月的更新补丁正常更新会出现0x80070643错误,原因是正常安装系统默认的恢复分区留小了,通过压缩系统盘空间然后在diskgenius扩容恢复分区空间可以解决这个问题,但是笔者在进行上述操作时依旧出现了报错,按照网上的说法可…...
全网最最最详细“Jupyter command ‘jupyter-notebook‘ not found.“的解决方案
"Jupyter command jupyter-notebook not found."。这通常意味着 jupyter-notebook 命令在当前的虚拟环境中未安装或未正确安装,因此系统无法识别此命令。 原因分析 未安装 Jupyter Notebook: 可能你的虚拟环境中还没有安装 Jupyter Notebook。虽然 Jupyt…...
Java中常用的集合及方法(2)
在Java(JDK8)中,集合(Collection)是数据结构的实现,用于存储和操作对象集合。 集合(Collection)中包含的一般类或接口: 在这其中呢,我们经常使用的其实就是L…...
如何轻松打造属于自己的水印相机小程序?
水印相机小程序源码 描述:微信小程序。本文将为您详细介绍小程序水印相机源码的搭建过程,教您如何轻松打造属于自己的水印相机小程序。无论您是初学者还是有一定基础的开发者,都能轻松掌握这个教程。 一:水印相机搭建教程 1 隐…...
Qt+FFmpeg+opengl从零制作视频播放器-12.界面美化
Qt是一个跨平台的C++图形用户界面应用程序开发框架,提供了丰富的功能和工具来创建美观的界面。以下是一些方法,可以帮助美化Qt界面: 使用样式表(QSS):Qt支持通过QSS(Qt样式表)来自定义界面的外观。QSS是一种类似于CSS的语言,可以用来设置控件的颜色、字体、边框等样式…...
【测试】1. 概念 + 基础篇
概念篇 测试相较于开发岗位而言,如果同学们的编程能力稍微弱一些,可以尝试测试方向(更简单) 1. 什么是软件测试 最常见的理解是:软件测试就是找BUG,发现缺陷。 早期,人们更多的将测试看成是对…...
【数据结构取经之路】快速排序的非递归实现
概述 递归实现快速排序在一些场景下有栈溢出的风险,下面就谈谈如何用非递归的方法实现快速排序。 非递归实现的思想 递归实现与非递归实现快速排序的本质是一致的,效率并不会因为用了非递归实现而有所提升。递归实现快速排序的本质就在于通过递归&…...
面试官: Spring Boot中spring-boot-starter-parent 有什么用
该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:spring-boot-starter-parent 有什么用 spring-boot-starter-parent 是 Spring Boot 提供的一个父项目,通过继承该父项目,可以简化 Spring Boot …...
手搭手RocketMQ发送消息
消息中间件的对比 消息中间件 ActiveMQ RabbitMQ RocketMQ kafka 开发语言 java erlang java scala 单击吞吐量 万级 万级 10万级 10万级 时效性 ms us ms ms 可用性 高(主从架构) 高(主从架构) 非常高(主从架构) 非常高(主从架构) 消息中间件: acti…...
Mysql数据库的优点
功能强大 MySQL中提供了多种数据库存储引擎,各引擎各有所长,适用于不同的应用场合,用户可以选择最合适的引擎以得到最高性能,可以处理每天访问量超过数亿的高强度的搜索 Web 站点。MySQL5 支持事务、视图、存储过程、触发器等。 支持跨平台 MySQL支持至少20种以上的开发…...
蓝桥杯练习系统(算法训练)ALGO-980 斐波那契串
资源限制 内存限制:256.0MB C/C时间限制:10.0s Java时间限制:30.0s Python时间限制:50.0s 问题描述 斐波那契串由下列规则生成: F[0] "0"; F[1] "1"; F[n] F[n-1] F[n-2]…...
AHU 数据库 实验五
【实验名称】 实验5 数据库的数据更新与视图管理 【实验目的】 1. 熟悉数据更新操作的概念与操作类型; 2. 熟练掌握INSERT、UPDATE、DELETE语句的基本语法; 3. 熟练运用INSERT、UPDATE、DELETE语句实现数据的插入、修改与删除…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
从面试角度回答Android中ContentProvider启动原理
Android中ContentProvider原理的面试角度解析,分为已启动和未启动两种场景: 一、ContentProvider已启动的情况 1. 核心流程 触发条件:当其他组件(如Activity、Service)通过ContentR…...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
