总结:本文传输的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);
}
}
调试
-
连接
-
控制台输出:
-
工具发送消息 工具内蓝色文字为发出的消息,绿色文字为应答接收
-
控制台输出: