netty+springboot+vue聊天室(需要了解netty)

先看看这个使用websocket实现的聊天室,因为前端是使用websocket,和下面的demo的前端差不多就不解释实现原理,所以建议还是看看(要是会websocket的大佬请忽略)

springboot+websocket+vue聊天室

目录

  • 一、实现内容
  • 二、代码实现
    • 1.后端
    • 2.前端
    • 源码

一、实现内容

  1. http://localhost:8080/netty?uid=1

在这里插入图片描述

  1. http://localhost:8080/netty?uid=2

在这里插入图片描述

  1. http://localhost:8080/netty?uid=3

在这里插入图片描述

二、代码实现

1.后端

在这里插入图片描述

  • netty服务端
@Component("NettyChatServer")
public class NettyChatServer {//主线程池:处理连接请求private static NioEventLoopGroup boss = new NioEventLoopGroup(2);//工作线程池:接收主线程发过来的任务,完成实际的工作private static NioEventLoopGroup worker = new NioEventLoopGroup(6);//创建一个服务器端的启动对象ServerBootstrap serverBootstrap=null;@Autowired//自定义handler、处理客户端发送过来的消息进行转发等逻辑MyTextWebSocketFrameHandler myTextWebSocketFrameHandler = new MyTextWebSocketFrameHandler();public void run() {serverBootstrap= new ServerBootstrap().group(boss, worker).channel(NioServerSocketChannel.class)//连接的最大线程数.option(ChannelOption.SO_BACKLOG, 128)//长连接,心跳机制.childOption(ChannelOption.SO_KEEPALIVE, true).childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {//因为基于http协议,使用http的编码和解码器nioSocketChannel.pipeline().addLast(new HttpServerCodec());//是以块方式写,添加ChunkedWriteHandler处理器nioSocketChannel.pipeline().addLast(new ChunkedWriteHandler());/*** 说明*   1. http数据在传输过程中是分段, HttpObjectAggregator ,就是可以将多个段聚合*   2. 这就就是为什么,当浏览器发送大量数据时,就会发出多次http请求*/nioSocketChannel.pipeline().addLast(new HttpObjectAggregator(8192));/*** 说明*    1. 对应websocket ,它的数据是以帧(frame,基于TCP)形式传递*    2. 可以看到WebSocketFrame下面有六个子类*    3. 浏览器请求时 ws://localhost:8888/wechat 表示请求的uri*    4. WebSocketServerProtocolHandler 核心功能是将 http协议升级为 ws协议 , 保持长连接*    5. 是通过一个 状态码 101*/nioSocketChannel.pipeline().addLast(new WebSocketServerProtocolHandler("/wechat"));//自定义handler、处理客户端发送过来的消息进行转发等逻辑nioSocketChannel.pipeline().addLast(myTextWebSocketFrameHandler);}});//server监听接口try {ChannelFuture channelfuture = serverBootstrap.bind(8888).sync();// 添加注册监听,监控关心的事件,当异步结束后就会回调监听逻辑channelfuture.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture channelFuture) throws Exception {if (channelFuture.isSuccess()){System.out.println("监听端口8888成功");}else{System.out.println("监听端口8888失败");}}});//关闭通道和关闭连接池(不是真正关闭,只是设置为关闭状态)channelfuture.channel().closeFuture().sync();} catch (InterruptedException e) {e.printStackTrace();}finally {//EventLoop停止接收任务、任务结束完毕停掉线程池boss.shutdownGracefully();worker.shutdownGracefully();}}
}
  • 自定义handler,处理业务逻辑
@Component
@ChannelHandler.Sharable
public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {//记录客户端和channel的绑定private static Map<Integer, Channel> channelMap=new ConcurrentHashMap<Integer, Channel>();@Overrideprotected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {//将发过来的内容进行解析成 自定义的MessageMessage message = JSON.parseObject(textWebSocketFrame.text(), Message.class);//绑定对应用户和channelif (!channelMap.containsKey(message.getFromUid())){channelMap.put(message.getFromUid(),channelHandlerContext.channel());}else{channelMap.replace(message.getFromUid(),channelHandlerContext.channel());}//发送给对应的客户端对应的channelif(channelMap.containsKey(message.getToUid())){//因为连接成功会发送一次注册消息(注册消息message.getToUid()== message.getFromUid())if(message.getToUid()!= message.getFromUid()){//不能重用之前的textWebSocketFramechannelMap.get(message.getToUid()).writeAndFlush(new TextWebSocketFrame(textWebSocketFrame.text()));}}else{//该用户暂未在线,先将消息存进数据库(这里没实现)System.out.println("该用户暂未在线,先将消息存进数据库");}//计数-1(计数法来控制回收内存)channelHandlerContext.fireChannelRead(textWebSocketFrame.retain());}
}
  • netty整合到springboot
