SpringBoot 集成Netty实现TCP/UDP通信协议【优化版】
引言
在现代物联网(IoT)应用中,设备与服务器之间的实时通信至关重要。Netty作为一个高性能的网络应用框架,与SpringBoot的集成可以简化开发过程,并提高应用性能。本文将详细介绍如何在SpringBoot中集成Netty,实现TCP和UDP通信协议。
通讯协议
在设计通讯协议时,我们考虑了数据的完整性和命令的明确性。以下是我们的协议设计:
字段名
字节数组下标
字节长度
说明
flag
byte[0]-byte[3]
4byte
固定包头标识,使用"9527"作为固定包头
cmd
byte[4]
1byte
通讯命令
operate
byte[5]
1byte
操作,11-发送请求,需要回复;12-发送请求,不需要回复;21-回复操作成功,22-回复操作失败
length
byte[6]-byte[7]
2byte
数据长度(从包头固定标识开始计算,总字节数)
msgNo
byte[8]
1byte
消息编号(1-255)
核心代码
3.1 Netty 服务器
package com.ljq.demo.springboot.netty.server.init;
import com.ljq.demo.springboot.netty.server.handler.UdpRadarNettyServerHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.net.InetSocketAddress;
/**
* 初始化雷达UDP服务
*/
@Slf4j
@Component
public class UdpRadarNettyServer implements ApplicationRunner {
@Value("${netty.portUdpRadar:9135}")
private Integer nettyPort;
@Resource
private UdpRadarNettyServerHandler udpRadarNettyServerHandler;
private Channel channel;
@Override
public void run(ApplicationArguments args) throws Exception {
this.start();
}
/**
* 启动服务
*/
public void start() throws InterruptedException {
EventLoopGroup mainGroup = new NioEventLoopGroup(2);
EventLoopGroup workGroup = new NioEventLoopGroup(8);
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(mainGroup)
.channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST, true)
.option(ChannelOption.SO_RCVBUF, 1024 * 1024 * 10)
.option(ChannelOption.SO_SNDBUF, 1024 * 1024 * 10)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.localAddress(new InetSocketAddress(nettyPort))
.handler(new ChannelInitializer<NioDatagramChannel>() {
@Override
protected void initChannel(NioDatagramChannel nioDatagramChannel) throws Exception {
nioDatagramChannel.pipeline()
.addLast(workGroup, udpRadarNettyServerHandler);
}
});
ChannelFuture future = bootstrap.bind().sync();
this.channel = future.channel();
log.info("---------- [init] UDP Radar netty server start, port:{} ----------", nettyPort);
}
public Channel getChannel() {
return this.channel;
}
}
3.2 Netty 服务端工具类
package com.ljq.demo.springboot.netty.server.util;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.ljq.demo.springboot.netty.server.init.UdpRadarNettyServer;
import com.ljq.demo.springboot.netty.server.model.response.UdpRadarHeader;
import io.netty.buffer.Unpooled;
import io.netty.channel.socket.DatagramPacket;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
import java.util.Objects;
/**
* UDP雷达服务端工具类
*/
@Slf4j
public class UdpRadarServerUtil {
public static final int HEADER_BYTE_LENGTH = 9;
public static final int HEADER_DATA_LENGTH = 18;
public static final int MSG_MAX_LENGTH = 1000;
public static final String HEADER_FLAG = "39353237";
private UdpRadarServerUtil() {
}
/**
* 校验服务端接收到的数据是否合法
*
* @param hexMsg 十六进制消息
* @return 是否合法
*/
public static boolean validate(String hexMsg) {
if (StrUtil.isBlank(hexMsg) || hexMsg.length() < HEADER_DATA_LENGTH
|| hexMsg.length() > MSG_MAX_LENGTH || !hexMsg.startsWith(HEADER_FLAG)) {
return false;
}
return true;
}
/**
* 解析请求头
*
* @param hexMsg 十六进制消息
* @return 请求头对象
*/
public static UdpRadarHeader parseHeader(String hexMsg) {
UdpRadarHeader header = new UdpRadarHeader();
header.setFlag(HexUtil.decodeHexStr(hexMsg.substring(0, 8)));
header.setCmd(HexUtil.hexToInt(hexMsg.substring(8, 10)));
header.setOperate(HexUtil.hexToInt(hexMsg.substring(10, 12)));
header.setLength(HexUtil.hexToInt(hexMsg.substring(12, 16)));
header.setMsgNo(HexUtil.hexToInt(hexMsg.substring(16, 18)));
return header;
}
/**
* 生成发送数据(仅请求头)
*
* @param header 请求头对象
* @return 十六进制数据
*/
public static String getHexData(UdpRadarHeader header) {
StringBuilder dataBuilder = new StringBuilder();
dataBuilder.append(HexUtil.encodeHexStr(header.getFlag()));
dataBuilder.append(String.format("%02x", header.getCmd()));
dataBuilder.append(String.format("%02x", header.getOperate()));
dataBuilder.append(String.format("%04x", header.getLength()));
dataBuilder.append(String.format("%02x", header.getMsgNo()));
return dataBuilder.toString();
}
/**
* 生成发送数据(包含业务数据)
*
* @param header 请求头对象
* @param data 业务数据
* @return 十六进制数据
*/
public static String getHexData(UdpRadarHeader header, String data) {
StringBuilder dataBuilder = new StringBuilder(getHexData(header));
if (StrUtil.isNotBlank(data)) {
dataBuilder.append(HexUtil.encodeHexStr(data));
}
return dataBuilder.toString();
}
/**
* 向客户端发送数据
*
* @param clientIp 客户端IP
* @param clientPort 客户端端口
* @param hexMsg 十六进制消息
*/
public static void sendData(String clientIp, int clientPort, String hexMsg) {
UdpRadarNettyServer udpRadarNettyServer = SpringUtil.getBean(UdpRadarNettyServer.class);
if (Objects.isNull(udpRadarNettyServer)) {
log.warn("UDP Radar Server is not running");
return;
}
DatagramPacket sendPacket = new DatagramPacket(Unpooled.copiedBuffer(Convert.hexToBytes(hexMsg)),
new InetSocketAddress(clientIp, clientPort));
udpRadarNettyServer.getChannel().writeAndFlush(sendPacket);
}
/**
* 向客户端发送数据
*
* @param clientId 客户端ID
* @param clientPort 客户端端口
* @param header 请求头对象
* @param data 业务数据
*/
public static void sendData(String clientId, int clientPort, UdpRadarHeader header, String data) {
String hexMsg = getHexData(header, data);
sendData(clientId, clientPort, hexMsg);
}
}
3.3 Netty 服务处理器
package com.ljq.demo.springboot.netty.server.handler;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.RandomUtil;
import com.ljq.demo.springboot.netty.server.model.response.UdpRadarHeader;
import com.ljq.demo.springboot.netty.server.util.UdpRadarServerUtil;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util
.concurrent.TimeUnit;
/**
* UDP雷达服务端处理器
*/
@Slf4j
@Component
@ChannelHandler.Sharable
public class UdpRadarNettyServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {
private final ExecutorService executorService = new ThreadPoolExecutor(4, 8, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10000), new DefaultThreadFactory("UDP-Radar-work-pool"),
new ThreadPoolExecutor.CallerRunsPolicy());
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, DatagramPacket packet) throws Exception {
String hexMsg = ByteBufUtil.hexDump(packet.content()).toUpperCase();
if (!UdpRadarServerUtil.validate(hexMsg)) {
log.info("invalid data");
return;
}
InetSocketAddress clientAddress = packet.sender();
executorService.execute(() -> {
UdpRadarHeader header = UdpRadarServerUtil.parseHeader(hexMsg);
log.info("UDP Radar server receive msg,client: {},cmd:{},operate:{}", clientAddress.getHostString(),
header.getCmd(), header.getOperate());
String responseHexMsg = demoService(hexMsg);
if (Objects.equals(11, header.getOperate())) {
UdpRadarServerUtil.sendData(clientAddress.getHostString(), clientAddress.getPort(), responseHexMsg);
}
});
}
/**
* 示例业务方法
*
* @param hexMsg 十六进制消息
* @return 响应的十六进制消息
*/
private String demoService(String hexMsg) {
UdpRadarHeader header = UdpRadarServerUtil.parseHeader(hexMsg);
UdpRadarHeader responseHeader = new UdpRadarHeader();
responseHeader.setCmd(header.getCmd());
responseHeader.setOperate(21);
responseHeader.setLength(UdpRadarServerUtil.HEADER_BYTE_LENGTH + 5);
responseHeader.setMsgNo(header.getMsgNo());
StringBuilder responseHexMsgBuilder = new StringBuilder();
responseHexMsgBuilder.append(UdpRadarServerUtil.getHexData(responseHeader));
responseHexMsgBuilder.append(String.format("%02x", RandomUtil.randomInt(0, 128)));
responseHexMsgBuilder.append(HexUtil.encodeHexStr(RandomUtil.randomString(4)));
return responseHexMsgBuilder.toString();
}
}
3.4 协议请求头封装类
package com.ljq.demo.springboot.netty.server.model.response;
import lombok.Data;
import java.io.Serializable;
/**
* UDP雷达请求头参数
*/
@Data
public class UdpRadarHeader implements Serializable {
private static final long serialVersionUID = 4765355155562316019L;
/**
* 固定包头标识(4byte)
*/
private String flag = "9527";
/**
* 命令(1byte)
*/
private Integer cmd;
/**
* 操作,11-发送请求,需要回复;12-发送请求,不需要回复;21-回复操作成功,22-回复操作失败(1byte)
*/
private Integer operate;
/**
* 数据长度(从包头固定标识开始计算,总字节数)(2byte)
*/
private Integer length;
/**
* 消息编号(1-255)(1byte)
*/
private Integer msgNo;
}
测试
4.1 测试工具
推荐使用NetAssist网络调试助手来模拟设备发送TCP/UDP指令。
4.2 测试数据
原始发送数据:
{
"flag": "9527",
"cmd": 20,
"operate": 11,
"length": 9,
"msgNo": 85
}
编码后的发送数据:
39353237140b000955
后台日志:
2024-08-20 09:52:41 | INFO | UDP-Radar-work-pool-3-1 | com.ljq.demo.springboot.netty.server.handler.UdpRadarNettyServerHandler 55| UDP Radar server receive msg,client: 192.168.15.32,cmd:20,operate:11
扩展阅读和资源链接
通过本篇文章您能学到什么
通过阅读本篇文章,您将获得以下知识和技能:
-
Netty与SpringBoot集成的理解:您将了解如何将Netty框架集成到SpringBoot应用中,以及这种集成如何简化物联网通信的开发。
-
TCP/UDP通信协议的实现:您将学会如何在Netty中实现TCP和UDP通信协议,包括如何配置服务器以及如何处理网络数据。
-
自定义通信协议的设计:您将掌握如何设计自定义的通信协议,包括数据包的结构和字段定义,以及如何确保数据的完整性和命令的明确性。
-
数据编码与解码:您将学习如何将数据编码为Hex格式进行传输,以及如何将接收到的Hex数据解码回原始格式。
-
网络编程实践:您将获得实际的网络编程经验,包括如何处理网络连接、数据传输和异常情况。
-
测试工具的使用:您将学会如何使用NetAssist这样的网络调试助手来模拟设备发送TCP/UDP指令,以及如何进行网络通信的测试。
-
问题解决技巧:您将了解一些调试技巧和常见问题的解决方案,帮助您在开发过程中快速定位和解决问题。
-
代码阅读与理解:通过分析和理解文章中提供的代码示例,您将提高代码阅读能力,这对于学习和工作中的代码审查都是非常有益的。
-
高性能网络应用开发:您将掌握使用Netty开发高性能网络应用的技巧,这对于需要处理大量并发网络连接的应用尤为重要。
-
资源链接与扩展学习:文章最后提供的资源链接将引导您进行更深入的学习,无论是Netty的高级特性还是SpringBoot的最佳实践,您都能找到进一步学习的途径。
通过本篇文章的学习,您不仅能够获得理论知识,还能够通过实践加深理解,为将来在物联网领域的网络通信开发打下坚实的基础。
你掌握了那些或遇到那些问题,欢迎评论留言进行讨论!!!