简单记录一下实现的整体框架,具体细节在实际生产中再细化就可以了。

第一步 引入netty依赖

SpringBoot的其他必要的依赖像Mybatis、Lombok这些都是老生常谈了 就不在这里放了

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

第二步 接下来就是准备工作。

消息服务类(核心代码) 聊天服务的功能就是靠这个类的start()函数来启动的 绑定端口8087 之后可以通socket协议访问这个端口来执行通讯

import com.bxt.demo.im.handler.WebSocketHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
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.stream.ChunkedWriteHandler;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; /**
* @Description: 即时通讯服务类
* @author: bhw
* @date: 2023年09月27日 13:44
*/
@Slf4j
public class IMServer {
  // 用来存放连入服务器的用户集合
public static final Map<String, Channel> USERS = new ConcurrentHashMap<>(1024);
  // 用来存放创建的群聊连接
public static final ChannelGroup GROUP = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); public static void start() throws InterruptedException {
log.info("IM服务开始启动");
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup(); // 绑定端口
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 添加http编码解码器
pipeline.addLast(new HttpServerCodec())
//支持大数据流
.addLast(new ChunkedWriteHandler())
// 对http消息做聚合操作 FullHttpRequest FullHttpResponse
.addLast(new HttpObjectAggregator(1024*64))
//支持websocket
.addLast(new WebSocketServerProtocolHandler("/"))
.addLast(new WebSocketHandler());
}
}); ChannelFuture future = bootstrap.bind(8087).sync();
log.info("服务器启动开始监听端口: {}", 8087);
future.channel().closeFuture().sync();
//关闭主线程组
bossGroup.shutdownGracefully();
//关闭工作线程组
workGroup.shutdownGracefully();
} }

创建聊天消息实体类

/**
* @Description: 聊天消息对象 可以自行根据实际业务扩展
* @author: seizedays
*/
@Data
public class ChatMessage extends IMCommand {
//消息类型
private Integer type;
//消息目标对象
private String target;
//消息内容
private String content; }

连接类型枚举类,暂时定义为建立连接、发送消息和加入群组三种状态码

@AllArgsConstructor
@Getter
public enum CommandType { //建立连接
CONNECT(10001),
//发送消息
CHAT(10002),
//加入群聊
JOIN_GROUP(10003),
ERROR(-1)
; private Integer code; public static CommandType match(Integer code){
for (CommandType value : CommandType.values()) {
if (value.code.equals(code)){
return value;
}
}
return ERROR;
} }

命令动作为聊天的时候 消息类型又划分为私聊和群聊两种 枚举类如下:

@AllArgsConstructor
@Getter
public enum MessageType { //私聊
PRIVATE(1),
//群聊
GROUP(2),
ERROR(-1)
;
private Integer type; public static MessageType match(Integer code){
for (MessageType value : MessageType.values()) {
if (value.type.equals(code)){
return value;
}
}
return ERROR;
} }

创建连接请求的拦截器

import com.alibaba.fastjson2.JSON;
import com.bxt.common.vo.Result;
import com.bxt.demo.im.cmd.IMCommand;
import com.bxt.demo.im.server.IMServer;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; /**
* @Description: 用户连接到服务端的拦截器
* @author: bhw
* @date: 2023年09月27日 14:28
*/
public class ConnectionHandler {
public static void execute(ChannelHandlerContext ctx, IMCommand command) {
if (IMServer.USERS.containsKey(command.getNickName())) {
ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(Result.error(command.getNickName() + "已经在线,不能重复连接"))));
ctx.channel().disconnect();
return;
} IMServer.USERS.put(command.getNickName(), ctx.channel()); ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(Result.success("系统消息:" + command.getNickName() + "与服务端连接成功")))); ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(Result.success(JSON.toJSONString(IMServer.USERS.keySet())))));
}
}

加入群组功能的拦截器

/**
* @Description: 加入群聊拦截器
* @author: bhw
* @date: 2023年09月27日 15:07
*/
public class JoinGroupHandler {
public static void execute(ChannelHandlerContext ctx) {
try {
IMServer.GROUP.add(ctx.channel());
ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(Result.success("加入系统默认群组成功!"))));
} catch (Exception e) {
ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(Result.error("消息内容异常"))));
} }
}

发送聊天到指定对象的功能拦截器

