✨重磅!盹猫的个人小站正式上线啦~诚邀各位技术大佬前来探秘!✨
这里有:
- 硬核技术干货:编程技巧、开发经验、踩坑指南,带你解锁技术新姿势!
- 趣味开发日常:代码背后的脑洞故事、工具测评,让技术圈不再枯燥~
- 独家资源分享:开源项目、学习资料包,助你打怪升级快人一步!
👉 点击直达→ 盹猫猫的个人小站 👈
🌟 来逛逛吧,说不定能挖到你正在找的技术宝藏哦~
目录
[1.1 什么是Netty?](#1.1 什么是Netty?)
[1.2 Netty流程图](#1.2 Netty流程图)
[2.1 Pom依赖](#2.1 Pom依赖)
[3.1 创建服务](#3.1 创建服务)
[3.2 创建解码器](#3.2 创建解码器)
[3.3 事件处理器](#3.3 事件处理器)
欢迎来到 盹猫(>^ω^<)的博客
本篇文章主要介绍了
**SpringBoot从0-1集成Netty实现自定义协议开发**
❤博主广交技术好友,喜欢文章的可以关注一下❤
一、前言
在使用SpringBoot实际的开发过程中,为适应不同的应用场景,有时会先择使用HTTP协议通信,有时又使用MQTT协议进行通信(当然SpringBoot现在已经有很多比较完善的协议集成,也基本满足大部分的开发场景)。但一些公司自定义了一些协议(比如一些比较小众的手表通信,或自
己用物联网硬件自己DIY了一些设备需要通信),这时使用SpringBoot应该怎么接收呢?

有些朋友可能Java技术很强,从0开始搞了一套Socket通信程序,这其实也不是不行,但如果有多线程、高并发,实现起来很麻烦,而且健壮性得不到保障。
想解决这些问题,其实并不难,只需要让你的SpringBoot集成Netty框架,本篇文章就是记录SpringBoot如何是如何集成Netty框架的。
但在此之前先来看看什么是Netty?
1.1 什么是Netty?
Netty 是一款基于 Java NIO 开发的异步、事件驱动的高性能网络通信框架,由 JBoss 团队开发并开源(Apache 2.0 协议),核心目标是简化高性能、高可靠性的 TCP/UDP 网络应用开发,解决原生 Java NIO 开发复杂度高、易出 bug(如空轮询、缓冲区管理混乱)等问题。
可以看出Netty就是为了简化TCP/UDP通信的网络应用开发而出现,并且使用了异步、事件驱动的方式。
**异步:**主线程只会管理子线程,而不会被阻塞。
**事件驱动:**已经对通信事件进行分类,只需要对不同事件分别进行代码填充即可。
1.2 Netty流程图

从Netty的工作流程图可以看出,其主要工作主要分为两个工作组,即Boss Group和Woker Group 。
Boss Group:用于处理连接请求,即当有连接发生时,注册一个通道,到Worker Group然后继续等待。(老板安排工作)
Worker Group:用于读写数据并分配给不同的通道处理器ChannelHdandler进行处理。(打工人根据要求进行处理😭)
废话不多说,来看看如何集成吧!
LET'S GO!🚗
二、环境准备
2.1 Pom依赖
XML
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
JDK这里使用JDK1.8。
XML
<dependencies>
<!--Netty主要依赖-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.65.Final</version>
</dependency>
<!--SpringBoot的Web开发依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.18</version>
</dependency>
</dependencies>
这里使用SpringBoot的Web依赖,当然如果不是做Web程序也可以使用spring-boot-starter依赖。
三、代码步骤
3.1 创建服务
首先根据流程图,我们需要创建BossGroup 和WorkerGroup,这两个是主要的工作组,代码如下:
java
/**
* Desc:[Boss工作组]
**/
final EventLoopGroup bossGroup = new NioEventLoopGroup();
/**
* Desc:[Worker工作组]
**/
final EventLoopGroup workGroup = new NioEventLoopGroup();
然后将这两个添加到ServerBootstrap(Netty提供的服务构建器 **)**中,代码如下:
java
//创建一个启动器
ServerBootstrap serverBootstrap = new ServerBootstrap();
//添加boss工作组和worker工作组
serverBootstrap.group(bossGroup, workGroup);
然后设置通道的类型为异步通道,代码如下:
java
serverBootstrap.channel(NioServerSocketChannel.class)
这时Boss工作组其实就已经设置完了(相当于流水线建完了,但是领导还没有决定让工人干什么),我们的目的是为了处理自定义的任务,所以还要添加编码器(Decoder)、事件处理器(Handler)、解码器(Encoder),这里编码器和解码器不是必须的。
代码如下:
java
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
//这里添加编解码器和监听器
ch.pipeline().addLast(编码器/解码器/事件处理器);
}
});
这里就可以在通道中设置具体的工作了,可以通过代码设置具体的工作事项,例如下面的代码:
java
//添加一个自定义的ByteMessage解码器
ch.pipeline().addLast(new ByteMessageDecoder());
//添加一个连接的是否读/写超时的事件处理器
ch.pipeline().addLast("idleStateHandler", new IdleStateHandler(600, 0, 0));
//添加一个自定义的设备事件处理器
ch.pipeline().addLast("common-handler", deviceServerHandlerJL);
这里IdleStateHandler是Netty自带的,而解码器和设备处理器一般需要自定义,因为要接收不同的协议格式和自定义处理事件。
3.2 创建解码器
解码器即为入站时要满足不同的需要进行数据格式转换的服务,一般放在pipeline的最上层,Netty有一些已经实现的解码器可以直接使用,如:JsonObjectDecoder (提供基础的Josn格式校验), 如果不配置默认是直接是ByteBuf接收,但一般情况下还是需要进行自定义的,而自定义时一般继承ByteToMessageDecoder(即直接对Byte数据进行操作) 。
例如实现一个固定长度的以16进制发送传输的协议数据时,我们需要按照固定开头(分割符)进行byte进制转换为16进制的字符串的功能,(需要将256转换为FF字符串),将并添加到list中方便后续的事件处理器进行处理,代码如下:
java
package com.ydhy.platform.decoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.ByteProcessor;
import java.util.List;
/**
* DES:
*
* @author nodcat
* Time:2023/1/28 下午5:33
* Implements{@link }
* extend{@link ByteToMessageDecoder}
**/
public class DeviceMessageDecoder extends ByteToMessageDecoder {
//
/**
* 长度49
*/
private final int frameLength;
/**
* DES:[构造方法]
*
* @param frameLength 消息长度 49
* @link [int]
*/
public DeviceMessageDecoder(int frameLength) {
this.frameLength = frameLength;
}
/**
* DES:[入站16进制数据解析]
*
* @param channelHandlerContext channel全局管理
* @param byteBuf byteBuf 数据缓存
* @param list 列表
* @link [io.netty.channel.ChannelHandlerContext, io.netty.buffer.ByteBuf, java.util.List<java.lang.Object>]
*/
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) {
int indexFb = byteBuf.forEachByte(new ByteProcessor.IndexOfProcessor((byte) Integer.parseInt("FB", 16)));
int index90 = byteBuf.forEachByte(new ByteProcessor.IndexOfProcessor((byte) Integer.parseInt("90", 16)));
if (indexFb != -1 && index90 != -1) {
if (indexFb == (index90 - 1)) {
byteBuf.readerIndex(indexFb);
if (byteBuf.readableBytes() >= frameLength) {
ByteBuf slice = byteBuf.readRetainedSlice(frameLength);
byte[] bytes = new byte[frameLength];
slice.readBytes(bytes);
list.add(bytesToHexString(bytes));
slice.release();
}
}
}
}
/**
* DES:[将byte数组转换为16进制的字符串]
*
* @param bArray 字节数组
* @return java.lang.String
* @link [byte[]]
*/
public String bytesToHexString(byte[] bArray) {
StringBuilder sb = new StringBuilder(bArray.length);
String sTemp;
for (byte b : bArray) {
sTemp = Integer.toHexString(0xFF & b);
if (sTemp.length() < 2) {
sb.append(0);
}
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
}
重写decode方法,查找起始的字节并限制接收的数据长度为49位,这样在后续处理中就直接对16进制的字符串进行胺位处理(长度为98的16进制字符串,因为一个byte转换为了两个16进制字符串表示),如果不经过Decoder转换,在后续的处理过程中就会繁琐很多。
java
//最先执行下面的addLast方法,确保解码器在最上层
ch.pipeline().addLast(new DeviceMessageDecoder(49));
//或者使用addFast添加到解码器到最上层
ch.pipeline().addFast(new DeviceMessageDecoder(49));
提示:这里可以根据实际的协议要求进行处理,确保解码器在处理流程的最上层。
3.3 事件处理器
事件处理器就是对具体的业务进行处理了,继承**SimpleChannelInboundHandler<Object>**来实现对解码器处理完的数据进行处理,继承后一般会重写以下方法:
读事件处理
java
@Override
public void channelRead0(ChannelHandlerContext ctx, Object msg) {
//这里的msg即为解码器处理完成后的数据
}
连接状态
java
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
//这里处理连接状态异常,如读写超时多久后断开链接
}
通道关闭事件
java
@Override
public void channelInactive(ChannelHandlerContext ctx) {
//通道关闭时调用,一般在remove事件之前被调用
}
事件处理器添加事件
java
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//当有连接时触发
}
事件处理器移除事件
java
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
//事件处理器移除时触发
}
同样需要添加到3.1的pipeline中,一般放在解码器之后,代码如下:
java
//这里可选有解码器
//添加一个自定义的设备事件处理器
ch.pipeline().addLast("handler", deviceServerHandler);
//这里可选有编码器
通过分别对上述不同事件触发的方法进行重写实现,以实现自定义的功能,例如:
当事件处理器添加时,我们可以提示用户上线了。
当事件处理器移除时,提示用户断开链接下线了。
当发生读事件时,我们接收数据并存储到数据库,或直接进行数据的响应。
可以使用下述代码进行数据的响应:
java
ctx.channel().writeAndFlush(响应内容);
这里ctx即为ChannelHandlerContext类,该类的功能可以在Handler方便获取事件处理器的上下文。
四、总结
本文通过集成Netty,可以更容易的实现高性能的异步通信功能,在自定义协议时可以使用分层架构,自定义解码器、编码器、事件处理器,并添加到ch.pipeline中,提高了代码的可维护性和健壮性,相比自己去实现socket通信,这种通信方式是更简单更推荐的。
上面就是所有文章内容了,如果内容对你有帮助,麻烦留一个赞👍和收藏⭐支持一下!
如果你对区块链 内容感兴趣可以查看我的专栏:小试牛刀-区块链
感谢您的关注和收藏!!!!!!
