[Netty实践] 简单聊天实现(一):基础部分

基于Springboot+WebSocket+Netty实现在线聊天、群聊系统 此文主要实现在好友添加、建群、聊天对话、群聊功能,使用Java作为后端语言进行支持,界面友好,开发简单。 阅读详情

目录

一、简介

二、结构

三、依赖

四、消息设计

五、序列化工具实现

六、编解码器实现

七、后续文章


一、简介

本章主要通过netty实现一个简单的聊天功能,主要分为三块:登录、单聊以及群聊功能,上诉功能会做的比较粗略,主打的就是一个学习,更完善的功能需要自行深入了解以及修改,也希望通过这个章节实践,能够给大家帮助。

该章节主要分为三部分,同时也是分为三篇博客:

第一部分,主要涉及服务端与客户端之间通信的消息设计,以及消息序列化相关实现

第二部分,主要涉及服务端的实现,主要包括服务端的创建、channel管理、组管理、对应各种Handler实现。

第三部分,主要涉及客户端的实现,主要包括客户端的创建、登录、单聊、群聊消息发送。

本篇博客,实现的是第一部分(基础部分)

二、结构

三、依赖

其他模块也一样

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

        <dependency>
            <groupId>io.protostuff</groupId>
            <artifactId>protostuff-core</artifactId>
            <version>1.8.0</version>
        </dependency>

        <dependency>
            <groupId>io.protostuff</groupId>
            <artifactId>protostuff-runtime</artifactId>
            <version>1.8.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>

    </dependencies>

四、消息设计

由于我们的聊天主要有以下三个功能:

登录,需要记录用户名对应的Channel

单聊,需要向对方用户发送消息

群聊,需要向群组发送消息

而以Request结尾的类是由客户端进行发送,服务端进行接收处理的。Response结尾的类是由服务端进行发送,客户端进行接收处理的。

继承了Message类的所有Request和Response,就是服务端与客户端之间交互的数据结构。

1、Message类,所有Request和Response类的父类,最关键的字段就是messageType,子类继承之后进行赋值,该值与类的类型进行绑定,用于byte字节数组反序列化时能够获取到需要反序列化的类型。

public abstract class Message {

    /**
     * 用于记录消息类型,序列化与反序列化时时候,用于找到指定类型
     */
    protected Byte messageType;

    /**
     * 发送时间
     */
    protected Date sendDate;

    public Message() {
        this.sendDate = new Date();
    }

}

2、LoginRequest和LoginResponse,这里登陆进行了简化,只需要用户传递一个username,就将username与channel进行绑定

@Data
@ToString
public class LoginRequest extends Message {

    private String username;

    public LoginRequest() {
        super.messageType = CommandConstant.loginRequest;
    }

    public LoginRequest(String username) {
        super.messageType = CommandConstant.loginRequest;
        this.username = username;
    }

}
@Data
@ToString
public class LoginResponse extends Message {

    private Boolean result;

    private String message;

    public LoginResponse() {
        super.messageType = CommandConstant.loginResponse;
    }

    public LoginResponse(Boolean result, String message) {
        super.messageType = CommandConstant.loginResponse;
        this.result = result;
        this.message = message;
    }

}

3、SingleMessageRequest和SingleMessageResponse,用于向另外一个用户发送消息

@Data
@ToString
public class SingleMessageRequest extends Message {

    /**
     * 发送人
     */
    private String sendFrom;

    /**
     * 接收人
     */
    private String sendTo;

    /**
     * 发送内容
     */
    private String content;

    public SingleMessageRequest() {
        super.messageType = CommandConstant.singleMessageRequest;
    }

    public SingleMessageRequest(String sendFrom, String sendTo, String content) {
        super.messageType = CommandConstant.singleMessageRequest;
        this.sendFrom = sendFrom;
        this.sendTo = sendTo;
        this.content = content;
    }
    
}
@Data
@ToString
public class SingleMessageResponse extends Message {

    /**
     * 发送人
     */
    private String sendFrom;

    /**
     * 发送内容
     */
    private String content;

    public SingleMessageResponse() {
        super.messageType = CommandConstant.singleMessageResponse;
    }

    public SingleMessageResponse(String sendFrom, String content) {
        super.messageType = CommandConstant.singleMessageResponse;
        this.sendFrom = sendFrom;
        this.content = content;
    }

}