import com.alibaba.excel.util.StringUtils;
import com.alibaba.fastjson2.JSON;
import com.bxt.common.vo.Result;
import com.bxt.demo.im.cmd.ChatMessage;
import com.bxt.demo.im.cmd.MessageType;
import com.bxt.demo.im.server.IMServer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import java.util.Objects; /**
* @Description: 聊天拦截器
* @author: bhw
* @date: 2023年09月27日 15:07
*/
public class ChatHandler {
public static void execute(ChannelHandlerContext ctx, TextWebSocketFrame frame) {
try {
ChatMessage message = JSON.parseObject(frame.text(), ChatMessage.class);
MessageType msgType = MessageType.match(message.getType()); if (msgType.equals(MessageType.PRIVATE)) {
if (StringUtils.isBlank(message.getTarget())){
ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(Result.error("系统消息:消息发送失败,请选择消息发送对象"))));
return;
}
Channel channel = IMServer.USERS.get(message.getTarget());
if (Objects.isNull(channel) || !channel.isActive()){
ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(Result.error("系统消息:消息发送失败,对方不在线"))));
IMServer.USERS.remove(message.getTarget());
return;
}
channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(Result.success("私聊消息(" + message.getTarget() + "):" + message.getContent())))); } else if (msgType.equals(MessageType.GROUP)) {
IMServer.GROUP.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(Result.success("群消息:发送者(" + message.getNickName() + "):" + message.getContent()))));
}else {
ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(Result.error("系统消息:不支持的消息类型"))));
} } catch (Exception e) {
ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(Result.error("消息内容异常"))));
} }
}

最后是websocket拦截器 接收到客户端的指令后选择对应的拦截器实现相应的功能:

import com.alibaba.fastjson2.JSON;
import com.bxt.common.vo.Result;
import com.bxt.demo.im.cmd.CommandType;
import com.bxt.demo.im.cmd.IMCommand;
import com.bxt.demo.im.server.IMServer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.extern.slf4j.Slf4j; /**
* @Description: websocket拦截器
* @author: bhw
* @date: 2023年09月27日 13:59
*/
@Slf4j
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { @Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) {
System.out.println(frame.text());
try {
IMCommand command = JSON.parseObject(frame.text(), IMCommand.class);
CommandType cmdType = CommandType.match(command.getCode());
if (cmdType.equals(CommandType.CONNECT)){
ConnectionHandler.execute(ctx, command);
} else if (cmdType.equals(CommandType.CHAT)) {
ChatHandler.execute(ctx,frame);
} else if (cmdType.equals(CommandType.JOIN_GROUP)) {
JoinGroupHandler.execute(ctx);
} else {
ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(Result.error("不支持的code"))));
}
}catch (Exception e){
ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(Result.error(e.getMessage()))));
} } @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 当连接断开时被调用
Channel channel = ctx.channel();
// 从 USERS Map 中移除对应的 Channel
removeUser(channel);
super.channelInactive(ctx);
} private void removeUser(Channel channel) {
// 遍历 USERS Map,找到并移除对应的 Channel
IMServer.USERS.entrySet().removeIf(entry -> entry.getValue() == channel);
}
}

第三步 启动服务

@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
// 启动IM服务
try {
IMServer.start();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} }

现在 客户端通过socket协议访问8087端口即可实现基本的聊天室功能了!

