2. DDS发现机制基础实现
任务目标
实现一个最小化的DDS发现机制:两个参与者(Participant)通过UDP组播发送SPDP(Simple Participant Discovery Protocol)报文,相互发现对方的存在,并建立可用的通信端点信息交换。本案例聚焦于发现阶段,不涉及后续的RTPS可靠传输或QoS策略。
环境与依赖
使用Python 3.8+,仅依赖标准库(socket, threading, json, time)。无需安装第三方DDS库,通过UDP组播模拟DDS发现的核心机制。
实现原理
DDS发现的核心是SPDP:每个参与者启动时向预定义的组播地址(此处用 239.255.0.1:7400)周期性地发送自身信息(参与者GUID、IP、端口),同时监听该组播地址接收其他参与者的宣告。当收到新的参与者信息时,将其记录到本地参与者表中,完成发现。
完整代码
import socket
import threading
import json
import time
import uuid
MCAST_GRP = '239.255.0.1'
MCAST_PORT = 7400
ANNOUNCE_INTERVAL = 2.0 # 宣告周期(秒)
LISTEN_TIMEOUT = 0.5 # 监听超时(秒)
class DDSDiscoveryParticipant:
def __init__(self, name: str):
self.name = name
self.guid = str(uuid.uuid4()) # 全局唯一标识
self.port = 7400 + int(self.guid[:2], 16) % 100 # 模拟数据端口
self.discovered = {} # guid -> {name, port, last_seen}
self.running = False
# 创建组播套接字
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind(('', MCAST_PORT))
mreq = socket.inet_aton(MCAST_GRP) + socket.inet_aton('0.0.0.0')
self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
self.sock.settimeout(LISTEN_TIMEOUT)
def _build_announcement(self) -> bytes:
"""构造SPDP宣告报文"""
info = {
'guid': self.guid,
'name': self.name,
'port': self.port,
'timestamp': time.time()
}
return json.dumps(info).encode('utf-8')
def _send_announcement(self):
"""发送宣告到组播组"""
data = self._build_announcement()
self.sock.sendto(data, (MCAST_GRP, MCAST_PORT))
def _listen_once(self):
"""监听一次组播消息,处理收到的宣告"""
try:
data, addr = self.sock.recvfrom(4096)
except socket.timeout:
return
try:
info = json.loads(data.decode('utf-8'))
guid = info['guid']
if guid != self.guid: # 忽略自己的宣告
self.discovered[guid] = {
'name': info['name'],
'port': info['port'],
'last_seen': time.time()
}
print(f"[{self.name}] 发现参与者: {info['name']} (GUID: {guid[:8]}...)")
except (json.JSONDecodeError, KeyError):
pass # 忽略无效报文
def start(self):
"""启动发现线程"""
self.running = True
thread = threading.Thread(target=self._run, daemon=True)
thread.start()
def _run(self):
"""主循环:周期宣告 + 持续监听"""
while self.running:
self._send_announcement()
# 在宣告间隔内多次监听
end_time = time.time() + ANNOUNCE_INTERVAL
while time.time() < end_time and self.running:
self._listen_once()
def stop(self):
self.running = False
self.sock.close()
def get_discovered_count(self) -> int:
return len(self.discovered)
def run_demo():
"""演示:启动两个参与者,验证相互发现"""
p1 = DDSDiscoveryParticipant("Participant-A")
p2 = DDSDiscoveryParticipant("Participant-B")
p1.start()
p2.start()
# 等待发现完成(最多5秒)
time.sleep(5)
print(f"\n=== 发现结果 ===")
print(f"{p1.name} 发现 {p1.get_discovered_count()} 个参与者")
print(f"{p2.name} 发现 {p2.get_discovered_count()} 个参与者")
# 验证:双方应互相发现
assert p1.get_discovered_count() >= 1, "Participant-A 未发现任何参与者"
assert p2.get_discovered_count() >= 1, "Participant-B 未发现任何参与者"
# 验证发现的是对方
p1_discovered_names = [v['name'] for v in p1.discovered.values()]
p2_discovered_names = [v['name'] for v in p2.discovered.values()]
assert "Participant-B" in p1_discovered_names, "A 未发现 B"
assert "Participant-A" in p2_discovered_names, "B 未发现 A"
print("✓ 验证通过:双方成功相互发现")
p1.stop()
p2.stop()
if __name__ == '__main__':
run_demo()
运行方式
python dds_discovery.py
验证方法与判定标准
运行上述脚本,观察输出:
- 两个参与者启动后,控制台应打印类似
[Participant-A] 发现参与者: Participant-B (GUID: 3f2a1c...)的日志,表明收到了对方的SPDP宣告。 - 最终输出应包含
=== 发现结果 ===以及双方发现数量均为1。 - 程序以
✓ 验证通过:双方成功相互发现结束,退出码为0,说明断言全部通过。
若在真实网络环境中运行,需确保本机防火墙允许UDP组播通信(目标端口7400)。
关键说明
- 本实现使用JSON作为SPDP报文的序列化格式,实际DDS使用CDR编码的二进制格式,但发现逻辑等价。
- 参与者通过
SO_REUSEADDR允许多个实例绑定同一端口,这是组播接收的必要条件。 - 发现结果保存在内存字典中,实际DDS实现会在此基础上建立端到端的通信连接。