UnetAgent邻居发现实现报错:MAC预留请求被拒绝问题排查
你的代码存在的核心问题
错误注册服务:你在
setup()里调用register(Services.MAC)等方法,这是完全错误的逻辑。register()是用来让你的Agent对外提供指定服务的,但Unet已经内置了MAC、DATAGRAM、ROUTE_MAINTENANCE的官方Agent。你注册这些服务会导致系统服务冲突,这就是"mac reservation request was denied: null"错误的根源——系统无法确定该使用哪个MAC服务来处理资源预约。同步阻塞的事件处理:在
startup()里直接调用discoverNodes(),而方法里用while(receive(...))做同步阻塞循环。Unet Agent是事件驱动模型,所有消息处理依赖单线程事件循环,这个阻塞操作会占用线程,导致MAC等服务的初始化消息无法及时处理,进一步加剧预约失败的问题。邻居实现方式不够优雅:手动构造
RouteDiscoveryReq发送到不存在的节点来触发邻居发现,属于绕路的做法,Unet本身提供了更直接的邻居维护机制。
修正后的代码(解决错误版本)
先修正核心错误,保留你原有的发现逻辑,但修复注册和阻塞问题:
@Override protected void setup() { this.nodeList = new ArrayList<>(); this.currentNodeIndex = 0; // 移除所有register调用——你是要使用服务,不是提供服务 } @Override protected void startup() { this.phy = agentForService(Services.PHYSICAL); if (this.phy == null) { this.log.warning("No PHY found, carrier sensing disabled!"); } else { this.subscribe(this.topic(this.phy, "snoop")); } // 异步触发邻居发现,避免阻塞事件循环 addTask(this::discoverNodes); } private void discoverNodes() { int attempts = 2; int phantom = 132; int timeout = 10000; AgentID rdp = agentForService(Services.ROUTE_MAINTENANCE); if (rdp == null) { log.warning("Route maintenance service not available!"); return; } this.subscribe(this.topic(rdp), RouteDiscoveryNtf.class); RouteDiscoveryReq req = new RouteDiscoveryReq(phantom); req.setCount(attempts); rdp.send(req); Message ntf; while ((ntf = receive(RouteDiscoveryNtf.class, timeout)) != null) { int neighbor = ((RouteDiscoveryNtf) ntf).getNextHop(); this.log.warning(" Discovered neighbor: " + neighbor); nodeList.add(neighbor); } nodeList = nodeList.stream().distinct().collect(Collectors.toList()); this.log.warning("Neighbors are " + nodeList); NUM_NODES = nodeList.size(); }
更简便的实现方式(利用Unet内置能力)
Unet的RouteMaintenanceAgent本身会自动维护邻居列表,你只需要订阅相关通知即可,无需手动发起发现请求:
@Override protected void setup() { this.nodeList = new ArrayList<>(); this.currentNodeIndex = 0; } @Override protected void startup() { AgentID routeAgent = agentForService(Services.ROUTE_MAINTENANCE); if (routeAgent == null) { log.warning("Route maintenance service not found!"); return; } // 订阅邻居列表更新通知 subscribe(topic(routeAgent, "neighborList"), NeighborListNtf.class, this::updateNeighborList); // 订阅新邻居发现通知 subscribe(topic(routeAgent, "neighborDiscovery"), NeighborDiscoveryNtf.class, this::onNewNeighbor); // 主动请求一次当前的邻居列表 routeAgent.send(new GetNeighborListReq()); } private void updateNeighborList(NeighborListNtf ntf) { nodeList.clear(); nodeList.addAll(ntf.getNeighbors()); nodeList = nodeList.stream().distinct().collect(Collectors.toList()); log.warning("Updated neighbor list: " + nodeList); NUM_NODES = nodeList.size(); } private void onNewNeighbor(NeighborDiscoveryNtf ntf) { int neighbor = ntf.getNeighbor(); if (!nodeList.contains(neighbor)) { nodeList.add(neighbor); log.warning("New neighbor found: " + neighbor); NUM_NODES = nodeList.size(); } }
为什么单独Groovy脚本可以运行
Shell里的Groovy脚本不会注册冲突的服务,而且脚本的receive操作是在Shell独立的线程中执行,不会阻塞Unet内置Agent的事件循环,因此MAC服务能正常完成资源预约并处理邻居发现请求。
内容的提问来源于stack exchange,提问作者Bhavya Mehta