基于SpringBoot+Netty实现即时通讯(IM)功能的更多相关文章

  1. 基于Android 平台简易即时通讯的研究与设计[转]

    摘要:论文简单介绍Android 平台的特性,主要阐述了基于Android 平台简易即时通讯(IM)的作用和功能以及实现方法.(复杂的通讯如引入视频音频等可以考虑AnyChat SDK~)关键词:An ...

  2. 使用 HTML5 webSocket API实现即时通讯的功能

    project下载地址:http://download.csdn.net/detail/wangshuxuncom/6430191 说明: 本project用于展示怎样使用 HTML5 webSock ...

  3. easy-im:一款基于netty的即时通讯系统

    介绍 easy-im是面向开发者的一款轻量级.开箱即用的即时通讯系统,帮助开发者快速搭建消息推送等功能. 基于easy-im,你可以快速实现以下功能: + 聊天软件 + IoT消息推送 基本用法 项目 ...

  4. websocket和基于swoole的简易即时通讯

    这里描述个基于swoole的websocket 匿名群聊 UI <!DOCTYPE html> <html> <head> <meta charset=&qu ...

  5. 基于SpringBoot+Netty实现一个自己的推送服务系统

    目标 实现一个WebSocket服务中心,支持水平扩展 技术栈 SpringBoot.Netty.JDK8.MySQL.Redis.RabbitMQ.MyBatis-Plus 环境搭建 主要功能点说明 ...

  6. Flutter高仿微信项目开源-具即时通讯IM功能

    项目地址:https://github.com/fluttercandies/wechat_flutter wechat_flutter  Flutter版本微信 效果图: 下载体验(Android) ...

  7. [Python]实现XMPP协议即时通讯发送消息功能

    #-*- coding: utf-8 -*- __author__ = 'tsbc' import xmpp import time #注意帐号信息,必须加@域名格式 from_user = 'che ...

  8. 即时通讯(IM-instant messager)

    即时通讯又叫实时通讯,简单来说就是两个及以上的人使用网络进行文字.文件.语音和视频的交流. 首先,进行网络进行通信,肯定需要网络协议,即时通讯专用的协议就是xmpp.xmpp协议要传递的消息类型是xm ...

  9. openfire+asmack搭建的安卓即时通讯(一) 15.4.7

    最进开始做一些android的项目,除了一个新闻客户端的搭建,还需要一个实现一个即时通讯的功能,参考了很多大神成型的实例,了解到operfire+asmack是搭建简易即时通讯比较方便,所以就写了这篇 ...

  10. openfire+asmack搭建的安卓即时通讯(三) 15.4.9

    (能用得上话的话求点赞=-=,我表达不好的话跟我说哦) 上一次我们拿到了服务器端的组数据和用户信息,这就可以为我们日后使用好友系统打下基础了! 但是光是拿到了这些东西我们怎么能够满足呢?我们一个即时通 ...

随机推荐

  1. .NET 5 的烦恼

    由于微软工程师的辛勤脑洞和劳作,.NET 生态如何演变完全看他们的决策,其中包含社区吸纳内容.团队讨论结果等等,不乏一些工程师.架构师偏好,很难摸得准.   就比如这一次未来规划,他们希望将 .NET ...

  2. ASP.NET Core 6框架揭秘实例演示[41]:跨域资源的共享(CORS)花式用法

    同源策略是所有浏览器都必须遵循的一项安全原则,它的存在决定了浏览器在默认情况下无法对跨域请求的资源做进一步处理.为了实现跨域资源的共享,W3C制定了CORS规范.ASP.NET利用CorsMiddle ...

  3. Jupyter无法运行websocket错误

    Juputer无法连接 系websocket连接出问题,浏览器控制台报错 ws 连接异常 WebSocket connection to 'ws://localhost:8888/api/kernel ...

  4. 自己动手实现rpc框架(二) 实现集群间rpc通信

    自己动手实现rpc框架(二) 实现集群间rpc通信 1. 集群间rpc通信 上一篇博客中MyRpc框架实现了基本的点对点rpc通信功能.而在这篇博客中我们需要实现MyRpc的集群间rpc通信功能. 自 ...

  5. SSIS向MySQL目标(Destination)写数据--Step By Step

    前言(废话) 最近的工作中涉及到SQLSERVER向MySQL的数据迁移同步,团队中理所当然准备用开发C#微服务接口的方式实现,我觉得这个路子曲折了,推荐SSIS的方式并自告奋勇接下了这个活.不过以前 ...

  6. 五分钟了解MES与MOM的区别和联系

    大家好,我是Edison. 上一篇,我们通过了解了MES系统的发展历程和标准体系.本篇,我们来快速了解一下近年来吵得很热的MOM是什么鬼,它和MES到底有什么区别和联系. MES是什么 MES (Ma ...

  7. 《设计模式的运用》使用策略模式+工厂模式优化代码中的if else

    使用策略模式优化if else 有这样一段逻辑 function{ for{ if() if() if( if( ) ) ... } } 公司有的祖传的代码,是一段规则校验,校验的越多,每一个请求都会 ...

  8. Centos 7安装Docker镜像仓库-Harbor

    下载安装包并导入镜像 # 进入文件下载目录 cd /root/software/ # 下载安装文件 # 如果下载失败可以本地下载,下载后上传至服务器,https://github.com/goharb ...

  9. Print, printf, println的区别

    print 非格式,打印变量的值,不换行 printf 支持格式化输出,不换行 println 非格式,打印变量的值 ,换行

  10. 构建易于运维的 AI 训练平台:存储选型与最佳实践

    伴随着公司业务的发展,数据量持续增长,存储平台面临新的挑战:大图片的高吞吐.超分辨率场景下数千万小文件的 IOPS 问题.运维复杂等问题.除了这些技术难题,我们基础团队的人员也比较紧张,负责存储层运维 ...