4、GroupMessageRequest和GroupMessageResponse,用于向一个群组发送消息

@Data
@ToString
public class GroupMessageRequest extends Message {

    /**
     * 发送人
     */
    private String sendFrom;

    /**
     * 群组
     */
    private String group;

    /**
     * 发送内容
     */
    private String content;

    public GroupMessageRequest() {
        super.messageType = CommandConstant.groupMessageRequest;
    }

    public GroupMessageRequest(String sendFrom, String group, String content) {
        super.messageType = CommandConstant.groupMessageRequest;
        this.sendFrom = sendFrom;
        this.group = group;
        this.content = content;
    }


}
@Data
@ToString
public class GroupMessageResponse extends Message {

    /**
     * 发送人
     */
    private String sendFrom;

    /**
     * 群组
     */
    private String group;

    /**
     * 发送内容
     */
    private String content;

    public GroupMessageResponse() {
        super.messageType = CommandConstant.groupMessageResponse;
    }

    public GroupMessageResponse(String sendFrom, String group, String content) {
        super.messageType = CommandConstant.groupMessageResponse;
        this.sendFrom = sendFrom;
        this.group = group;
        this.content = content;
    }
    
}

5、CommandConstand,通过数值常量messageType绑定消息类型,在序列化对象时,会在数据中记录对象的messageType,在反序列化对象时,会从数据包中拿到messageType,将其转化为对应的消息类型进行处理

public class CommandConstant {

    public final static Byte singleMessageRequest = 1;
    public final static Byte singleMessageResponse = 2;

    public final static Byte groupMessageRequest = 3;
    public final static Byte groupMessageResponse = 4;

    public final static Byte loginRequest = 5;
    public final static Byte loginResponse = 6;

    public static Map<Byte, Class<? extends Message>> messageTypeMap = new ConcurrentHashMap<>();

    static {
        messageTypeMap.put(singleMessageRequest, SingleMessageRequest.class);
        messageTypeMap.put(singleMessageResponse, SingleMessageResponse.class);

        messageTypeMap.put(groupMessageRequest, GroupMessageRequest.class);
        messageTypeMap.put(groupMessageResponse, GroupMessageResponse.class);

        messageTypeMap.put(loginRequest, LoginRequest.class);
        messageTypeMap.put(loginResponse, LoginResponse.class);
    }

    public static Class<? extends Message> getMessageClass(Byte messageType){
        return messageTypeMap.get(messageType);
    }

}

五、序列化工具实现

该序列化工具主要用于将对象序列化为字节数组、以及将字节数组序列化为对象

public class SerializationUtil {

    private final static Map<Class<?>, Schema<?>> schemaCache = new ConcurrentHashMap<>();

    /**
     * 序列化
     */
    public static <T> byte[] serialize(T object){
        LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);

        try {
            Class<T> cls = (Class<T>) object.getClass();
            Schema<T> schema = getSchema(cls);

            return ProtostuffIOUtil.toByteArray(object, schema, buffer);
        } catch (Exception e) {
            throw e;
        } finally {
            buffer.clear();
        }
    }

    /**
     * 反序列化
     */
    public static <T> T deserialize(Class<T> cls, byte[] data) {
        Schema<T> schema = getSchema(cls);
        T message = schema.newMessage();
        ProtostuffIOUtil.mergeFrom(data, message, schema);
        return message;
    }

    public static <T> Schema<T> getSchema(Class<T> cls) {
        Schema<T> schema = (Schema<T>) schemaCache.get(cls);

        if(schema == null) {
            schema = RuntimeSchema.getSchema(cls);
            schemaCache.put(cls, schema);
        }
        return schema;
    }
    
}

六、编解码器实现

1、MessageEncode,用于将消息对象序列化为字节数组

字节数组主要包括三部分:

·有效数组长度,占4个字节,长度不包括自己,用于半包黏包判断

·消息的类型,占1个字节,用于反序列选择类型使用

·消息对象,占n个字节

public class MessageEncode extends MessageToByteEncoder<Message> {

    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, Message message, ByteBuf byteBuf) throws Exception {
        // 将对象进行序列化
        byte[] data = SerializationUtil.serialize(message);

        // 写数据长度,前4个字节用于记录数据总长度(对象 + 类型(1个字节))
        byteBuf.writeInt(data.length + 1);
        // 写记录消息类型,用于反序列选择类的类型
        byteBuf.writeByte(message.getMessageType());
        // 写对象
        byteBuf.writeBytes(data);
    }

}

2、MesageDecode,用于将字节数组反序列化为消息对象

反序列时会进行判断数据是否足够读取,足够的话就会读取到符合长度的字节数组进行序列化,否则的话等到下一个数据包到来再进行重新判断处理(解决半包黏包方案)

public class MessageDecode extends ByteToMessageDecoder {


    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {

        // 由于数据包的前4个字节用于记录总数据大小,如果数据不够4个字节,不进行读
        if(byteBuf.readableBytes() < 4) {
            return;
        }

        // 标记开始读的位置
        byteBuf.markReaderIndex();

        // 前四个字节记录了数据大小
        int dataSize = byteBuf.readInt();

        // 查看剩余可读字节是否足够,如果不是,重置读取位置,等待下一次解析
        if(byteBuf.readableBytes() < dataSize) {
            byteBuf.resetReaderIndex();
            return;
        }

        // 读取消息类型
        byte messageType = byteBuf.readByte();
        // 读取数据, 数组大小需要剔除1个字节的消息类型
        byte[] data = new byte[dataSize -1];

        byteBuf.readBytes(data);

        Message message = SerializationUtil.deserialize(CommandConstant.getMessageClass(messageType), data);

        list.add(message);
    }

}

七、后续文章

以上就是本节需要实现的所有内容,接下来关于server和client的实现,看以下文章:

https://blog.youkuaiyun.com/Staba/article/details/135061442

https://blog.youkuaiyun.com/Staba/article/details/135061661

怎么用Netty开发IM即时通讯聊天软件 例如\r\n,如果一个包被拆分了,则等待下一个包发送过来之后找到其中的\r\n,然后对其拆分后的头部部分与前一个包的剩余部分进行合并,这样就得到了一个完整的包。也就是说,Netty是一个基于NIO的客户、服务器端编程框架,使用Netty可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。①客户端在发送数据包的时候,每个包都固定长度。如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包,也就是将一个大的包拆分为多个小包进行发送。... 阅读详情

相关推荐

Netty实现简易聊天室的功能

使用Netty来实现一个聊天室是很容易的,当有客户端连接服务端的时候,在服务端的channelActive方法里面即可感应到。这里的解决方案是发送每条数据的时候,将数据的长度一并发送,比如可以选择每条数据的前4位是数据的长度,应用层处理时可以根据长度来判断每条数据的开始和结束。同理,channelInactive是该客户端断开服务端时会回调的方法,在里面给其他客户端发一条“下线了”的信息即可。如果数据长度不够length,就等待下一次数据过来,取数据的时候也只取length长度的数据。

天瑕的博客 2665

netty应用场景_程序员:利用Netty来写一个简单的聊天室、心跳检测

NettyNetty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应...

weixin_39645306的博客 441

JAVA Netty框架下的聊天程序(Maven项目)

sever handler import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.Channe...

qq_41359808的博客 3585

Netty--聊天业务

推荐 了解 消息推送中的已读消息和未读消息设计问题 站内信问题难点 Netty websocket 实现简单聊天功能的相关

Kaka_csdn14的博客 2201

实现飞书机器人推送消息到指定群组或者用户

飞书是一个集成了聊天、协作和办公功能的企业级平台,广泛应用于企业内部沟通和协作。飞书机器人(Feishu Bot)是一种强大的工具,允许开发者通过编程接口与飞书进行互动,从而实现自动化消息推送和任务管理。本文将详细介绍如何通过飞书机器人推送消息给指定的人或者群组,帮助您快速上手。

qq_41898196的博客 5866

Silvaco Atlas从头开始(四)——定义材料参数和模型

这部分是关于定义材料参数和模型。

qsz1425AWD2121的博客 4650

简单netty聊天室(前端部分)

序 分享一下学习 netty 时练手的聊天室项目,目前已经完成了前端部分的一大半,因此写一篇博客来记录一下。 (因为本人是前端白痴,所以只用 jquery 和 bootstrap 等简单工具实现了前端页面,望谅解( ╯▽╰)) 简介 本项目(SimpleNettyChatroom)使用 Spring Boot 作为后端开发框架,使用 jQuery 和 Bootstrap 作为前端开发框架,并且配合使用了前端 ajax 技术和后端 netty 框架。 如果想学习或了解更多关于项目源码的内容,欢迎访问和星标我

