当前位置: 首页 > news >正文

spring boot 实现直播聊天室(二)

spring boot 实现直播聊天室(二)

技术方案:

  • spring boot
  • netty
  • rabbitmq

目录结构

在这里插入图片描述

引入依赖

<dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.96.Final</version>
</dependency>

SimpleNettyWebsocketServer

netty server 启动类

@Slf4j
public class SimpleNettyWebsocketServer {private SimpleWsHandler simpleWsHandler;public SimpleNettyWebsocketServer(SimpleWsHandler simpleWsHandler) {this.simpleWsHandler = simpleWsHandler;}public void start(int port) throws InterruptedException {NioEventLoopGroup boss = new NioEventLoopGroup(1);NioEventLoopGroup work = new NioEventLoopGroup(2);try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(boss, work).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();//HTTP协议编解码器,用于处理HTTP请求和响应的编码和解码。其主要作用是将HTTP请求和响应消息转换为Netty的ByteBuf对象,并将其传递到下一个处理器进行处理。pipeline.addLast(new HttpServerCodec());//用于HTTP服务端,将来自客户端的HTTP请求和响应消息聚合成一个完整的消息,以便后续的处理。pipeline.addLast(new HttpObjectAggregator(65535));pipeline.addLast(new IdleStateHandler(30,0,0));//处理请求参数pipeline.addLast(new SimpleWsHttpHandler());pipeline.addLast(new WebSocketServerProtocolHandler("/n/ws"));pipeline.addLast(simpleWsHandler);}});Channel channel = bootstrap.bind(port).sync().channel();log.info("server start at port: {}", port);channel.closeFuture().sync();} finally {boss.shutdownGracefully();work.shutdownGracefully();}}
}

NettyUtil: 工具类

public class NettyUtil {public static AttributeKey<String> G_U = AttributeKey.valueOf("GU");/*** 设置上下文参数* @param channel* @param attributeKey* @param data* @param <T>*/public static <T> void setAttr(Channel channel, AttributeKey<T> attributeKey, T data) {Attribute<T> attr = channel.attr(attributeKey);if (attr != null) {attr.set(data);}}/*** 获取上下文参数 * @param channel* @param attributeKey* @return* @param <T>*/public static <T> T getAttr(Channel channel, AttributeKey<T> attributeKey) {return channel.attr(attributeKey).get();}/*** 根据 渠道获取 session* @param channel* @return*/public static NettySimpleSession getSession(Channel channel) {String attr = channel.attr(G_U).get();if (StrUtil.isNotBlank(attr)){String[] split = attr.split(",");String groupId = split[0];String username = split[1];return new NettySimpleSession(channel.id().toString(),groupId,username,channel);}return null;}
}

处理handler

SimpleWsHttpHandler

处理 websocket 协议升级时地址请求参数 ws://127.0.0.1:8881/n/ws?groupId=1&username=tom, 解析groupId 和 username ,并设置这个属性到上下文

/*** @Date: 2023/12/13 9:53* 提取参数*/
@Slf4j
public class SimpleWsHttpHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {if (msg instanceof  FullHttpRequest request){//ws://localhost:8080/n/ws?groupId=xx&username=tomString decode = URLDecoder.decode(request.uri(), StandardCharsets.UTF_8);log.info("raw request url: {}", decode);Map<String, String> queryMap = getParams(decode);String groupId = MapUtil.getStr(queryMap, "groupId", null);String username = MapUtil.getStr(queryMap, "username", null);if (StrUtil.isNotBlank(groupId) && StrUtil.isNotBlank(username)) {NettyUtil.setAttr(ctx.channel(), NettyUtil.G_U, groupId.concat(",").concat(username));}//去掉参数 ===>  ws://localhost:8080/n/wsrequest.setUri(request.uri().substring(0,request.uri().indexOf("?")));ctx.pipeline().remove(this);ctx.fireChannelRead(request);}else{ctx.fireChannelRead(msg);}}/*** 解析 queryString* @param uri* @return*/public static Map<String, String> getParams(String uri) {Map<String, String> params = new HashMap<>(10);int idx = uri.indexOf("?");if (idx != -1) {String[] paramsArr = uri.substring(idx + 1).split("&");for (String param : paramsArr) {idx = param.indexOf("=");params.put(param.substring(0, idx), param.substring(idx + 1));}}return params;}
}
SimpleWsHandler

处理消息

@Slf4j
@ChannelHandler.Sharable
public class SimpleWsHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {@Autowiredprivate PushService pushService;/*** 在新的 Channel 被添加到 ChannelPipeline 中时被调用。这通常发生在连接建立时,即 Channel 已经被成功绑定并注册到 EventLoop 中。* 在连接建立时被调用一次** @param ctx* @throws Exception*/@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {NettySimpleSession session = NettyUtil.getSession(ctx.channel());if (session == null) {log.info("handlerAdded channel id: {}", ctx.channel().id());} else {log.info("handlerAdded channel group-username: {}-{}", session.group(), session.identity());}}/*** 连接断开时,Netty 会自动触发 channelInactive 事件,并将该事件交给事件处理器进行处理* 在 channelInactive 事件的处理过程中,会调用 handlerRemoved 方法** @param ctx* @throws Exception*/@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {NettySimpleSession session = NettyUtil.getSession(ctx.channel());if (session!=null){log.info("handlerRemoved channel group-username: {}-{}", session.group(), session.identity());}offline(ctx.channel());}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {//todo msg 可以是json字符串,这里仅仅只是纯文本NettySimpleSession session = NettyUtil.getSession(ctx.channel());if (session!=null){MessageDto messageDto = new MessageDto();messageDto.setSessionId(session.getId());messageDto.setGroup(session.group());messageDto.setFromUser(session.identity());messageDto.setContent(msg.text());pushService.pushGroupMessage(messageDto);}else {log.info("channelRead0 session is null channel id: {}-{}", ctx.channel().id(),msg.text());}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {log.info("SimpleWsHandler 客户端异常断开 {}", cause.getMessage());//todo offlineoffline(ctx.channel());}@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {if (evt instanceof IdleStateEvent idleStateEvent) {if (idleStateEvent.state().equals(IdleStateEvent.READER_IDLE_STATE_EVENT)) {log.info("SimpleWsIdleHandler channelIdle 5 秒未收到客户端消息,强制关闭: {}", ctx.channel().id());//todo offlineoffline(ctx.channel());}} else if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {String attr = NettyUtil.getAttr(ctx.channel(), NettyUtil.G_U);if (StrUtil.isBlank(attr)) {ctx.writeAndFlush("参数异常");offline(ctx.channel());} else {//todo 可以做用户认证等等//记录用户登陆sessionNettySimpleSession session = NettyUtil.getSession(ctx.channel());Assert.notNull(session, "session 不能为空");SessionRegistry.getInstance().addSession(session);}}super.userEventTriggered(ctx,evt);}/*** 用户下线,处理失效 session* @param channel*/public void offline(Channel channel){NettySimpleSession session = NettyUtil.getSession(channel);if (session!=null){SessionRegistry.getInstance().removeSession(session);}channel.close();}}

PushService

推送服务抽取

public interface PushService {/*** 组推送* @param messageDto*/void pushGroupMessage(MessageDto messageDto);}@Service
public class PushServiceImpl implements PushService {@Autowiredprivate MessageClient messagingClient;@Overridepublic void pushGroupMessage(MessageDto messageDto) {messagingClient.sendMessage(messageDto);}
}

NettySimpleSession

netty session 封装

public class NettySimpleSession extends AbstractWsSession {private Channel channel;public NettySimpleSession(String id, String group, String identity, Channel channel) {super(id, group, identity);this.channel = channel;}@Overridepublic void sendTextMessage(MessageDto messageDto) {String content = messageDto.getFromUser() + " say: " + messageDto.getContent();// 不能直接 write content, channel.writeAndFlush(content);// 要封装成 websocketFrame,不然不能编解码!!!channel.writeAndFlush(new TextWebSocketFrame(content));}
}

启动类

@Slf4j
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}@Beanpublic SimpleWsHandler simpleWsHandler(){return new SimpleWsHandler();}@PostConstructpublic void init() {new Thread(() -> {log.info(">>>>>>>> start netty ws server....");try {new SimpleNettyWebsocketServer(simpleWsHandler()).start(8881);} catch (InterruptedException e) {log.info(">>>>>>>> SimpleNettyWebsocketServer start error", e);}}).start();}}

其他代码参考 spring boot 实现直播聊天室

测试

websocket 地址 ws://127.0.0.1:8881/n/ws?groupId=1&username=tom

在这里插入图片描述

good luck!

相关文章:

spring boot 实现直播聊天室(二)

spring boot 实现直播聊天室(二) 技术方案: spring bootnettyrabbitmq 目录结构 引入依赖 <dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.96.Final</version> </dependency>Si…...

alibaba fastjson GET List传参 和 接收解析

之前一直都是 get传的都是单字符串&#xff08;例如 xxxxxxxxx?name{name};name“woaini”;&#xff09;&#xff0c;并没有传list的. GET List传参 问题场景 String url"xxxxxxxx?id{id}"; HashMap<String,Object> param new HashMap<>(); param.pu…...

API自动化测试是什么?我们该如何做API自动化测试呢?

API测试已经成为测试工作中的常规任务之一。为了提高测试效率并减少重复的手工操作&#xff0c;API自动化测试变得越来越重要。本文总结了API自动化测试方面的经验和心得&#xff0c;旨在与读者分享。 掌握自动化技能已经成为高级测试工程师的必备技能。敏捷和持续测试改变了传…...

PyTorch 的 10 条内部用法

欢迎阅读这份有关 PyTorch 原理的简明指南[1]。无论您是初学者还是有一定经验&#xff0c;了解这些原则都可以让您的旅程更加顺利。让我们开始吧&#xff01; 1. 张量&#xff1a;构建模块 PyTorch 中的张量是多维数组。它们与 NumPy 的 ndarray 类似&#xff0c;但可以在 GPU …...

Django、Echarts异步请求、动态更新

前端页面 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>echarts示例</title> <script src"jquery.min.js"></script><script type "text/javascript" src "echarts.m…...

Mac部署Odoo环境-Odoo本地环境部署

Odoo本地环境部署 安装Python安装Homebrew安装依赖brew install libxmlsec1 Python运行环境Pycharm示例配置 Mac部署Odoo环境-Odoo本地环境部署 安装Python 新机&#xff0c;若系统没有预装Python&#xff0c;则安装需要版本的Python 点击查询Python官网下载 安装Homebrew 一…...

【✅面试编程题:如何用队列实现一个栈】

✅面试编程题&#xff1a;如何用队列实现一个栈 &#x1f4a1;典型回答 &#x1f4a1;典型回答 使用两个队列可以实现一个栈&#xff0c;一个队列用来存储栈中的元素&#xff0c;另一个队列用来在pop操作时暂存元素。 上才艺&#xff1a; import java.util.LinkedList; impo…...

Windows本地的RabbitMQ服务怎么在Docker for Windows的容器中使用

1. 进入管理界面 windows安装过程请访问&#xff1a;Windows安装RabbitMQ、添加PHP的AMQP扩展 浏览器访问&#xff1a;http://127.0.0.1:15672/ 2. 创建虚拟主机 上面访问的是 RabbitMQ 的管理界面&#xff0c;可以在这个界面上进行一些操作&#xff0c;比如创建虚拟主机、…...

YOLOv5改进 | 2023卷积篇 | AKConv轻量级架构下的高效检测(既轻量又提点)

一、本文介绍 本文给大家带来的改进内容是AKConv是一种创新的变核卷积&#xff0c;它旨在解决标准卷积操作中的固有缺陷&#xff08;采样形状是固定的&#xff09;&#xff0c;AKConv的核心思想在于它为卷积核提供了任意数量的参数和任意采样形状&#xff0c;能够使用任意数量…...

微信小程序:模态框(弹窗)的实现

效果 wxml <!--新增&#xff08;点击按钮&#xff09;--> <image classimg src"{{add}}" bindtapadd_mode></image> <!-- 弹窗 --> <view class"modal" wx:if"{{showModal}}"><view class"modal-conten…...

uniapp交互反馈api的使用示例

官方文档链接&#xff1a;uni.showToast(OBJECT) | uni-app官网 1.uni.showToast({}) 显示消息提示框。 常用属性&#xff1a; title:页面提示的内容 image&#xff1a;改变提示框默认的icon图标 duration&#xff1a;提示框在页面显示多少秒才让它消失 添加了image属性后。 注…...

XUbuntu22.04之HDMI显示器设置竖屏(一百九十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…...

如何用 Cargo 管理 Rust 工程系列 甲

以下内容为本人的学习笔记&#xff0c;如需要转载&#xff0c;请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/ceMTUzRjDoiLwjn_KfZSrg 这几年 Rust 可谓是炙手可热的新兴编程语言了&#xff0c;而且被投票为最受程序员喜爱的语言。它很现代&#xff0c;专门…...

Windows下ping IP+端口的方法

有两种方法&#xff1a; 1. windows 开通 telnet 参考&#xff1a; https://zhuanlan.zhihu.com/p/570982111 2. 安装插件 参考&#xff1a;Windows下ping IP端口的方法 推荐使用第二种。...

【python】os.getcwd()函数详解和示例

os.getcwd() 是 Python 的一个内建函数&#xff0c;用于获取当前工作目录的路径。这个函数属于 os 模块&#xff0c;需要导入这个模块才能使用它。 import os data_rootos.path.abspath(os.path.join(os.getcwd(),"../.."))# get data root path data_root1os.path.…...

Linux(二十一)——virtualenv安装成功之后,依然提示未找到命令(-bash: virtualenv: 未找到命令)

Linux(二十一)——virtualenv安装成功之后&#xff0c;依然提示未找到命令&#xff08;-bash: virtualenv: 未找到命令&#xff09; 解决办法&#xff1a; 创建软连接 ln -s /usr/local/python3/bin/virtualenv /usr/bin/virtualenv...

RNN介绍及Pytorch源码解析

介绍一下RNN模型的结构以及源码&#xff0c;用作自己复习的材料。 RNN模型所对应的源码在&#xff1a;\PyTorch\Lib\site-packages\torch\nn\modules\RNN.py文件中。 RNN的模型图如下&#xff1a; 源码注释中写道&#xff0c;RNN的数学公式&#xff1a; 表示在时刻的隐藏状态…...

Qt 文字描边(基础篇)

项目中有时需要文字描边的功能 1.基础的绘制文字 使用drawtext处理 void MainWindow::paintEvent(QPaintEvent *event) {QPainter painter(this);painter.setRenderHint(QPainter::Antialiasing, true);painter.setRenderHint(QPainter::SmoothPixmapTransform, true);painte…...

.360勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复

导言&#xff1a; 在数字化时代&#xff0c;.360勒索病毒如影随形&#xff0c;威胁个人和组织的数据安全。本文将深入介绍.360病毒的特征、威胁&#xff0c;以及如何有效地恢复被加密的数据文件&#xff0c;同时提供预防措施&#xff0c;助您更好地保护数字资产。如不幸感染这…...

Nginx(四层+七层代理)+Tomcat实现负载均衡、动静分离

一、Tomcat多实例部署 具体步骤请看我之前的博客 写文章-CSDN创作中心https://mp.csdn.net/mp_blog/creation/editor/134956765?spm1001.2014.3001.9457 1.1 访问测试多实例的部署 1.2 分别在三个tomcat服务上部署jsp的动态页面 mkdir /usr/local/tomcat/webapps/test vim …...

Git系列一:git的下载与安装

Git 是一个开源的分布式版本控制系统&#xff0c;简单来说就是团队协作开发的一个工具。 进入正文&#xff1a; Git的下载&#xff1a;这里不推荐用官方网站下载&#xff0c;太慢&#xff0c;用国内的镜像源&#xff1a;CNPM Binaries Mirror 点进去之后选择&#xff1a; 这…...

ESP32复刻诺基亚功能机:嵌入式手持终端全栈设计

1. 项目概述复刻经典功能机并非怀旧情怀的简单投射&#xff0c;而是一次面向嵌入式系统工程实践的完整闭环训练。本项目以Nokia 1110为物理载体与交互范式蓝本&#xff0c;采用ESP32-WROOM-32作为主控平台&#xff0c;构建了一台具备现代嵌入式能力的微型手持终端。其设计目标明…...

快速原型开发:用快马平台十分钟搭建技能学习应用界面

最近在做一个技能学习平台的原型&#xff0c;时间紧任务重&#xff0c;需要一个能快速验证想法、展示核心交互的界面。传统的开发流程&#xff0c;从环境搭建到页面绘制&#xff0c;再到功能联调&#xff0c;没个一两天根本下不来。这次我尝试用了一种新思路——借助AI辅助的在…...

【码道初阶-Hot100】 LeetCode 49. 字母异位词分组:从排序哈希到分组映射,彻底讲透为什么排序后可以作为同一组的标识

LeetCode 49. 字母异位词分组&#xff1a;从排序哈希到分组映射&#xff0c;彻底讲透为什么排序后可以作为同一组的标识 摘要 LeetCode 49. 字母异位词分组&#xff08;Group Anagrams&#xff09; 是哈希表题目中的经典代表。题目本身不算复杂&#xff0c;但它非常适合训练一…...

当GitHub界面成为协作障碍:如何用87KB插件实现全界面中文改造

当GitHub界面成为协作障碍&#xff1a;如何用87KB插件实现全界面中文改造 【免费下载链接】github-chinese GitHub 汉化插件&#xff0c;GitHub 中文化界面。 (GitHub Translation To Chinese) 项目地址: https://gitcode.com/gh_mirrors/gi/github-chinese 问题&#x…...

影墨·今颜实战案例:单日生成100+张小红书爆款人像内容流程

影墨今颜实战案例&#xff1a;单日生成100张小红书爆款人像内容流程 1. 项目背景与需求分析 在内容为王的时代&#xff0c;小红书平台对高质量人像内容的需求持续增长。传统摄影制作流程需要模特、化妆师、摄影师、后期团队协同工作&#xff0c;单组照片成本数千元&#xff0…...

全网首份「龙虾」安全部署指南来了!360出品

近日&#xff0c;开源AI智能体OpenClaw&#xff08;网友戏称为“赛博龙虾”&#xff09;迅速走红网络。随着应用热度持续攀升&#xff0c;多地政府相继出台专项扶持政策&#xff0c;从企业到个人开发者&#xff0c;部署OpenClaw正成为新的趋势。该工具通过整合通信软件与大语言…...

PCL2-CE社区版启动器:让Minecraft游戏体验不再受限于传统启动工具

PCL2-CE社区版启动器&#xff1a;让Minecraft游戏体验不再受限于传统启动工具 【免费下载链接】PCL-CE PCL2 社区版&#xff0c;可体验上游暂未合并的功能 项目地址: https://gitcode.com/gh_mirrors/pc/PCL-CE PCL2-CE社区版启动器是一款开源的Minecraft启动工具&#…...

Flux Sea Studio 数据库集成实践:使用MySQL管理海量生成作品与元数据

Flux Sea Studio 数据库集成实践&#xff1a;使用MySQL管理海量生成作品与元数据 你有没有遇到过这样的烦恼&#xff1f;用Flux Sea Studio生成了一大堆精美的海景图片&#xff0c;时间一长&#xff0c;电脑里全是“海景_001.png”、“海景_002.png”这样的文件。想找一张特定…...

解锁知识壁垒:突破限制高效获取文档的创新方案

解锁知识壁垒&#xff1a;突破限制高效获取文档的创新方案 【免费下载链接】baidu-wenku fetch the document for free 项目地址: https://gitcode.com/gh_mirrors/ba/baidu-wenku 在信息爆炸的数字时代&#xff0c;知识获取的效率直接决定学习与工作的质量。当重要文档…...