SpringBoot集成Netty实战:构建高效TCPUDP通信服务端【物联网开发必备】

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指令。

下载NetAssist

NetAssist

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

扩展阅读和资源链接

通过本篇文章您能学到什么

通过阅读本篇文章,您将获得以下知识和技能:

  1. Netty与SpringBoot集成的理解:您将了解如何将Netty框架集成到SpringBoot应用中,以及这种集成如何简化物联网通信的开发。

  2. TCP/UDP通信协议的实现:您将学会如何在Netty中实现TCP和UDP通信协议,包括如何配置服务器以及如何处理网络数据。

  3. 自定义通信协议的设计:您将掌握如何设计自定义的通信协议,包括数据包的结构和字段定义,以及如何确保数据的完整性和命令的明确性。

  4. 数据编码与解码:您将学习如何将数据编码为Hex格式进行传输,以及如何将接收到的Hex数据解码回原始格式。

  5. 网络编程实践:您将获得实际的网络编程经验,包括如何处理网络连接、数据传输和异常情况。

  6. 测试工具的使用:您将学会如何使用NetAssist这样的网络调试助手来模拟设备发送TCP/UDP指令,以及如何进行网络通信的测试。

  7. 问题解决技巧:您将了解一些调试技巧和常见问题的解决方案,帮助您在开发过程中快速定位和解决问题。

  8. 代码阅读与理解:通过分析和理解文章中提供的代码示例,您将提高代码阅读能力,这对于学习和工作中的代码审查都是非常有益的。

  9. 高性能网络应用开发:您将掌握使用Netty开发高性能网络应用的技巧,这对于需要处理大量并发网络连接的应用尤为重要。

  10. 资源链接与扩展学习:文章最后提供的资源链接将引导您进行更深入的学习,无论是Netty的高级特性还是SpringBoot的最佳实践,您都能找到进一步学习的途径。

通过本篇文章的学习,您不仅能够获得理论知识,还能够通过实践加深理解,为将来在物联网领域的网络通信开发打下坚实的基础。

你掌握了那些或遇到那些问题,欢迎评论留言进行讨论!!!

Spring Boot 是一个开源的Java开发框架,用于开发微服务和基于RESTful架构的应用。Netty 是一个用于构建高性能、事件驱动的网络应用程序的Java框架。Netty 提供了TCP 和 UDP 传输协议的支持,因此可以方便地使用Netty构建TCP客户端。 在Spring Boot中使用Netty构建TCP客户端的步骤如下: 1. 在pom.xml文件中添加Netty的依赖项: ``` <dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>x.x.x</version> </dependency> </dependencies> ``` 2. 创建一个TCP客户端的处理器类,继承Netty的SimpleChannelInboundHandler抽象类,重写相应的方法来处理数据。 ``` public class TcpClientHandler extends SimpleChannelInboundHandler<String> { @Override public void channelActive(ChannelHandlerContext ctx) { // 在连接建立时发送数据 ctx.writeAndFlush("Hello Server!"); } @Override public void channelRead0(ChannelHandlerContext ctx, String message) { // 处理接收到的数据 System.out.println("Received from server: " + message); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 处理异常情况 cause.printStackTrace(); ctx.close(); } } ``` 3. 创建一个Spring Boot的启动类,并在其中配置Netty的相关参数和创建TCP客户端的Bootstrap实例。 ``` @SpringBootApplication public class TcpClientApplication { public static void main(String[] args) { SpringApplication.run(TcpClientApplication.class, args); } @Bean public CommandLineRunner tcpClientRunner() { return args -> { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new TcpClientHandler()); } }); ChannelFuture future = bootstrap.connect("localhost", 8888).sync(); future.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } }; } } ``` 以上就是使用Spring BootNetty构建TCP客户端的基本步骤。通过以上配置,可以编写相应的业务逻辑来与服务器进行通信,实现相关功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值