Springboot基于Netty实现一个Socket服务,接收TCP报文

939 阅读3分钟

总结:本文传输的tcp报文是16进制数组,在接收转换的时候需要针对性解析;在工作中使用socket传输的不一定是tcp报文,需注意修改解析方法

1、添加pom依赖

        <!-- Spring Boot Webflux 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <!-- Netty 依赖 -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.79.Final</version>
        </dependency>

        <!-- Spring Boot Starter AOP 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2、创建线程

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
public class ThreadPoolConfig {
    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10); // 核心线程数
        executor.setMaxPoolSize(20); // 最大线程数
        executor.setQueueCapacity(100); // 队列容量
        executor.setThreadNamePrefix("NIOSocket-ThreadPool-"); // 线程前缀
        executor.initialize();
        return executor;
    }
}

3、创建连接策略

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class IpRejectHandler extends ChannelInboundHandlerAdapter {

    // 拒绝连接的IP列表
    private static final String[] BLOCKED_IPS = {};

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        String remoteAddress = ctx.channel().remoteAddress().toString();
        if (isBlocked(remoteAddress)) {
            System.out.println("拒绝连接: " + remoteAddress);
            ctx.close();
        } else {
            System.out.println("允许连接: " + remoteAddress);
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        String remoteAddress = ctx.channel().remoteAddress().toString();
        System.out.println("设备已断开连接: " + remoteAddress);
        super.channelInactive(ctx);
    }

    private boolean isBlocked(String remoteAddress) {
        for (String blockedIp : BLOCKED_IPS) {
            if (remoteAddress.contains(blockedIp)) {
                return true;
            }
        }
        return false;
    }
}

4、创建自定义Decoder跟Encoder

import com.hcsr.socketserver.utils.HexUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;

import java.util.List;

public class HexMessageDecoder extends MessageToMessageDecoder<ByteBuf> {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        byte[] bytes = new byte[in.readableBytes()];
        in.readBytes(bytes);
        String hexString = HexUtils.byteArrayToHexString(bytes);
        out.add(hexString);
    }
}
import com.hcsr.socketserver.utils.HexUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

public class HexMessageEncoder extends MessageToByteEncoder<String> {

    @Override
    protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
        byte[] bytes = HexUtils.hexStringToByteArray(msg);
        ByteBuf buffer = Unpooled.wrappedBuffer(bytes);
        out.writeBytes(buffer);
    }
}

5、服务配置

import com.hcsr.socketserver.handler.HexMessageDecoder;
import com.hcsr.socketserver.handler.HexMessageEncoder;
import com.hcsr.socketserver.handler.IpRejectHandler;
import com.hcsr.socketserver.handler.NioSocketHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;


@Component
public class NioSocketInitializer extends ChannelInitializer<SocketChannel> {
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    public NioSocketInitializer(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
        this.threadPoolTaskExecutor = threadPoolTaskExecutor;
    }


    @Override
    protected void initChannel(SocketChannel ch) {
        ChannelPipeline pipeline = ch.pipeline();
        // tcp报文解码器,接收后自动调用解码
        pipeline.addLast(new HexMessageDecoder());
        // tcp报文编码器,应答时自动编码
        pipeline.addLast(new HexMessageEncoder());

        // 如果socket传输的是字符串类型,请使用StringDecoder和StringEncoder
        // pipeline.addLast(new StringDecoder());
        // pipeline.addLast(new StringEncoder());

        // 初始化并添加你的业务处理类
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.initialize(); // 初始化线程池

        // 使用自定义线程池处理业务逻辑
        pipeline.addLast(new NioSocketHandler(threadPoolTaskExecutor));
        // 连接策略
        pipeline.addLast(new IpRejectHandler());
    }
}

6、业务逻辑处理

import com.hcsr.socketserver.domain.entity.Equipment;
import com.hcsr.socketserver.service.IFireEquipmentService;
import com.hcsr.socketserver.utils.HexUtils;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

@Component
public class NioSocketHandler extends SimpleChannelInboundHandler<String> {

    private static IFireEquipmentService fireEquipmentService;