@SpringBootApplication
public class OnlinechatApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(OnlinechatApplication.class, args);NettyChatServer nettyChatServer = (NettyChatServer)context.getBean("NettyChatServer");nettyChatServer.run();}}

2.前端

  • 和weocket的demo区别发送的ws协议uri不同,ws://localhost:8888/wechat
  • 还有就是,websocket建立连接之后就先发送一次绑定消息到服务器端(将用户和channel的关系对应起来)
<template><div class="bg"><el-container class="wechat"><el-aside width="35%" style="border-right: 1px solid #fff"><!-- 自己 --><div class="item"><el-avatar:size="46":src="user.avatarUrl"style="float: left; margin-left: 2px"></el-avatar><div class="name">{{ user.nickname}}<el-tag style="margin-left: 5px" type="success">本人</el-tag></div></div><!-- 在线用户 --><divclass="item"v-for="(item1, index) in userlist":key="item1.uid"@click="selectUser(index)"><!-- 新数消息 --><el-badge:value="new_message_num[index]":max="99":hidden="!new_message_num[index] > 0"style="float: left; margin-left: 2px"><el-avatar :size="46" :src="item1.avatarUrl"></el-avatar></el-badge><div class="name">{{ item1.nickname }}</div></div></el-aside><el-main><el-container class="wechat_right"><!-- 右边顶部 --><el-header class="header">{{anotherUser != null && anotherUser.uid > 0? anotherUser.nickname: "未选择聊天对象"}}</el-header><!-- 聊天内容 --><el-main class="showChat"><div v-for="item2 in messageList[index]" :key="item2.msg"><!-- 对方发的 --><div class="leftBox" v-if="item2.FromUid == anotherUser.uid"><span style="font-size: 4px">{{ item2.time }}</span>{{ item2.msg }}</div><div class="myBr" v-if="item2.FromUid == anotherUser.uid"></div><!-- 自己发的 --><div class="rightBox" v-if="item2.FromUid == user.uid"><span style="font-size: 4px">{{ item2.time }}</span>{{ item2.msg }}</div><div class="myBr" v-if="item2.FromUid == user.uid"></div></div></el-main><!-- 输入框 --><el-main class="inputValue"><textarea v-model="inputValue" id="chat" cols="26" rows="5"></textarea><!-- 发送按钮 --><el-buttonv-if="anotherUser != null && anotherUser.uid > 0 && inputValue != ''"type="success"size="mini"roundid="send"@click="senMessage">发送</el-button></el-main></el-container></el-main></el-container></div>
</template><script>
export default {data() {return {//自己user: {},//要私信的人anotherUser: {},//在线的用户userlist: [],//要私信的人在userlist的索引位置index: 0,//消息队列集合 [本人和第一个人之间的消息集合、本人和第二个人之间的消息集合、...]messageList: [],//新消息个数集合new_message_num: [],//将要发送的内容inputValue: "",//websocketwebsocket: null,};},methods: {//获取自己被分配的信息getYourInfo(uid) {let params = new URLSearchParams();this.$axios.post("/user/getYourInfo/" + uid, params).then((res) => {this.user = res.data.data;if (res.data.code == 200) {//获取在线用户this.getUserList();}}).catch((err) => {console.error(err);});},//获取在线用户getUserList() {let params = new URLSearchParams();this.$axios.post("/user/getUserList", params).then((res) => {this.userlist = res.data.data.filter(//去掉自己(user) => user.uid !== this.user.uid);//填充消息数据 messagelist:[[]、[]...]  并且将新消息队列置为0for (let i = 0; i < this.userlist.length; i++) {this.messageList.push([]);this.new_message_num.push(0);}//将当前的客户端和服务端进行连接,并定义接收到消息的处理逻辑this.init(this.user.uid);}).catch((err) => {console.error(err);});},//选择聊天对象selectUser(index) {this.anotherUser = this.userlist[index];this.index = index;//将新消息置为0this.new_message_num[index] = 0;},//将当前的客户端和服务端进行连接,并定义接收到消息的处理逻辑init(uid) {var self = this;if (typeof WebSocket == "undefined") {console.log("您的浏览器不支持WebSocket");return;}//清除之前的记录if (this.websocket != null) {this.websocket.close();this.websocket = null;}//-----------------------连接服务器-----------------------let socketUrl = "ws://localhost:8888/wechat";//开启WebSocket 连接this.websocket = new WebSocket(socketUrl);//指定连接成功后的回调函数this.websocket.onopen = function () {console.log("websocket已打开");//发送一次注册消息(使后端先绑定channel和用户的关系,以至于找到对应的channel转发消息)let message = {FromUid: uid,ToUid: uid,msg: uid + "的绑定消息",time: new Date().toLocaleTimeString(),};self.websocket.send(JSON.stringify(message));};//指定连接失败后的回调函数this.websocket.onerror = function () {console.log("websocket发生了错误");};//指定当从服务器接受到信息时的回调函数this.websocket.onmessage = function (msg) {//消息体例如{"FromUid":1,"ToUid":2,"msg":"你好","time":"00:07:03"} => message对象let data = JSON.parse(msg.data);//添加到对应的消息集合中let index = data.FromUid > uid ? data.FromUid - 2 : data.FromUid - 1;self.messageList[index].push(data);//新消息数+1self.new_message_num[index]++;};//指定连接关闭后的回调函数this.websocket.onclose = function () {console.log("websocket已关闭");};},//发送信息senMessage() {//消息体例如{"FromUid":1,"ToUid":2,"msg":"你好","time":"00:07:03"}let message = {FromUid: this.user.uid,ToUid: this.anotherUser.uid,msg: this.inputValue,time: new Date().toLocaleTimeString(),};//将消息插进消息队列,显示在前端this.messageList[this.index].push(message);//将消息发送至服务器端再转发到对应的用户this.websocket.send(JSON.stringify(message));//清空一下输入框内容this.inputValue = "";},},created() {let uid = this.$route.query.uid;if (uid != undefined) {//获取被分配的用户信息this.getYourInfo(uid);}},
};
</script><style>
/*改变滚动条 */
::-webkit-scrollbar {width: 3px;border-radius: 4px;
}::-webkit-scrollbar-track {background-color: inherit;-webkit-border-radius: 4px;-moz-border-radius: 4px;border-radius: 4px;
}::-webkit-scrollbar-thumb {background-color: #c3c9cd;-webkit-border-radius: 4px;-moz-border-radius: 4px;border-radius: 4px;
}
.bg {background: url("https://s1.ax1x.com/2022/06/12/Xgr9u6.jpg") no-repeat top;background-size: cover;background-attachment: fixed;width: 100%;height: 100%;position: fixed;top: 0;left: 0;right: 0;bottom: 0;
}
.wechat {width: 60%;height: 88%;margin: 3% auto;border-radius: 20px;background-color: rgba(245, 237, 237, 0.3);
}
/*聊天框左侧 */
.item {position: relative;width: 94%;height: 50px;margin-bottom: 3%;border-bottom: 1px solid #fff;
}
.item .name {line-height: 50px;float: left;margin-left: 10px;
}
/*聊天框右侧 */.wechat_right {position: relative;width: 100%;height: 100%;
}
.header {text-align: left;height: 50px !important;
}
.showChat {width: 100%;height: 65%;
}
.inputValue {position: relative;margin: 0;padding: 0;width: 100%;height: 50%;
}
.inputValue #chat {font-size: 18px;width: 96%;height: 94%;border-radius: 20px;resize: none;background-color: rgba(245, 237, 237, 0.3);
}
#send {position: absolute;bottom: 12%;right: 6%;
}
/*展示区 */
.leftBox {float: left;max-width: 60%;padding: 8px;position: relative;font-size: 18px;border-radius: 12px;background-color: rgba(40, 208, 250, 0.76);
}
.rightBox {float: right;max-width: 60%;padding: 8px;font-size: 18px;border-radius: 12px;position: relative;background-color: rgba(101, 240, 21, 0.945);
}
.myBr {float: left;width: 100%;height: 20px;
}
.leftBox > span {left: 3px;width: 120px;position: absolute;top: -16px;
}
.rightBox > span {width: 120px;position: absolute;right: 3px;top: -16px;
}
</style>

