Netty中的udp
使用Netty去实现udp通讯,在netty中支持udp的广播,组播,单播三中模式,本文主要是和硬件进行udp通讯,需要实现一发一收机制,所以使用单播模式进行通讯。
一. 实现服务端
- 该服务类交由Spring进行管理
- PortDefinition为配置文件,进行读取yml配置参数的
- UdpInitHandler为处理器
@Slf4j
@Service
public class UdpListeningServer implements Runnable {
@Autowired
private PortDefinition portDefinition;
@Autowired
private UdpInitHandler udpInitHandler;
private EventLoopGroup bossGroup;
private List<Channel> ioChannels = new ArrayList<Channel>();
@PostConstruct
public void init() {
new Thread(this).start();
}
/**
* 服务监听启动
*/
public void run() {
bossGroup = new NioEventLoopGroup();
try {
Bootstrap bs = new Bootstrap();
bs.group(bossGroup);
bs.channel(NioDatagramChannel.class);
bs.option(ChannelOption.SO_BROADCAST, false);// 不广播
bs.handler(udpInitHandler);
int udpPort = portDefinition.getUdpPort();
// 绑定端口,开始接收进来的连接
Channel ch = bs.bind(udpPort).sync().channel();
ioChannels.add(ch);
log.info("... UDP listening on port:{}", udpPort);
} catch (Exception e) {
throw new BizException("udp服务监听失败:{}", e);
}
}
/**
* 注销、关闭
*/
@PreDestroy
public void destroy() {
//
if (bossGroup != null) {
bossGroup.shutdownGracefully();
}
//
for (Channel ch : ioChannels) {
ch.closeFuture().syncUninterruptibly();
}
}
}
二. 实现Udp处理器
- MantunsciMsgHandler 位消息处理器,由于整个服务端都交由Spring进行管理,所以必须使用注入的方式,不能通过new进行创建
/**
* UDP的处理器初始化
*
* @author xxxx
*
*/
@Component
public class UdpInitHandler extends ChannelInitializer<DatagramChannel> {
@Autowired
private MsMsgHandler msMsgHandler;
@Override
protected void initChannel(DatagramChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ChannelInboundHandlerAdapter());
// 消息UDP解析器
pipeline.addLast(new MsMsgDecoder());
// 消息UDP编码器
pipeline.addLast(new MsMsgEncoder())
// 消息UDP处理器
pipeline.addLast(msMsgHandler);
}
}
三. 实现UDP解析器
- 具体解析内容,按需解析
/**
* UDP消息解析器
*
* @author xxxx
*/
@Slf4j
public class MsMsgDecoder extends MessageToMessageDecoder<DatagramPacket> {
@Override
protected void decode(ChannelHandlerContext ctx, DatagramPacket dataPkg, List<Object> out) throws Exception {
// 获取UDP消息数据包字节流
ByteBuf buf = dataPkg.content();
// 转换协议将协议转换为十六进制,方便排查问题
String msg = Decoder.bytesToString(buf);
// 解析字节流并转换为消息对象
ProtocolMessageDto msgObj = new ProtocolMessageDto
........
}
}
四. 实现UDP编码器
- 具体编码内容,按需编码
/**
* UDP消息编码器
*
* @author xxxx
*/
@Slf4j
public class MsMsgEncoder extends MessageToMessageEncoder<ProtocolMessageDto> {
@Override
protected void encode(ChannelHandlerContext ctx, ProtocolMessageDto msg, List<Object> out) throws Exception {
//起始域
StringBuffer sb = new StringBuffer(msg.getMsf());
//消息ID
sb.append(msg.getCmd());
//消息参数1
sb.append(msg.getCmdVer());
//消息参数2
........
}
}
五. 实现UDP协议消息处理器
- 该处理器主要对解码后的消息进行业务处理
/**
* UDP协议消息处理器
*
* @author xxxx
*/
@Slf4j
@Component
public class MsMsgHandler extends SimpleChannelInboundHandler<ProtocolMessageDto> {
public static Map<String, Channel> getMTS_MAP() {
return MTS_MAP;
}
public static Map<String, String> getMTS_IP_MAP() {
return MTS_IP_MAP;
}
/**
* 用来保存对应的设备MAC-channel
*/
private static final ConcurrentHashMap<String, Channel> MTS_MAP = new ConcurrentHashMap<>();
/**
* 用来保存对应的设备MAC-ip:port
*/
private static final ConcurrentHashMap<String, String> MTS_IP_MAP = new ConcurrentHashMap<>();
/**
* 用来记录网关-服务器间通讯时间
*/
public static final ConcurrentHashMap<String, LocalDateTime> MTS_HEARTBEAT_MAP = new ConcurrentHashMap<>();
public static void closeChannel(String gwMac) {
MTS_MAP.remove(gwMac);
MTS_IP_MAP.remove(gwMac);
MTS_HEARTBEAT_MAP.remove(gwMac);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProtocolMessageDto msgObj) throws Exception {
// 处理从消息解码器传递下来的消息对象,先对消息队形进行判空防止NPE
if (msgObj == null) {
return;
}
MTS_HEARTBEAT_MAP.put(msgObj.getMac(), LocalDateTime.now());
// CMD 命令,为服务器下发命令,服务器再次收到时释放同步锁。无需回复
if (MsCMDConstant.COMMAND.equals(msgObj.getCmd())) {
String key = msgObj.getCmd() + msgObj.getCmdVer();
mtsCorrespondent.confirmSuccess(msgObj, key);
return;
}
MTS_MAP.put(msgObj.getMac(), ctx.channel());
MTS_IP_MAP.put(msgObj.getMac(), msgObj.getIp() + MsConstant.COLON + msgObj.getPort());
// 获取消息对象的DATA属性,
String data = msgObj.getData();
// 根据消息类型区分,参照协议对应消息体约定格式解析具体数据,并转换成数据对象,进行不同的业务逻辑处理
// CMDxxxx配置消息
if (MsCMDConstant.HANDSHAKE_MSG.equals(msgObj.getCmd()) || MsCMDConstant.HEARTBEAT_MSG.equals(msgObj.getCmd())) {
}
// CMD=xxxx消息
else if (MsCMDConstant.PUSH_GATEWAY_CONFIG_MSG.equals(msgObj.getCmd())) {
this.netConfig(data);
}
//回复时都没有回复数据体
msgObj.setData("");
ctx.writeAndFlush(msgObj);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
六. 实现发送逻辑
/**
* @author z26146
* @description: 通信者,负责与硬件通信
* @date 2022-5-12 14:02
*/
@Slf4j
@Component
public class MtsCorrespondent {
/**
* 暂存发送的包 - 用于重复逻辑
*/
private final Map<String, ProtocolMessageDto> receConfirm = new ConcurrentHashMap<>();
/**
* 发送报文数据
*
* @param message 数据信息
* @param isResend 是否重发
*/
public void sendMessage(ProtocolMessageDto message, boolean isResend) {
if (redisClient.exists(RedisKeyConstants.OK_IOT_GATEWAY + message.getMac())) {
// 将对象存入缓存中
String key;
key = message.getCmd() + message.getCmdVer();
// 将key转为大写
key = key.toUpperCase();
// 初始化暂定为 未响应
message.setAck(RespondCodeUtil.RESPON
int count = CommonUtils.NumberUtil.NUM_ZERO;
//开启同步 当命令存在时,进行等待,如果6s后上个命令还没执行完返回设备忙碌
while (receConfirm.containsKey(key)) {
if (count == CommonUtils.NumberUtil.NUM_THREE) {
break;
}
try {
Thread.sleep(CommonUtils.NumberUtil.NUM_2000);
} catch (InterruptedException e) {
log.error("{}", e);
}
count++;
if (count == CommonUtils.NumberUtil.NUM_THREE) {
message.setAck(RespondCodeUtil.DEVICE_BUSY.getCode());
return;
}
int num = isResend ? CommonUtils.NumberUtil.NUM_THREE : CommonUtils.NumberUtil.NUM_ONE;
while (num > CommonUtils.NumberUtil.NUM_ZERO) {
receConfirm.put(key, message);
if (message.getAck() == RespondCodeUtil.RESPONSE_TIMEOUT.getCode()) {
synchronized (message) {
try {
send(message);
//等待网关4秒消息响应
message.wait(CommonUtils.NumberUtil.NUM_4000);
} catch (InterruptedException e) {
log.error("{}", e);
}
}
}
if (message.getAck() == RespondCodeUtil.RESPONSE_TIMEOUT.getCode()) {
//未响应的才需要释放 key value 已响应的key value已被释放
if (message == receConfirm.get(key)) {
receConfirm.remove(key);
}
num--;
} else {
num = CommonUtils.NumberUtil.NUM_ZERO;
}
}
}
log.info("<------ 响应数据结果 :{}", message);
}
/**
* 释放线程
*
* @param message 消息
* @param key 键
*/
public void confirmSuccess(ProtocolMessageDto message, String key) {
ProtocolMessageDto sendMessage;
sendMessage = receConfirm.remove(key.toUpperCase());
// 网关响应成功后,唤醒发送对象
if (sendMessage != null) {
sendMessage.setAck(message.getAck());
sendMessage.setData(message.getData());
synchronized (sendMessage) {
sendMessage.notify();
}
}
}
/**
* 发送报文
*
* @param message 发送报文
*/
protected void send(ProtocolMessageDto message) {
String[] ipp = MsMsgHandler.getMTS_IP_MAP().get(message.getMac()).split(MsConstant.COLON);
message.setIp(ipp[0]);
message.setPort(Integer.parseInt(ipp[1]));
Channel channel = MsMsgHandler.getMTS_MAP().get(message.getMac());
try {
channel.writeAndFlush(message);
} catch (NullPointerException e) {
log.error("发送报文失败:{}", message);
}
}
}