    @Autowired
    public void setFireEquipmentService(IFireEquipmentService fireEquipmentService){
        NioSocketHandler.fireEquipmentService = fireEquipmentService;
    }

    private String hexData = "";

    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;

    public NioSocketHandler(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
        this.threadPoolTaskExecutor = threadPoolTaskExecutor;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        // 异步处理消息
        threadPoolTaskExecutor.execute(() -> {
            // 你的处理逻辑
            String clientId = ctx.channel().id().asShortText();
            System.out.println("接收到用户id为"+clientId+"的消息: " + msg);

            // 应答报文,根据要求应答
            String repeak = msg;
            // 应答
            ctx.writeAndFlush(repeak);

        });
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

7、socket服务启动类

import com.hcsr.socketserver.init.NioSocketInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

@Component
public class NioSocketServer {
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private ChannelFuture channelFuture;

    public void start(int port) throws Exception {
        bossGroup = new NioEventLoopGroup(1); // 主线程组
        workerGroup = new NioEventLoopGroup(); // 工作线程组

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new NioSocketInitializer(threadPoolTaskExecutor))
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            channelFuture = b.bind(port).sync();
            System.out.println("NIO Socket服务器已启动,监听端口:" + port);

            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}

8、启动类启动服务

设置启动端口,启动监听

import com.hcsr.socketserver.server.NioSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SocketserverApplication implements CommandLineRunner {

    @Autowired
    private NioSocketServer nioSocketServer;


    public static void main(String[] args) {
        SpringApplication.run(SocketserverApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        nioSocketServer.start(1883);
    }

}

9、工具类

public class HexUtils {

    public static String byteArrayToHexString(byte[] data) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : data) {
            hexString.append(String.format("%02X ", b));
        }
        return hexString.toString().trim();
    }

    public static byte[] hexStringToByteArray(String s) {
        s = s.replaceAll("\\s", ""); // 去除所有空白字符
        int len = s.length();
        if (len % 2 != 0) {
            throw new IllegalArgumentException("Invalid hexadecimal string length");
        }

        byte[] data = new byte[len / 2];

        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                                 + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

    /**
     * 将16进制字符串转换为ASCII字符串
     * @param hexString
     * @return
     */
    public static String hexStringToAscii(String hexString) {
        byte[] bytes = hexStringToByteArray(hexString);
        return new String(bytes);
    }

    /**
     * 将16进制字符串转换为十进制整型
     * @param hexString
     * @return
     */
    public static long hexStringToDecimal(String hexString) {
        hexString = hexString.replaceAll("\\s", ""); // 去除所有空白字符
        long decimalValue = 0;

        for (int i = 0; i < hexString.length(); i++) {
            char c = hexString.charAt(i);
            int value = Character.digit(c, 16); // 获取当前字符的10进制值
            decimalValue = decimalValue * 16 + value;
        }

        return decimalValue;
    }


    /**
     * 将16进制字符串转换为浮点数
     * @param hexString
     * @return
     */
    public static float hexStringToFloat(String hexString) {
        hexString = hexString.replaceAll("\\s", ""); // 去除所有空白字符
        byte[] bytes = hexStringToByteArray(hexString);
        return bytesToFloat(bytes);
    }

    /**
     * 格式化16进制字符串
     * @param hexString
     * @return
     */
    public static String formatHexString(String hexString) {
        StringBuilder formattedString = new StringBuilder();
        for (int i = 0; i < hexString.length(); i += 2) {
            if (i > 0) {
                formattedString.append(" ");
            }
            formattedString.append(hexString.substring(i, i + 2));
        }

        return formattedString.toString();
    }

    private static float bytesToFloat(byte[] bytes) {
        int intBits = 0;
        for (int i = 0; i < 4; i++) {
            intBits |= (bytes[i] & 0xFF) << (24 - 8 * i);
        }
        return Float.intBitsToFloat(intBits);
    }
}

调试

  • 连接 连接.png

  • 控制台输出: 输出.png

  • 工具发送消息 工具内蓝色文字为发出的消息,绿色文字为应答接收 发送测试消息.png

  • 控制台输出: 输出.png