源码

源代码

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

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

相关文章

k8s-部署对象存储minio

环境信息 minio版本 :最新 k8s 版本1.22 使用nfs作为共享存储 一.单节点安装包部署 脚本部署&#xff0c;一键部署&#xff0c;单节点应用于数据量小&#xff0c;一些缓存存储&#xff0c;比如gitlab-runner的产物数据&#xff0c;maven的打包依赖数据 #!/bin/bash# 步骤…

【AI基础】第三步:纯天然保姆喂饭级-安装并运行chatglm2-6b

chatglm2构建时使用了RUST&#xff0c;所以在安装chatglm2之前&#xff0c;先安装RUST。 此系列文章列表&#xff1a; 【AI基础】第一步&#xff1a;安装python开发环境-windows篇_下载安装ai环境python-CSDN博客 【AI基础】第一步&#xff1a;安装python开发环境-conda篇_mini…

【云原生】Docker Compose 使用详解

目录 一、前言 二、Docker Compose 介绍 2.1 Docker Compose概述 2.2 Docker Compose特点 2.3 Docker Compose使用场景 三、Docker Compose 搭建 3.1 安装docker环境 3.2 Docker Compose安装方式一 3.2.1 下载最新版/如果不是最新可替换最新版本 3.2.2 设置权限 3.2.…

