场景是婴儿培养箱设备有一个固定的ip,java服务区监听这个ip和端口,然后发送固定报文给设备,设备就会发送数据过来,netty server就可以监听该客户端后就可以接收到数据,然后把接收到的数据做解析。
1,添加netty maven依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.7</version>
</dependency>
2,编写netty 启动类
import cn.hutool.cron.CronUtil;
import com.david.analysis.netty.server.NettyServer;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
/**
* 功能描述: 任务队列
* @Author
* @Date
*/
@Component
@Slf4j
public class LaunchRunner implements CommandLineRunner {
@Resource
private NettyServer nettyServer;
@Resource
private SocketProperties socketProperties;
/**
* netty 服务启动 方法
* @param args
* @throws Exception
*/
@Override
public void run(String... args) throws Exception {
//不用的时候 注释掉
TaskRunner();
InetSocketAddress address = new InetSocketAddress(socketProperties.getHost(),socketProperties.getPort());
log.info("netty服务器启动地址:"+socketProperties.getHost());
nettyServer.start(address);
}
/**
* 执行正在运行的任务
*/
private void TaskRunner() {
/**
* 任务队列启动
*/
CronUtil.setMatchSecond(true);
CronUtil.start();
log.info("\n-----------------------任务服务启动------------------------\n\t" +
"当前正在启动的{}个任务"+
"\n-----------------------------------------------------------\n\t"
, CronUtil.getScheduler().size()
);
}
}
3,服务端初始化
import com.david.analysis.netty.server.messge.HexDecoder;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 功能描述: 服务端初始化,客户端与服务器端连接一旦创建,这个类中方法就会被回调,设置出站编码器和入站解码器
*
* @Author
* @Date
*/
public class NettyServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//接收消息格式,使用自定义解析数据格式
pipeline.addLast("decoder",new HexDecoder());
//发送消息格式,使用自定义解析数据格式
// pipeline.addLast("encoder",new HexEncoder());
//针对客户端,如果在1分钟时没有想服务端发送写心跳(ALL),则主动断开
//如果是读空闲或者写空闲,不处理,这里根据自己业务考虑使用
//pipeline.addLast(new IdleStateHandler(600,0,0, TimeUnit.SECONDS));
//自定义的空闲检测
// pipeline.addLast(new ServerHandler());
pipeline.addLast(new NettyServerHandler());
}
}
4,数据转码工具类,因为设备发过来的数据是十六进制的,需要转成十进制
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class HexDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 读取到十六进制字符串
StringBuilder hexString = new StringBuilder();
while (in.isReadable()) {
hexString.append(String.format("%02X", in.readByte()));
}
// 将十六进制字符串转换为字节数组
String hex = hexString.toString();
byte[] bytes = new byte[hex.length() / 2];
for (int i = 0; i < hex.length(); i += 2) {
bytes[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i+1), 16));
}
// 将字节数组添加到输出列表
ByteBuf decoded = ctx.alloc().buffer();
decoded.writeBytes(bytes);
out.add(decoded);
}
}
5,服务端处理类 收到设备数据后的处理
重点是
channelRead方法 netty在收到消息后会调用该方法,然后里面就可以做数据解析后其他业务操作,我在代码里还写了mqtt发送的逻辑,这块 会在其他文章里写,暂时不需要的可以去掉。
import com.alibaba.fastjson.JSONObject;
import com.david.analysis.common.DataAnalysisUtil;
import com.david.analysis.dto.BabyIncubatorYP970DTO;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import io.netty.buffer.ByteBuf;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import java.net.InetSocketAddress;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.eclipse.paho.client.mqttv3.IMqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
/**
* 功能描述: netty服务端处理类
*
* @Author
* @Date
*/
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private static final String MQTT_BROKER = "tcp://xxx:1883"; // MQTT服务器地址 改成自己的ip
private static final String MQTT_TOPIC = "test/data"; // MQTT主题
private static IMqttClient mqttClient;
private static final String USERNAME = "example_user"; // 替换为你的用户名
private static final String PASSWORD = "123456"; // 替换为你的密码
/**
* 功能描述: 有客户端连接服务器会触发此函数
* @Author
* @Date
* @param ctx 通道
* @return void
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
int clientPort = insocket.getPort();
//获取连接通道唯一标识
ChannelId channelId = ctx.channel().id();
//如果map中不包含此连接,就保存连接
if (ChannelMap.getChannelMap().containsKey(channelId)) {
log.info("客户端:{},是连接状态,连接通道数量:{} ",channelId,ChannelMap.getChannelMap().size());
} else {
//保存连接
ChannelMap.addChannel(channelId, ctx.channel());
log.info("客户端:{},连接netty服务器[IP:{}-->PORT:{}]",channelId, clientIp,clientPort);
log.info("连接通道数量: {}",ChannelMap.getChannelMap().size());
}
try {
initMQTTClient(); // 初始化MQTT客户端
} catch (MqttException e) {
throw new RuntimeException(e);
}
// 每隔五秒发送一次数据
scheduler.scheduleAtFixedRate(() -> {
//xxx 替换成自己的报文
String message = "xxxx";
// ByteBuf buffer = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8);
// byte[] bytes = hexStringToByteArrayHaveSpace(message);
// ByteBuf buffer = Unpooled.copiedBuffer(bytes);
byte[] bytes = hexStringToByteArray(message);
ByteBuf buffer = Unpooled.copiedBuffer(bytes);
// ctx.writeAndFlush(buffer);
System.out.println(LocalDateTime.now()+" 向客户端发送数据:"+message);
//使用 ChannelFuture 来检查操作是否成功完成
ChannelFuture future = ctx.writeAndFlush(buffer);
future.addListener(f -> {
if (f.isSuccess()) {
System.out.println("Message sent successfully");
System.out.println("发送的实际内容:"+message );
} else {
f.cause().printStackTrace();
}
});
}, 0, 5, TimeUnit.SECONDS);
}
private static byte[] hexStringToByteArray(String hexString) {
hexString = hexString.replaceAll("\\s", "");
if (hexString.length() % 2 != 0) {
throw new IllegalArgumentException("Invalid hex string length.");
}
int length = hexString.length();
byte[] data = new byte[length / 2];
for (int i = 0; i < length; i += 2) {
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
+ Character.digit(hexString.charAt(i + 1), 16));
}
return data;
}
public static byte[] hexStringToByteArrayHaveSpace(String hexString) {
hexString = hexString.replaceAll("\\s", "");
if (hexString.length() % 2 != 0) {
throw new IllegalArgumentException("Invalid hex string length.");
}
int length = hexString.length();
byte[] data = new byte[length / 2];
for (int i = 0; i < length; i += 2) {
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
+ Character.digit(hexString.charAt(i + 1), 16));
}
return data;
}
public static String byteBufToHexString(ByteBuf byteBuf) {
StringBuilder hexString = new StringBuilder();
while (byteBuf.isReadable()) {
hexString.append(String.format("%02X ", byteBuf.readByte()));
}
return hexString.toString().trim();
}
/**
* Hex字符串转byte
* @param inHex 待转换的Hex字符串
* @return 转换后的byte
*/
public static byte hexToByte(String inHex){
return (byte)Integer.parseInt(inHex,16);
}
/**
* hex字符串转byte数组
* @param inHex 待转换的Hex字符串
* @return 转换后的byte数组结果
*/
public static byte[] hexToByteArray(String inHex){
int hexlen = inHex.length();
byte[] result;
if (hexlen % 2 == 1){
//奇数
hexlen++;
result = new byte[(hexlen/2)];
inHex="0"+inHex;
}else {
//偶数
result = new byte[(hexlen/2)];
}
int j=0;
for (int i = 0; i < hexlen; i+=2){
result[j]=hexToByte(inHex.substring(i,i+2));
j++;
}
return result;
}
/**
* 功能描述: 有客户端终止连接服务器会触发此函数
* @Author
* @Date
* @param ctx 通道处理程序上下文
* @return void
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) {
InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = inSocket.getAddress().getHostAddress();
ChannelId channelId = ctx.channel().id();
//包含此客户端才去删除
if (ChannelMap.getChannelMap().containsKey(channelId)) {
//删除连接
ChannelMap.getChannelMap().remove(channelId);
log.info("客户端:{}, 断开 netty服务器[IP:{}-->PORT:{}]",channelId, clientIp,inSocket.getPort());
log.info("连接通道数量: " + ChannelMap.getChannelMap().size());
}
// 客户端断开连接时,取消定时任务
scheduler.shutdownNow();
}
private static void initMQTTClient() throws MqttException {
mqttClient = new MqttClient(MQTT_BROKER, MqttClient.generateClientId());
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true);
options.setUserName(USERNAME); // 设置用户名
options.setPassword(PASSWORD.toCharArray()); // 设置密码
mqttClient.connect(options);
}
/**
* 功能描述: 有客户端发消息会触发此函数
* @Author
* @Date
* @param ctx 通道处理程序上下文
* @param msg 客户端发送的消息
* @return void
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] receivedBytes = new byte[buf.readableBytes()];
buf.readBytes(receivedBytes);
String receivedHex = bytesToHex(receivedBytes);
System.out.println(LocalDateTime.now()+" Received Hex: " + receivedHex);
//开始解析 接收到的报文
BabyIncubatorYP970DTO babyIncubatorYP970=DataAnalysisUtil.analysisData(receivedHex);
String babyIncubatorYP970Str = JSONObject.toJSONString(babyIncubatorYP970);
System.out.println("解析结果:"+babyIncubatorYP970Str);
// 发送数据到MQTT服务器
sendToMQTT(babyIncubatorYP970Str);
buf.release();
log.info("加载客户端报文,客户端id:{},客户端消息:{}",ctx.channel().id(), msg);
// String data = String.valueOf(msg);
Integer water = Integer.parseInt(data.substring(6,10),16);
// //十六进制转 十进制
// Integer water = Integer.parseInt(data,16);
// log.info("当前数据:{} ",water);
//响应客户端
//this.channelWrite(ctx.channel().id(), msg);
}
private void sendToMQTT(String message) {
try {
MqttMessage mqttMessage = new MqttMessage(message.getBytes());
mqttClient.publish(MQTT_TOPIC, mqttMessage);
System.out.println("Published to MQTT topic: " + MQTT_TOPIC);
} catch (MqttException e) {
e.printStackTrace();
}
}
private String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
hexString.append(String.format("%02X", b)+" ");
}
return hexString.toString();
}
/* @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
String bytes = "01 03 00 02 00 01 25 CA";
ctx.writeAndFlush(bytes);
}*/
/**
* 功能描述: 服务端给客户端发送消息
* @Author
* @Date
* @param channelId 连接通道唯一id
* @param msg 需要发送的消息内容
* @return void
*/
public void channelWrite(ChannelId channelId, Object msg) throws Exception {
Channel channel = ChannelMap.getChannelMap().get(channelId);
if (channel == null) {
log.info("通道:{},不存在",channelId);
return;
}
if (msg == null || msg == "") {
log.info("服务端响应空的消息");
return;
}
//将客户端的信息直接返回写入ctx
channel.write(msg);
//刷新缓存区
channel.flush();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
String socketString = ctx.channel().remoteAddress().toString();
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.READER_IDLE) {
log.info("Client:{},READER_IDLE 读超时",socketString);
ctx.disconnect();
Channel channel = ctx.channel();
ChannelId id = channel.id();
ChannelMap.removeChannelByName(id);
} else if (event.state() == IdleState.WRITER_IDLE) {
log.info("Client:{}, WRITER_IDLE 写超时",socketString);
ctx.disconnect();
Channel channel = ctx.channel();
ChannelId id = channel.id();
ChannelMap.removeChannelByName(id);
} else if (event.state() == IdleState.ALL_IDLE) {
log.info("Client:{},ALL_IDLE 总超时",socketString);
ctx.disconnect();
Channel channel = ctx.channel();
ChannelId id = channel.id();
ChannelMap.removeChannelByName(id);
}
}
}
/**
* 功能描述: 发生异常会触发此函数
* @Author
* @Date
* @param ctx 通道处理程序上下文
* @param cause 异常
* @return void
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
log.info("{}:发生了错误,此连接被关闭。此时连通数量:{}",ctx.channel().id(),ChannelMap.getChannelMap().size());
}
}
6,配置读取类
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* 功能描述: 配置类
*
* @Author
* @Date
*/
@Setter
@Getter
@ToString
@Component
@Configuration
@PropertySource("classpath:application.yml")
@ConfigurationProperties(prefix = "socket")
public class SocketProperties {
private Integer port;
private String host;
}
yml文件配置内容:
socket:
# 监听端口 8090
port: 8081
#ip地址
host: xxxx 替换自己的ip
如果没有设备的话 可以参考下面的netty client代码,先自己模拟设备给服务端发数据
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class NettyClient {
private final String host;
private final int port;
public NettyClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new YourClientHandler());
}
});
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new NettyClient("模拟设备ip", 8081).start();
}
}
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
public class YourClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelActive(ChannelHandlerContext ctx) {
// 发送测试消息 这里的xxxx是设备的发送报文根据实际情况填写
// ByteBuf message = Unpooled.copiedBuffer("xxxx", CharsetUtil.UTF_8);
String msg="xxxxx";
byte[] bytes = hexStringToByteArrayHaveSpace(msg);
ByteBuf buffer = Unpooled.copiedBuffer(bytes);
ChannelFuture future = ctx.writeAndFlush(buffer);
future.addListener(f -> {
if (f.isSuccess()) {
System.out.println("Client message sent successfully.");
} else {
System.err.println("Failed to send client message: " + f.cause());
}
});
}
public static byte[] hexStringToByteArrayHaveSpace(String hexString) {
hexString = hexString.replaceAll("\\s", "");
if (hexString.length() % 2 != 0) {
throw new IllegalArgumentException("Invalid hex string length.");
}
int length = hexString.length();
byte[] data = new byte[length / 2];
for (int i = 0; i < length; i += 2) {
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
+ Character.digit(hexString.charAt(i + 1), 16));
}
return data;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("Received message from server: " + msg.toString(CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
其他依赖的maven就不一一贴了 网上都有 如果实在有不太好确定的可以留言 我会贴出来