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'}给服务端95ed8191ebb1c8c9f021df580ed604bf.png打开浏览器,给用户sss推送消息 http://127.0.0.1:8088/push/sssbb8db40adbcff28d239bc1495dff7c36.png 

四、引用

  • https://www.cnblogs.com/dongl961230/p/17099057.html

  • http://www.liuhaihua.cn/archives/710299.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/742439.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数码管的动态显示(二)

1.原理 这个十六进制是右边的dp为高位。 数码管的动态显示&#xff0c;在第一个计数周期显示个位&#xff0c;在第二个周期显示十位&#xff0c;在第三个周期显示百位由于人眼的视觉和数码管的特性&#xff0c;感觉就是显示了234&#xff0c;每个数码管的显示需要从输入的数据里…

Flutter第四弹:Flutter图形渲染性能

目标&#xff1a; 1&#xff09;Flutter图形渲染性能能够媲美原生&#xff1f; 2&#xff09;Flutter性能优于React Native? 一、Flutter图形渲染原理 1.1 Flutter图形渲染原理 Flutter直接调用Skia。 Flutter不使用WebView&#xff0c;也不使用操作系统的原生控件,而是…

labview中6种机械动作的区别

1.单击时转换&#xff1a;单击时转换&#xff0c;需要手动转换或者赋值回复原来状态&#xff1b; 2.释放时转换&#xff1a;释放时时转换&#xff0c;需要手动转换或者赋值回复原来状态&#xff1b; 3.单击时转换保持到鼠标释放&#xff1a;触发两次&#xff0c;自动恢复原来状…

Linux:导出环境变量命令export

相关阅读 Linuxhttps://blog.csdn.net/weixin_45791458/category_12234591.html?spm1001.2014.3001.5482 Linux中的内建命令export命令用于创建一个环境变量&#xff0c;或将一个普通变量导出为环境变量&#xff0c;并且在这个过程中&#xff0c;可以给该环境变量赋值。 下面…

多数问题求解之蒙特卡洛与分治法

多数问题&#xff08;Majority Problem&#xff09;是一个有多种求解方法的经典问题&#xff0c;其问题定义如下&#xff1a; 给定一个大小为 n n n的数组&#xff0c;找出其中出现次数超过 n / 2 n/2 n/2的元素 例如&#xff1a;当输入数组为 [ 5 , 3 , 5 , 2 , 3 , 5 , 5 ] […

【WPS】压缩图片

第一步&#xff1a; 点击插入&#xff0c;点击图片 第二步&#xff1a; 点击图片工具&#xff0c;点击压缩图片 第三步&#xff1a;

Vue3 前端生成随机id( 生成 UUID )

效果展示 封装工具&#xff08;代码展示&#xff09; 重新创建一个文件**/utils/someTools.js**&#xff0c;并在里面写入如下代码。 function Tools() {}Tools.prototype.guid function () {return xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx.replace(/[xy]/g, function (c) {v…

jQuery模态框弹窗提示代码

jQuery模态框弹窗提示代码 下载地址 jQuery模态框弹窗提示代码

Requests教程-17-请求代理设置

上一小节我们学习了requests解决乱码的方法&#xff0c;本小节我们讲解一下requests设置代理的方法。 代理基本原理 代理实际上指的就是代理服务器&#xff0c; 英文叫作proxy server &#xff0c;它的功能是代理网络用户去取得网络信息。形象地说&#xff0c;它是网络信息的中…

【阿里云系列】-基于云效构建部署Springboot项目到ACK

介绍 为了提高项目迭代的速度加速交付产品给客户&#xff0c;我们通常会选择CICD工具来减少人力投入产生的成本&#xff0c;开源的工具比如有成熟的Jenkins&#xff0c;但是本文讲的是阿里云提高的解决方案云效平台&#xff0c;通过配置流水线的形式实现项目的快速部署到服务器…

【数据结构】双向链表及LRU缓存的实现

目录 前言 1. 在原有的自定义链表类 Linked 的基础上&#xff0c;添加新的 “节点添加”方法 addNode(Node node) 测试用例 测试结果 2. 在自定义链表类的基础上&#xff0c;使用双重循环“强力” 判断两个节点是否发生相交 测试用例 测试结果 3. 在自定义链表类的基础上…

基于opencv的手势识别

当然可以&#xff0c;下面是一个使用OpenCV实现简单手势识别&#xff0c;并在摄像头捕捉的视频中描绘出手部轮廓为线条的示例。该代码会读取摄像头流&#xff0c;然后检测出手部&#xff0c;并用线条描绘出手的轮廓。 首先&#xff0c;你需要安装OpenCV库。如果你还没有安装&am…

Vulnhub靶机:Kioptrix_Level1.1

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;192.168.56.101&#xff09; 靶机&#xff1a;Kioptrix_Level1.1&#xff08;192.168.56.104&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://www.vul…

C语言例2-3:从键盘输入一个正整数(位数小于或等于10),判断其是否是回文数

回文数是将自然数n的各位数字反向排列得到自然数n1&#xff0c;若n1与n相等&#xff0c;则称为回文数&#xff0c;例如12321 //从键盘输入一个正整数&#xff08;位数小于或等于10&#xff09;&#xff0c;判断其是否是回文数 //回文数是将自然数n的各位数字反向排列得到自然数…

(BAT向)Java岗常问高频面试汇总:MyBatis 微服务 Spring 分布式 MySQL等

看面试题可以是为了面试&#xff0c;也可以是对自己学到的东西的一种查漏补缺&#xff0c;更加深刻的去了解一些核心知识点 Spring面试高频问题 问题一&#xff1a;谈 需要zi料 绿色徽【vip1024b】 谈你对spring IOC 和 DI 的理解&#xff0c;它们有什么区别&#xff1f; **问题…

Xterminal:未来的终端体验

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 开发环境篇 ✨特色专栏&#xff1a; M…

CTR之行为序列建模用户兴趣:DIEN

前言 在上一篇文章中 CTR之行为序列建模用户兴趣&#xff1a;DIN&#xff0c;开启了用户行为序列建模用户兴趣的篇章。DIN引入了Attention机制&#xff0c;对于不同的候选item&#xff0c;可以根据用户的历史行为序列&#xff0c;动态地学习用户的兴趣表征向量。但是&#xff…

Mybatis-Plus实现Service封装

文章目录 5.1 MP封装Service介绍5.1.1 说明5.1.2 实现流程5.1.3 核心API介绍 5.2 MP封装Service快速入门5.2.1 定义服务扩展接口5.2.2 定义服务实现5.2.3 测试测试 5.3 MP封装Service实现CRUD操作 5.1 MP封装Service介绍 5.1.1 说明 MybatisPlus为了开发更加快捷&#xff0c;…

前端去除网页水印

按F12&#xff0c;打开开发者工具面板&#xff0c;然后直接在样式搜索backgroud 然后直接取消backgroud 的复选框即可。

【Linux】-Linux下的软件商店yum工具介绍(linux和windows互传文件仅仅一个拖拽搞定!!!!)

目录 1.Linux 软件包管理器yum 1.1快速认识yum 1.2 yumz下载方式&#xff08;如何使用yum进行下载&#xff0c;注意下载一定要是root用户或者白名单用户&#xff08;可提权&#xff09;&#xff09; 1.2.1下载小工具rzsz 1.2.2 rzsz使用 1.2.2查看软件包 1.3软件的卸载 2.yum生…