c++【入门】正多边形每个内角的度数

限制 时间限制 : 1 秒 内存限制 : 128 MB 题目 根据多边形内角和定理&#xff0c;正多边形内角和等于&#xff1a;&#xff08;n &#xff0d; 2&#xff09;180(n大于等于3且n为整数&#xff09;&#xff08;如下图所示是三角形、四边形、五边形、六边形的形状&#xff09…

ROS学习记录:栅格地图格式

一、机器人导航所使用的地图数据&#xff0c;就是ROS导航软件包里的map_server节点在话题 /map 中发布的消息数据&#xff0c;消息类型是nav_msgs消息包中的OccupancyGrid&#xff0c;它的中文意思的占据栅格&#xff0c;是一种正方形小格子组成的地图。 二、对障碍物进行俯视&…

嵌入式 Linux LED 驱动开发实验学习

I.MX6U-ALPHA 开发板上的 LED 连接到 I.MX6ULL 的 GPIO1_IO03 这个引脚上&#xff0c;进行这个驱动开发实验之前&#xff0c;需要了解下地址映射。 地址映射 MMU 全称叫做 MemoryManage Unit&#xff0c;也就是内存管理单元。在老版本的 Linux 中要求处理器必须有 MMU&#x…

normalizing flows vs 直方图规定化

normalizing flows名字的由来 The base density P ( z ) P(z) P(z) is usually defined as a multivariate standard normal (i.e., with mean zero and identity covariance). Hence, the effect of each subsequent inverse layer is to gradually move or “flow” the da…

mqtt-emqx:paho.mqttv5的简单例子

# 安装emqx 请参考【https://blog.csdn.net/chenhz2284/article/details/139551293?spm1001.2014.3001.5502】 # 下面是示例代码 【pom.xml】 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</…

VL830 USB4 最高支持40Gbps芯片功能阐述以及原理图分享

前文斥巨资拆了一个扩展坞供大家参考。其中核心即为本文要说的这个VL830,USB4的HUB芯片。 拆解报告传送门&#xff1a;USB4 Gen3x2 最高40Gbps传输速率的HUB扩展坞拆解分析 OK&#xff0c;闲话少叙。直接进入主题&#xff0c;我就直接翻译规格书了。 VL830是一款USB4端点设备…

Mac保姆级配置jdk环境