ahy231的博客 1087

顶级的FDE需要拥有哪些技能?

上一篇《》说明白了什么是FDE。这一篇回答下一个问题:要成为顶级 FDE,需要哪些具体的技能?要想弄清楚FDE应该有哪些能力,就得先搞清楚FDE要完成哪些工作。同时,有一点是我们要密切关注的:现在是大模型时代,所以我们不能脱离大模型讨论FDE的能力。我们可以把FDE在客户现场需要完成的工作分为四类:破冰与诊断、价值交付、项目与预期管理、沟通与影响。另有两项元能力贯穿始终。以下按工作类别逐一拆解,每项能力配 1 / 3 / 5 分标准,方便对照自评。文末有完整打分工具链接。

mydeman的学习日志 260

[Netty实践] 简单聊天实现(三):client部分

本章节主要讲第三部分,涉及客户端的实现,主要包括客户端进行登录、私聊、群聊。

Staba的博客 1199

【域控制器EMC】域控制器EMC设计总结

汇总项目设计当中关于EMC的设计要点,主要从原理设计、Layout、结构、外设部分总结。满足要求的器件和合理的电路能降低电磁干扰;正确的布局、接地、信号路径、屏蔽等可以减少电磁辐射。结构件设计也会对EMC性能产生影响。

weixin_42098658的博客 1259

eCognition易康面向对象分类批量选择样本,批量导出shp

可以的,使用这个工具条最右边那个三角尺manual editing toolbar,这是用来直接指定类别的,咱们可以拿它选完以后把指定的类别转成训练样本。另一个好处是,这个选出来的样本可以直接导出成shp,但select sample是不可以的,只能导出TTAMask也就是栅格掩膜。如果要导出shp,要在转之前导出。中间那条有点点的红线就是我的鼠标轨迹,只要碰到的对象就会全部选中,然后一点3的那个按钮就直接全部划分为林地了,十分丝滑。在弹出来的工作条里,首先选择你的类别,比如这里选一个forest。

qq_61845364的博客 4070

简单netty聊天室(后端部分)

简介 这是 SimpleNettyChatroom 项目的后端部分介绍。如果你想看更多关于该项目前端的内容,请点击下面的链接: 简单netty聊天室(前端部分) 如果你想浏览项目源码,请点击下面的链接: https://github.com/ahy231/SimpleNettyChatroom 总体介绍 一个普通的 Springboot 项目,目前只实现了一对一实时通讯。在 config 包里,有登录过滤器(GlobalConfig + LoginInterceptor)和错误处理(GlobalExcep

ahy231的博客 409

筹码峰五句口诀,看懂主力的秘密动作!(附实战图示)

筹码图的价值在于,它不像K线那样容易造假。主力可以骗线,但骗不了筹码分布。主力在不在;是吸筹还是出货;接下来是变盘还是震荡。这五句口诀可以说是“筹码阅读的入门圣经”:单峰坚挺变盘在即,双峰对垒高抛低吸;上峰不移主力在跑,多峰林立行情不止;下峰不移不离不弃,筹码识庄稳中取利!📌市场从来不是靠猜,而是靠看清。筹码,就是主力最真实的痕迹。如果你想学会提前布局、跟庄不被甩,学会看筹码峰——这一步必须打牢!

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 1162

[Netty实践] 简单聊天实现(二):server部分

本章节主要讲第二部分,涉及服务端的实现,主要包括服务端的创建、channel管理、组管理、对应各种Handler实现。

Staba的博客 1141

AI奶牛TMR智能饲喂机器人 Qt信创完整项目( 统信/麒麟国产系统直接编译)

内置**犊牛、育成牛、围产牛、泌乳牛、干奶牛**5套标准化饲喂配方;+" 饲喂批次完成|牛群:"+grpText+"|投喂饲料:"+QString::number(ft)+"吨|剩料:"+QString::number(st)+"吨|异常牛:"+QString::number(abCow);cv::putText(frame,"霉变变质饲料禁止饲喂",cv::Point(20,40),cv::FONT_HERSHEY_SIMPLEX,0.7,cv::Scalar(0,0,255),2);

寒寒豆豆软件公司的博客 183

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

easychat项目复盘---聊天部分netty篇

