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.mzph.cn/web/24428.shtml

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

相关文章

html+CSS+js部分基础运用17

在图书列表中&#xff0c;为书名“零基础学JavaScript”和“HTML5CSS3精彩编程200例”添加颜色。&#xff08;请用class或style属性实现&#xff09;&#xff0c;效果如下图1所示&#xff1a; 图1 图书列表 Class和style的综合应用。&#xff08;1&#xff09;应用class的对象、…

命令行打包最简单的android项目从零开始到最终apk文件

准备好需要的工具 AndroidDevTools - Android开发工具 Android SDK下载 Android Studio下载 Gradle下载 SDK Tools下载 jdk的链接我就不发出来,自己选择,我接下来用的是8版本的jdk和android10的sdk sdk的安装和环境变量的配置 sdk tool压缩包打开后是这样子,打开sdk mana…

高防CDN是如何应对DDoS和CC攻击的

高防CDN&#xff08;内容分发网络&#xff09;主要通过分布式的网络架构来帮助网站抵御DDoS&#xff08;分布式拒绝服务&#xff09;和CC&#xff08;挑战碰撞&#xff09;攻击。 下面是高防CDN如何应对这些攻击的详细描述&#xff1a; 1. DDoS攻击防护 DDoS攻击通过大量的恶…

【动态规划-BM71 最长上升子序列(一)】

题目 BM71 最长上升子序列(一) 分析 dp[i] 考虑到下标i&#xff0c;其组成的最长上升子序列长度 可以用动态规划的原因&#xff1a; 到i的结果可以由到j &#xff08;j<i) 的结果推出&#xff0c;只需要判断下标j对应的数字是否比下标i 对应的字母小即可 注意&#xf…

vs2013 - 打包

文章目录 vs2013 - 打包概述installshield2013limitededitionMicrosoft Visual Studio 2013 Installer Projects选择哪种来打包? 笔记VS2013打包和VS2019打包的区别打包工程选择view打包工程中单击工程名称节点&#xff0c;就可以在属性框中看到要改的属性(e.g. 默认是x86, 要…

Linux安装RocketMQ教程【带图文命令巨详细】

巨详细Linux安装Nacos教程RocketMQ教程 1、检查残留版本2、上传压缩包至服务器2.1压缩包获取2.2创建相关目录 3、安装RocketMQ4、配置RocketMQ4.1修改runserver.sh和runbroker.sh启动脚本4.2新增broker.conf配置信息4.3启动关闭rocketmq4.4配置开机自启动&#xff08;扩展项&am…

AI Agentic Design Patterns with AutoGen(下):工具使用、代码编写、多代理群聊

文章目录 四、工具使用: 国际象棋游戏4.1 准备工具4.2 创建两个棋手代理和棋盘代理4.3 注册工具到代理4.4 创建对话流程&#xff0c;开始对话4.5 增加趣味性&#xff1a;加入闲聊 五、代码编写&#xff1a;财务分析5.1导入和配置代码执行器5.2 创建 代码执行/编写 代理5.3 定义…

win10重装系统?电脑系统重装一键清晰,干货分享!

在电脑的使用过程中&#xff0c;由于各种原因&#xff0c;我们可能会遇到系统崩溃、运行缓慢或者出现各种难以解决的问题。这时&#xff0c;重装系统往往是一个有效的解决方案。今天&#xff0c;我们就来详细介绍一下如何在Win10环境下进行系统的重装&#xff0c;帮助大家轻松解…

【三十三】springboot+序列化实现返回值脱敏和返回值字符串时间格式化问题

互相交流入口地址 整体目录&#xff1a; 【一】springboot整合swagger 【二】springboot整合自定义swagger 【三】springboot整合token 【四】springboot整合mybatis-plus 【五】springboot整合mybatis-plus 【六】springboot整合redis 【七】springboot整合AOP实现日志操作 【…

Java学习-JDBC(一)

JDBC 概念 JDBC(Java Database Connectivity)Java数据库连接JDBC提供了一组独立于任何数据库管理系统的APIJava提供接口规范&#xff0c;由各个数据库厂商提供接口的实现&#xff0c;厂商提供的实现类封装成jar文件&#xff0c;也就是我们俗称的数据库驱动jar包JDBC充分体现了…