1.找到下载的jdk环境 通常是这个。留作备用 /Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/bin 然后新开一个终端下执行以下命令 sudo vim ~/.bash_profile 进入编辑模式后 按 i 开始添加内容结束编辑模式按 ESC结束后保存输入 :wq!不保存输入 :q! 注意…

pytorch之猫狗识别项目

1. 导入资源包 资源包&#xff1a; import torchvision&#xff1a;PyTorch 提供的视觉库&#xff0c;包含了常用的计算机视觉模型架构、数据集以及图像转换工具。 from torchvision import datasets, models&#xff1a;导入 torchvision 中的 datasets 和 models 模块&#…

RPA影刀 | 设置当前时间

1. 新建流程 2. 创建指令 指令1&#xff1a;获取当前日期时间 指令3&#xff1a;日期时间转文本 %Y&#xff1a;年 %m&#xff1a;月 %d&#xff1a;日期 其他字符自由添加。 常用格式如下&#xff1a; %Y年%m月%d日%Y-%m-%d%Y_%m_%d%Y%m%d 3. 运行流程

fastadmin/thinkPHPQueue消息队列详细教程

thinkphp-queue 是thinkphp 官方提供的一个消息队列服务,它支持消息队列的一些基本特性: 消息的发布,获取,执行,删除,重发,失败处理,延迟执行,超时控制等队列的多队列, 内存限制 ,启动,停止,守护等消息队列可降级为同步执行1、通过composer安装thinkPHP消息队列 …

spring源码解析-(2)Bean的包扫描

包扫描的过程 测试代码&#xff1a; // 扫描指定包下的所有类 BeanDefinitionRegistry registry new SimpleBeanDefinitionRegistry(); // 扫描指定包下的所有类 ClassPathBeanDefinitionScanner scanner new ClassPathBeanDefinitionScanner(registry); scanner.scan(&quo…

Java中volatile关键字

保证了不同线程对这个变量进行操作时的可见性&#xff0c;即一个线程修改了某个变量的值&#xff0c;这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。 1.volatile的可见性 一个典型的例子&#xff1a;永不停止的循环。 public class Forever…

kettle从入门到精通 第六十六课 ETL之kettle kettle阻塞教程,轻松获取最后一行数据,so easy

场景&#xff1a;ETL沟通交流群内有小伙伴反馈&#xff0c;如何在同步一批数据完成之后记录下同步结果呢&#xff1f;或者是调用后续步骤、存储过程、三方接口等。 解决&#xff1a;使用步骤Blocking step进行阻塞处理即可。 1、下面的demo演示从表t1同步数据至表t2&#xff…

[每周一更]-(第100期):介绍 goctl自动生成代码

​ 在自己组件库中&#xff0c;由于部分设计会存在重复引用各个模板的文件&#xff0c;并且基础架构中需要基础模块内容&#xff0c;就想到自动生成代码模板&#xff0c;刚好之前有使用过goctl&#xff0c;以下就简单描述下gozero中goctl场景和逻辑&#xff0c;后续自己借鉴将自…

TiDB-从0到1-配置篇

TiDB从0到1系列 TiDB-从0到1-体系结构TiDB-从0到1-分布式存储TiDB-从0到1-分布式事务TiDB-从0到1-MVCCTiDB-从0到1-部署篇TiDB-从0到1-配置篇 一、系统配置 TiDB的配置分为系统配置和集群配置两种。 其中系统配置对应TiDB Server&#xff08;不包含TiKV和PD的参数&#xff0…

在Windows11系统上搭建SFTP服务器

利用OpenSSH搭建SFTP服务器 下载安装部署OpenSSH创建一个测试账户测试链接为SFTP用户配置根目录下载安装部署OpenSSH 参考链接 部署完启动服务要使用管理员模式。 net start sshd创建一个测试账户 使用PC的微软账户是访问不了SFTP的。 需要使用被微软账户覆盖掉的系统账户和…

【python报错】list indices must be integers or slices, not tuple

【Python报错】list indices must be integers or slices, not tuple 在Python中&#xff0c;列表&#xff08;list&#xff09;是一种常用的数据结构&#xff0c;用于存储一系列的元素。当你尝试使用不支持的索引类型访问列表元素时&#xff0c;会遇到list indices must be in…