Java实现在线沟通功能
文章目录
- 1、介绍 和 特点
- 2、整合SpringBoot
- 2.1、导入依赖
- 2.2、websocket 配置类
- 2.3、消息处理类
- 2.4、启动服务
- 2.5、前端代码:张三
- 2.6、前端代码:李四
- 3、效果
- 4、小结
1、介绍 和 特点
t-io是基于JVM的网络编程框架,和netty属同类,所以netty能做的t-io都能做,考虑到t-io是从项目抽象出来的框架,所以t-io提供了更多的和业务相关的API,大体上t-io具有如下特点和能力
- 内置完备的监控和流控能力
- 内置半包粘包处理
- 一骑绝尘的资源管理能力
- 内置心跳检查和心跳发送能力
- 内置IP拉黑
- 一流性能和稳定性(第三方权威平台TFB提供性能测试和稳定性服务)
- 极其稳定的表现(很多用户还是停在t-io 1.x版本,就是因为太过稳定,不想变动)
- 内置慢攻击防御
- 唯一一个内置异步发送、阻塞发送、同步发送的网络框架
- 唯一内置集群分发消息的能力
- 独创的多端口资源共享能力(譬如一个端口是websocket协议,一个端口是私有的im协议,这两个端口的资源可以共享,这对协议适配极其有用)
- 独创协议适配转换能力(让基于websocket和基于socket的应用看起来像是同一个协议)
- 独一档的资源和业务绑定能力:绑定group、绑定userid、绑定token、绑定bsId,这些绑定几乎囊括了所有业务需求
2、整合SpringBoot
2.1、导入依赖
<!-- tio-websocket -->
<dependency><groupId>org.t-io</groupId><artifactId>tio-websocket-server</artifactId><version>3.5.9.v20200214-RELEASE</version>
</dependency>
注意:每个版本之前存在差异请查看官方文档
2.2、websocket 配置类
import com.asurplus.tio.websocket.handle.MyWsMsgHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.tio.server.ServerTioConfig;
import org.tio.websocket.server.WsServerStarter;import java.io.IOException;/*** websocket 配置类*/
@Configuration
public class WebSocketConfig {/*** 注入消息处理器*/@Autowiredprivate MyWsMsgHandler myWsMsgHandler;/*** 启动类配置** @return* @throws IOException*/@Beanpublic WsServerStarter wsServerStarter() throws IOException {// 设置处理器WsServerStarter wsServerStarter = new WsServerStarter(6789, myWsMsgHandler);// 获取到ServerTioConfigServerTioConfig serverTioConfig = wsServerStarter.getServerTioConfig();// 设置心跳超时时间,默认:1000 * 120serverTioConfig.setHeartbeatTimeout(1000 * 120);// 启动wsServerStarter.start();return wsServerStarter;}
}
这里我们注入了 WsServerStarter 的 bean,这样在 SpringBoot 启动的时候,就能启动咱们的 websocket 服务
- 注明了 websocket 的服务端口为:6789
- 消息处理类为:myWsMsgHandler,在下一步我们将会去实现这个类
- 设置了心跳的超时时间为:120秒,默认值,可以不设置
2.3、消息处理类
package com.ying.tiiochat.config;import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import org.tio.core.ChannelContext;
import org.tio.core.Tio;
import org.tio.http.common.HttpRequest;
import org.tio.http.common.HttpResponse;
import org.tio.websocket.common.WsRequest;
import org.tio.websocket.common.WsResponse;
import org.tio.websocket.server.handler.IWsMsgHandler;/*** 消息处理类*/
@Component
public class MyWsMsgHandler implements IWsMsgHandler {/*** <li>对httpResponse参数进行补充并返回,如果返回null表示不想和对方建立连接,框架会断开连接,如果返回非null,框架会把这个对象发送给对方</li>* <li>注:请不要在这个方法中向对方发送任何消息,因为这个时候握手还没完成,发消息会导致协议交互失败。</li>* <li>对于大部分业务,该方法只需要一行代码:return httpResponse;</li>** @param httpRequest* @param httpResponse* @param channelContext* @return* @throws Exception*/@Overridepublic HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {// 可以在此做一些业务逻辑,返回null表示不想连接return httpResponse;}/*** 握手成功后触发该方法** @param httpRequest* @param httpResponse* @param channelContext* @throws Exception*/@Overridepublic void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {// 拿到用户idString id = httpRequest.getParam("userId");// 绑定用户Tio.bindUser(channelContext, id);// 给用户发送消息JSONObject message = new JSONObject();message.put("msg", "连接成功...");message.put("sendName", "系统提醒");WsResponse wsResponse = WsResponse.fromText(message.toString(), "UTF-8");Tio.sendToUser(channelContext.tioConfig, id, wsResponse);}/*** <li>当收到Opcode.BINARY消息时,执行该方法。也就是说如何你的ws是基于BINARY传输的,就会走到这个方法</li>** @param wsRequest* @param bytes* @param channelContext* @return 可以是WsResponse、byte[]、ByteBuffer、String或null,如果是null,框架不会回消息* @throws Exception*/@Overridepublic Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {System.out.println("我走了onBytes");return null;}/*** 当收到Opcode.CLOSE时,执行该方法,业务层在该方法中一般不需要写什么逻辑,空着就好** @param wsRequest* @param bytes* @param channelContext* @return 可以是WsResponse、byte[]、ByteBuffer、String或null,如果是null,框架不会回消息* @throws Exception*/@Overridepublic Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {// 关闭连接Tio.remove(channelContext, "WebSocket Close");return null;}/*** <li>当收到Opcode.TEXT消息时,执行该方法。也就是说如何你的ws是基于TEXT传输的,就会走到这个方法</li>** @param wsRequest* @param text* @param channelContext* @return 可以是WsResponse、byte[]、ByteBuffer、String或null,如果是null,框架不会回消息* @throws Exception*/@Overridepublic Object onText(WsRequest wsRequest, String text, ChannelContext channelContext) throws Exception {JSONObject message = JSONObject.parseObject(text);// 接收消息的用户IDString receiver = message.getString("receiver");// 发送消息者String sendName = message.getString("sendName");// 消息String msg = message.getString("msg");// 保存聊天记录到DB等业务逻辑...WsResponse wsResponse = WsResponse.fromText(message.toString(), "UTF-8");Tio.sendToUser(channelContext.tioConfig, receiver, wsResponse);JSONObject resp = new JSONObject();resp.put("sendName", "系统提醒");resp.put("msg", "发送成功");return resp.toString();}
}
我们实现了 IWsMsgHandler 接口,并重写了该接口的 5 个方法,这 5 个方法从 发送握手包,到消息收发,到断开连接等一系列过程
2.4、启动服务

启动成功后,可以看出 tio 的打印结果,我们可以看出服务端口为我们设置的 6789,我们便可以连接测试了
2.5、前端代码:张三
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>websocket通讯</title>
</head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script type="text/javascript"><!-- 连接-->var socket;var userName;// 连接function connect() {var socketUrl = "ws://localhost:6789/?userId=" + $('#sendName').val();if (socket != null) {socket.close();socket = null;}socket = new WebSocket(socketUrl);//打开事件socket.onopen = function () {console.log("开始建立链接....")};//关闭事件socket.onclose = function () {console.log("websocket已关闭");};//发生了错误事件socket.onerror = function () {console.log("websocket发生了错误");};/*** 接收消息* @param msg*/socket.onmessage = function (msg) {console.log(msg)var json = JSON.parse(msg.data);if (msg.msg != '连接成功') {$("#msgDiv").append('<p class="other" style="color:olivedrab;">' + json.sendName + ':'+json.msg+'</p>');}};}/*** 发送消息*/function sendMessage() {var msg = $("#msg").val();if (msg == '' || msg == null) {alert("消息内容不能为空");return;}var receiver = $("#receiver").val();if (receiver == '' || receiver == null) {alert("接收人不能为空");return;}var sendName = $("#sendName").val();if (sendName == '' || sendName == null) {alert("发送人不能为空");return;}var msgObj = {"receiver": receiver,"sendName": sendName,"msg": msg};$("#msgDiv").append('<p class="user" style="color: red">' + sendName + ':'+msg+'</p>');try{socket.send(JSON.stringify(msgObj));$("#msg").val('');}catch (e) {alert("服务器内部错误");}}
</script>
<body>
用户名:<input type="text" id="sendName" value="张三">
<input type="button" value="连接" onclick="connect()" ><br>
发送者:<input type="text" id="sender" value="张三" ><br>
接受者:<input type="text" id="receiver" value="李四"><br><br>
消 息:<textarea id="msg"></textarea><br><input type="button" value="发送" onclick="sendMessage()"><br><br>消息记录:<div id="msgDiv" style="border: 1px red solid;width: 400px;height: 200px"></div>
<br></body>
</html>
2.6、前端代码:李四
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>websocket通讯</title>
</head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script type="text/javascript"><!-- 连接-->var socket;var userName;// 连接function connect() {var socketUrl = "ws://localhost:6789/?userId=" + $('#sendName').val();if (socket != null) {socket.close();socket = null;}socket = new WebSocket(socketUrl);//打开事件socket.onopen = function () {console.log("开始建立链接....")};//关闭事件socket.onclose = function () {console.log("websocket已关闭");};//发生了错误事件socket.onerror = function () {console.log("websocket发生了错误");};/*** 接收消息* @param msg*/socket.onmessage = function (msg) {console.log(msg)var json = JSON.parse(msg.data);if (msg.msg != '连接成功') {$("#msgDiv").append('<p class="other" style="color:olivedrab;">' + json.sendName + ':'+json.msg+'</p>');}};}/*** 发送消息*/function sendMessage() {var msg = $("#msg").val();if (msg == '' || msg == null) {alert("消息内容不能为空");return;}var receiver = $("#receiver").val();if (receiver == '' || receiver == null) {alert("接收人不能为空");return;}var sendName = $("#sendName").val();if (sendName == '' || sendName == null) {alert("发送人不能为空");return;}var msgObj = {"receiver": receiver,"sendName": sendName,"msg": msg};$("#msgDiv").append('<p class="user" style="color: red">' + sendName + ':'+msg+'</p>');try{socket.send(JSON.stringify(msgObj));$("#msg").val('');}catch (e) {alert("服务器内部错误");}}
</script>
<body>
用户名:<input type="text" id="sendName" value="李四">
<input type="button" value="连接" onclick="connect()" ><br>
发送者:<input type="text" id="sender" value="李四" ><br>
接受者:<input type="text" id="receiver" value="张三"><br><br>
消 息:<textarea id="msg"></textarea><br><input type="button" value="发送" onclick="sendMessage()"><br><br>消息记录:<div id="msgDiv" style="border: 1px red solid;width: 400px;height: 200px"></div>
<br></body>
</html>
记得有两个HTML文件哦

3、效果

4、小结
但在实际开发中,远没有一对一聊天这么简单,我们可能还需要聊天列表,历史聊天记录,客户列表、聊天室、多对多聊天(平台客服、商家客服、用户),包括 商品快捷方式,订单信息快捷方式,不过这些都是可以根据这个Demo的基础上做出来的。等有时间会根据这个Demo做一个具体的用例
原文:https://blog.csdn.net/weixin_46522803/article/details/126548065?spm=1001.2014.3001.5506
相关文章:
Java实现在线沟通功能
文章目录1、介绍 和 特点2、整合SpringBoot2.1、导入依赖2.2、websocket 配置类2.3、消息处理类2.4、启动服务2.5、前端代码:张三2.6、前端代码:李四3、效果4、小结1、介绍 和 特点 t-io是基于JVM的网络编程框架,和netty属同类,所…...
识别密文加密类型
离线密码破解:离线不会触发密码锁定机制不会产生大量登录失败日志引起管理员注意HASH识别工具(识别哈希类型):hash-identifierHashid yara规则匹配文件得到特定加密算法一、hash-identifierKali Linux提供工具hash-identifier来识…...
node报错
记录bug:运行 npx -p storybook/cli sb init 时报错gyp info spawn C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exegyp info spawn args [gyp info spawn args build/binding.sln,gyp info spawn args /nologo,gyp info spawn args…...
如何使用开源 BI 工具 DataEase 实现系列数据分析呢?
当我们使用可视化分析工具制作仪表板时,可能需要制作的仪表板不是单个单个的可视化大屏,而是一系列的仪表板,我们需要用它来产生一个连续性的故事,那么这个时候我们该怎么办呢?例如说总分形式,我们需要一个…...
金仓数据库安装
一、麒麟操作系统安装金仓数据库 操作系统 DISTRIB_IDKylin DISTRIB_RELEASEV10 DISTRIB_CODENAMEjuniper 按照安装文档的步骤安装,记得记住设置的数据库的用户名、密码 二、window安装连接数据库的工具软件 三、jdbc连接数据库 (1)连接工…...
深入浅出Webpack2-快速掌握webpack基本配置
深入浅出Webpack2-快速掌握webpack基本配置1.Entry1.1 context1.2 Entry类型2.Output2.1 filename2.2 path3.Module3.1配置Loader4.Resolve4.1 alias4.2 extensions4.3 modules5.Plugin6.DevServer7.其他配置项上一篇文章我们快速上手认识了一下webpack,今天这篇文章…...
如何使评论具有可操作性?取悦客户的指南
永远不要低估承认的力量。 当品牌与客户互动时,认可会带来更好的关系和更好的沟通。与买家和客户建立更多的个人联系意味着品牌需要证明他们支持他们的产品和客户。评论是利用客户分享他们的故事的那些时刻的绝佳机会。 为什么评论在 SaaS 中至关重要 在 B2B 软件的…...
一文带你彻底搞懂Nginx反向代理
一文带你彻底搞懂Nginx反向代理一、什么是反向代理1.1 正向代理1.2 反向代理1.3 总结二、配置反向代理2.1 准备 Tomcat2.2 配置 Nginx一、什么是反向代理 1.1 正向代理 举一个通俗的例子,因为众所周知的原因,我们无法访问谷歌,但是因为某些…...
手写SpringBoot的starter
自定义SpringBoot的starter 引言 starter命名格式: 官方的 starter 的命名格式为 spring-boot-starter-{xxxx} 比如spring-boot-starter-activemq 第三方我们自己的命名格式为 {xxxx}-spring-boot-starter。比如mybatis-spring-boot-starter。 如果我们忽略这种约定…...
pytorch1.2.0+python3.6
一、说明 pytorch1.2.0python3.6CUDA10.0cudnn7.4.1.5 二、步骤 在conda中创建一个新的虚拟环境 查看一下自己的所有环境 激活虚拟环境 conda activate torch1.2.0 关于cuda和cudnn 1、查看自己电脑系统是10.2版本 http://链接:https://pan.baidu.com/s/1v5cN6…...
WindowsPowerShell 停止、启动、暂停和重启服务、卸载服务
PowerShell 停止、启动、暂停和重启服务、卸载服务 PowerShell 停止、启动、暂停和重启服务 官文 powershell卸载服务 官文 目录PowerShell 停止、启动、暂停和重启服务、卸载服务停止、启动、暂停和重启停止服务启动服务暂停服务重启服务卸载移除服务停止、启动、暂停、重启…...
数据库专题
请简洁描述 MySQL 中 InnoDB 支持的四种事务隔离级别名称,以及逐级之间的区别? 默认隔离级别 mysql repeatable-read oracle read-committed 脏读:不可重复读:幻读: CHAR 和 VARCHAR 的区别?…...
浅谈MySQL索引
目录 1.索引的定义 2.索引的原理 3.Hash索引与B Tree索引 4.索引的分类 5.建立索引的注意事项 1.索引的定义 索引是存储引擎用于快速找到数据记录的一种数据结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。 索…...
安装包UI美化之路-通过nsNiuniuSkin来做Electron程序的打包、发布与升级
nsNiuniuSkin从发布之初,因其简单、简洁、高效,受到了非常多公司的青睐,现在已经越来越多的公司采用我们的这套解决方案来制作安装包了! 从一个安装包UI插件,逐步演化成一套集美观、安全、简洁、自动化为一体的完整的…...
飞鹅打印机怎么样?飞鹅打印机好用吗?飞鹅打印机怎么知道订单是否漏单?
外卖打印机怎么选?飞鹅打印机好用吗?飞鹅智能云打印机产品专注于云打印的解决方案和技术服务提供。2019 年飞鹅已经成为国内先进的云打印服务提供商,主要是服务美团、饿了么客户,产品主要优势:自动接单、自动打印,无需…...
网络协议(八):传输层-TCP(三次握手、四次挥手原理)
网络协议系列文章 网络协议(一):基本概念、计算机之间的连接方式 网络协议(二):MAC地址、IP地址、子网掩码、子网和超网 网络协议(三):路由器原理及数据包传输过程 网络协议(四):网络分类、ISP、上网方式、公网私网、NAT 网络…...
最新OpenMVG编译安装与逐命令运行增量式和全局式SfM教程
openmvg是一个轻便的可以逐步运行的SfM开源库,它同时实现了增量式和全局式两种算法。 说明文档地址:https://openmvg.readthedocs.io/en/latest/ github主页地址:https://github.com/openMVG/openMVG 1 编译安装 openmvg的安装比较简单&…...
数据结构与算法系列之插入排序
💗 💗 博客:小怡同学 💗 💗 个人简介:编程小萌新 💗 💗 如果博客对大家有用的话,请点赞关注再收藏 🌞 什么是插入排序 有一个已经有序的数据序列,要求在这个已经排好的数…...
Text to image论文精读ALR-GAN:文本到图像合成的自适应布局优化
ALR-GAN是北京工业大学学者提出的一种自适应布局优化生成对抗网络,其可以在没有任何辅助信息的情况下自适应地优化合成图像的布局。 文章发表于2023年,IEEE Transactions on Multimedia(TMM)期刊(CCF B,JCR…...
windows版 redis在同一局域网下互联
项目场景: 同一局域网下各个主机互相连接同一个redis 问题描述 无法连接 原因分析: 没有放行对方的地址 解决方案: 修改配置文件 最重要的一步如下 然后把 redis.windows.conf的文件也照上面的修改一下保持一致 然后安装一下redis服务这…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
PHP 8.5 即将发布:管道操作符、强力调试
前不久,PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5!作为 PHP 语言的又一次重要迭代,PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是,借助强大的本地开发环境 ServBay&am…...