什么是虚拟局域网?快解析有哪些的虚拟化应用功能?

什么是虚拟局域网&#xff1f;从字面上理解就是不是真实存在的局域网。虚拟局域网是将网络用户和设备集中在一起&#xff0c;从而可以对不同地域和商业的需要有一定的支持性。虚拟局域网有它的优点&#xff0c;在使用过程中可以为企业提供更安全、更稳定、更灵活的服务保障体系…

记录jenkins pipeline ,git+maven+sonarqube+打包镜像上传到阿里云镜像仓库

1、阶段视图&#xff1a; 2、准备工作 所需工具与插件 jdk&#xff1a;可以存在多版本 maven&#xff1a;可以存在多版本 sonar-scanner 凭证令牌 gitlab&#xff1a;credentialsId sonarqube:配置在sonarqube208服务中 3、jenkinsfile pipeline {agent anystages {stage(从…

Mac怎么读取内存卡 Mac如何格式化内存卡

在今天的数字化时代&#xff0c;内存卡已经成为了我们生活中不可或缺的一部分。对于Mac电脑用户而言&#xff0c;正确地读取和管理内存卡中的数据至关重要。下面我们来看看Mac怎么读取内存卡&#xff0c;Mac如何格式化内存卡的相关内容。 一、Mac怎么读取内存卡 苹果电脑在读…

Python中__面向对象__学习 (上)

目录 一、类和对象 1.类的定义 2.根据对象创建类 二、构造和析构 1.构造方法 &#xff08;1&#xff09;不带参数的构造方法 &#xff08;2&#xff09;带参数的构造方法 2.析构方法 三、重载 1.定制对象的字符串形式 &#xff08;1&#xff09;只重载__str__方法 …

QT Udp广播实现设备发现

测试环境 本文选用pc1作为客户端&#xff0c;pc2&#xff0c;以及一台虚拟机作为服务端。 pc1,pc2(客户端&#xff09;: 虚拟机&#xff08;服务端)&#xff1a; 客户端 原理&#xff1a;客户端通过发送广播消息信息到ip:255.255.255.255(QHostAddress::Broadcast),局域网…

了解Java内存模型(Java Memory Model, JMM)

了解Java内存模型&#xff08;Java Memory Model, JMM&#xff09; Java内存模型&#xff08;Java Memory Model, JMM&#xff09;是Java语言规范中规定的一组规则&#xff0c;定义了多线程程序中变量&#xff08;包括实例字段、静态字段和数组元素&#xff09;的访问方式。JM…

git 大文件上传失败 Please remove the file from history and try again.

根据提示执行命令 --- 查找到当前文件 git rev-list --objects --all | grep b24e74b34e7d482e2bc687e017c8ab28cd1d24b6git filter-branch --tree-filter rm -f 文件名 --tag-name-filter cat -- --all git push origin --tags --force git push origin --all --force

Fort Firewall防火墙工具v3.12.13

软件介绍 Fort Firewall是一款开源系统的免费防火墙&#xff0c;体积小巧、占用空间不大&#xff0c;可以为用户的电脑起到保护作用&#xff0c;该软件可以控制程序访问网络&#xff0c;控制用户的电脑网速&#xff0c;用户可以更轻松便捷的进行网络安全防护&#xff0c;保护系…

计算机网络 —— 网络层(IP数据报)

计算机网络 —— 网络层&#xff08;IP数据报&#xff09; 网络层要满足的功能IP数据报IP数据报格式IP数据报首部格式数据部分 IP数据报分片 我们今天进入网络层的学习。 网络层要满足的功能 网络层作为OSI模型中的第三层&#xff0c;是计算机网络体系结构的关键组成部分&…

实验六、IPv4 地址的子网划分,第 2 部分《计算机网络》

你有没有发现&#xff0c;困的时候真的清醒不了。 目录 一、实验目的 二、实验内容 三、实验小结 一、实验目的 完成本练习之后&#xff0c;您应该能够确定给定 IP 地址和子网掩码的子网信息。 知道 IP 地址、网络掩码和子网掩码后&#xff0c;您应该能够确定有关该 IP 地…