在使用netty开发和硬件对接时,经常会遇到服务端给硬件设备发送命令后需要等待设备反馈响应命令后执行逻辑。
一、定义同步等待
/**
* @author: 晨光
* @description: 同步等待
* @Version 1.0
*/
public class SyncPromise {
// 用于接收结果
private BaseMessageInfoVo messageResponse;
//CountDownLatch可以看作是一个计数器,当计数器的值减到0时,所有等待的线程将被释放并继续执行。
private final CountDownLatch countDownLatch = new CountDownLatch(1);
// 用于判断是否超时
private boolean isTimeout = false;
/**
* 同步等待返回结果
* timeout 超时时间 unit 时间单位
*/
public BaseMessageInfoVo get(long timeout, TimeUnit unit) throws InterruptedException {
// 等待阻塞,超时时间内countDownLatch减到0,将提前唤醒,以此作为是否超时判断
// 如果在指定时间内计数器仍未归零,则返回false,否则返回true。
boolean earlyWakeUp = countDownLatch.await(timeout, unit);
if(earlyWakeUp) {
// 超时时间内countDownLatch减到0,提前唤醒,说明已有结果
return messageResponse;
} else {
// 超时时间内countDownLatch没有减到0,自动唤醒,说明超时时间内没有等到结果
isTimeout = true;
return null;
}
}
// 计数器清零,唤醒
public void wake() {
countDownLatch.countDown();
}
public BaseMessageInfoVo getMessageResponse() {
return messageResponse;
}
public void setMessageResponse(BaseMessageInfoVo messageResponse) {
this.messageResponse = messageResponse;
}
public boolean isTimeout() {
return isTimeout;
}
}
2、定义工具类发送同步消息
/**
* @author: 晨光
* @description: 定义工具类
* @Version 1.0
*/
public class SyncUtil {
private final static Map<String, SyncPromise> syncPromiseMap = new ConcurrentHashMap<>();
//key 唯一主键类,方便在处理逻辑中找到唤醒
public static BaseMessageInfoVo send(String key,Channel channel,BaseMessageInfoVo messageRequest, long timeout, TimeUnit unit) throws Exception{
if(channel == null) {
throw new NullPointerException("channel");
}
if(messageRequest == null) {
throw new NullPointerException("rpcRequest");
}
if(timeout <= 0) {
throw new IllegalArgumentException("timeout must greater than 0");
}
// 创造一个容器,用于存放当前线程与rpcClient中的线程交互
SyncPromise syncPromise = new SyncPromise();
syncPromiseMap.put(key, syncPromise);
// 发送消息,此处如果发送玩消息并且在get之前返回了结果,下一行的get将不会进入阻塞,也可以顺利拿到结果
channel.writeAndFlush(messageRequest);
// 等待获取结果
BaseMessageInfoVo messageResponse = syncPromise.get(timeout, unit);
if(messageResponse == null) {
if(syncPromise.isTimeout()) {
throw new TimeoutException("等待响应结果超时");
} else{
throw new Exception("其他异常");
}
}
// 移除容器
syncPromiseMap.remove(header+ ChannelMap.getEquipCode(channel));
return messageResponse;
}
public static Map<String, SyncPromise> getSyncPromiseMap(){
return syncPromiseMap;
}
}
通过上面的类即可同步向设备发送命令。
3、在设备返回命令的逻辑中对应唤醒处理逻辑
/**
* 连接管理 handler
*/
@Slf4j
@Service
@ChannelHandler.Sharable
public class SyncRequestHandler extends SimpleChannelInboundHandler<BaseMessageInfoVo> { //ChannelInboundHandlerAdapter
@Override
protected void channelRead0(ChannelHandlerContext ctx, BaseMessageInfoVo resp) throws Exception {
//查询相应key对应的是否有返回,如果有返回就唤醒,直接返回响应数据
SyncPromise syncPromise = SyncUtil.getSyncPromiseMap().get(resp.getHeader()+ChannelMap.getEquipCode(ctx.channel()));
if(syncPromise != null){
//在获取对象不为null时执行唤醒操作,否则直接丢弃
syncPromise.setMessageResponse(resp);
syncPromise.wake();
}
}
}
4、具体的调用发送命令处理逻辑
BaseMessageInfoVo responseInfo = SyncUtil.send(MessageConstant.UPGRADE_PREIX, channel, baseMessageInfoVo, 5, TimeUnit.SECONDS);
System.out.println(JSONObject.toJSONString(responseInfo));//根据具体逻辑进行判断
return ApiResult.success(responseInfo.getHeader()+responseInfo.getData(),"发送成功");