再次回归到事件循环组,事件循环组会不断循环解决io问题,如果事件循环组没有结束,idlestatehandler会一直接收到消息,一但idlestatehandler接受不到则代表事件循环组已经结束,意味着客户端与服务器断开,则需要一个处理器去关闭通道,所以handlerheartbeat应运而生。事件循环组的含义就是无限循环去不断的处理io事件,不断循环就达到了实时的目的,因为他永不停歇,只要一发送信息就会处理,另一方就可以接受到信息。// 对http协议的支持,使用http的编码器,解码器。

PPPPickup的博客 538

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

netty编程之基于websocket实现聊天功能

本文看下netty如何通过websocket实现聊天功能。类似于实现http server,netty实现websocket也很简单,同样使用对应的编码器和解码器就行了,相关的有HttpServerCodec,HttpObjectAggregator,ChunkedWriteHandler。server类: MyChannelInitializer: 其中设置了支持websocket相关的编解码器是关键。 MyServerHandler: 对解码后的websocket消息按照不同的消息类型做了不同的处理,工

wang0907的博客 1844

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Netty搭建简易聊天室

NIO框架Netty搭建简易聊天室 Netty Reactor模型 单线程模型 用户发起 IO 请求到 Reactor 线程 Ractor 线程将用户的 IO 请求放入到通道,然后再进行后续处理 处理完成后,Reactor 线程重新获得控制权,继续其他客户端的处理 缺点: 这种模型一个时间点只有一个任务在执行,这个任务执行完了,再去执行下一个任务。 但单线程的 Reactor 模型每一个用户事...

jl935418600的博客 1393

[Netty实践] 简单WebSocket服务实现

关于WebSocket此处不进行过多介绍,本章主要着重通过Netty实现WebSocket通信服务端,并且实现一个简单的通过网页进行聊天的功能。讲到WebSocket,这里简单介绍一下为什么要使用WebSocket。以往我们通过网页与服务器进行交互时,都是通过发起一个http/https请求,该请求是无状态的,发送请求后,等待获取服务器返回的结果之后,这次请求就结束了,客户端与服务端就断开了。

Staba的博客 1732

netty聊天室

package com.example.netty.chat.server; import com.example.netty.chat.protocol.IMDecoder; import com.example.netty.chat.protocol.IMEncoder; import com.example.netty.chat.server.handler.HttpHandler; im...

lucky的专栏 347

netty主动断开连接_使用Netty实现 IM 聊天贼简单,看不懂就锤爆哪吒的狗头~(结局)...

5. 断开重连Netty 客户端需要实现断开重连机制,解决各种情况下的断开情况。例如说:Netty 客户端启动时,Netty 服务端处于挂掉,导致无法连接上。在运行过程中,Netty 服务端挂掉,导致连接被断开。任一一端网络抖动,导致连接异常断开。具体的代码实现比较简单,只需要在两个地方增加重连机制。Netty 客户端启动时,无法连接 Netty 服务端时,发起重连。Netty 客户端运行时,和 ...

weixin_39616367的博客 2436

netty简单实例

一: 简介      工作中用到netty,先用起来,再深入研究,这里我们一起谈谈聊天群发的小实例。 实例是maven工程,用到spring。我们先从建立服务器端开始,然后再介绍client端。 工程目录: ************************************************************************************

netboy 8445

Netty分布式聊天室 - 语音视频聊天

基于Netty的分布式聊天室的项目到此结束,这只是一个示例项目,还需要丰富一下细节, 但正常的使用已经没有问题了dftdla/NettyChatCloud - 码云 - 开源中国 (gitee.com)

爱飞的男孩的博客 1283

netty实现简单聊天室

1. package com.xiaogang.netty.chat.server; import java.util.Date; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.Channe

sidihuo的专栏 492

Springboot +Netty+Vue实现聊天(单聊+创建群聊并聊天)

Springboot +Netty+Vue实现简单的单对单聊天 后台 项目结构 pom文件 主要在SpringBoot项目的pom文件基础上,加上下面的 <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <!-- Use 'netty-all' for 4.0 or above --> <scope

qq_40917075的博客 5999
上一篇: [Netty实践] 简单WebSocket服务实现
下一篇: [Netty实践] 简单聊天实现(二):server部分
明天再去学习
博客等级 码龄7年 148粉丝 45原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值