4. SOME/IP服务发现重传机制
任务目标
在SOME/IP服务发现(SD)中,服务提供者通过发送OfferService报文宣告服务可用,服务消费者通过发送FindService报文搜索服务。由于UDP传输不可靠,报文可能丢失,因此需要定时重传机制确保服务信息可靠传播。本案例实现一个最小可复现的SD重传管理器,支持OfferService与FindService报文的定时重传,并验证重传次数与间隔符合预期。
核心实现
以下代码实现一个轻量级重传管理器,核心逻辑包括:报文发送记录(跟踪已发送但未确认的报文)、定时重传调度(按初始间隔与最大重传次数执行)、确认取消机制(收到匹配响应后停止重传)。为聚焦重传逻辑本身,不引入完整SOME/IP序列化,使用简化报文结构体表示关键字段。
import time
import threading
from dataclasses import dataclass
from enum import Enum
from typing import Optional, Callable, Dict
class MessageType(Enum):
OFFER_SERVICE = 0x01
FIND_SERVICE = 0x02
@dataclass
class SDMessage:
msg_type: MessageType
service_id: int
instance_id: int
session_id: int
class RetransmissionManager:
"""SOME/IP SD 报文定时重传管理器"""
def __init__(self, initial_delay: float = 1.0, max_retries: int = 3,
send_callback: Optional[Callable[[SDMessage], None]] = None):
self.initial_delay = initial_delay # 初始重传间隔(秒)
self.max_retries = max_retries # 最大重传次数
self.send_callback = send_callback # 实际发送报文的回调
self._records: Dict[int, dict] = {} # session_id -> 记录
self._lock = threading.Lock()
self._stop_event = threading.Event()
self._threads: list[threading.Thread] = []
def send_message(self, msg: SDMessage) -> None:
"""首次发送报文并启动重传定时器"""
with self._lock:
self._records[msg.session_id] = {
'msg': msg,
'retries_left': self.max_retries,
'delay': self.initial_delay
}
# 立即发送首次报文
if self.send_callback:
self.send_callback(msg)
# 启动重传线程
t = threading.Thread(target=self._retransmit_loop, args=(msg.session_id,), daemon=True)
self._threads.append(t)
t.start()
def confirm_received(self, session_id: int) -> None:
"""收到对应响应(如OfferService确认),取消重传"""
with self._lock:
self._records.pop(session_id, None)
def _retransmit_loop(self, session_id: int) -> None:
"""重传循环:按间隔重发,直到确认或达到最大次数"""
while not self._stop_event.is_set():
with self._lock:
record = self._records.get(session_id)
if record is None:
return # 已确认,停止
if record['retries_left'] <= 0:
self._records.pop(session_id, None)
return # 重传次数耗尽
record['retries_left'] -= 1
delay = record['delay']
msg = record['msg']
# 等待间隔时间(可被停止事件中断)
if self._stop_event.wait(delay):
return
# 执行重传
if self.send_callback:
self.send_callback(msg)
def stop(self) -> None:
"""停止所有重传线程"""
self._stop_event.set()
for t in self._threads:
t.join(timeout=0.5)
self._threads.clear()
# ========== 验证示例 ==========
if __name__ == '__main__':
sent_log = []
def mock_send(msg: SDMessage):
sent_log.append((time.time(), msg))
print(f"[发送] {msg.msg_type.name} service={msg.service_id} "
f"session={msg.session_id} retries_left={msg.session_id}")
# 场景1:OfferService 重传3次后放弃(无确认)
mgr = RetransmissionManager(initial_delay=0.1, max_retries=3, send_callback=mock_send)
offer = SDMessage(MessageType.OFFER_SERVICE, service_id=0x1234, instance_id=0x01, session_id=1)
mgr.send_message(offer)
time.sleep(0.5) # 等待重传完成
mgr.stop()
offer_count = sum(1 for _, m in sent_log if m.msg_type == MessageType.OFFER_SERVICE)
print(f"\nOfferService 实际发送次数: {offer_count} (预期: 4 = 首次 + 3次重传)")
# 场景2:FindService 收到确认后停止重传
sent_log.clear()
mgr2 = RetransmissionManager(initial_delay=0.1, max_retries=5, send_callback=mock_send)
find = SDMessage(MessageType.FIND_SERVICE, service_id=0x5678, instance_id=0x02, session_id=2)
mgr2.send_message(find)
time.sleep(0.15) # 等待首次重传前
mgr2.confirm_received(session_id=2) # 模拟收到响应
time.sleep(0.3)
mgr2.stop()
find_count = sum(1 for _, m in sent_log if m.msg_type == MessageType.FIND_SERVICE)
print(f"FindService 实际发送次数: {find_count} (预期: 1,确认后不再重传)")
运行与验证
运行方式:直接执行 python sd_retransmission.py(需Python 3.7+,无第三方依赖)。
验证方法:观察输出中的发送日志与统计行:
- 场景1:OfferService应打印4次发送(首次+3次重传),统计行显示
实际发送次数: 4,证明无确认时按最大重传次数执行后自动停止。 - 场景2:FindService应仅打印1次发送,统计行显示
实际发送次数: 1,证明收到确认后重传被正确取消。
若两个场景统计值分别等于4和1,则重传机制验证通过。可通过修改initial_delay与max_retries参数观察重传间隔与次数变化。
关键设计说明
- 重传策略:采用固定间隔重传(非指数退避),简化实现;实际SOME/IP协议中可扩展为按倍数递增间隔。
- 线程安全:使用
threading.Lock保护记录字典,避免并发访问冲突。 - 停止机制:
stop()通过事件通知所有重传线程退出,避免程序无法结束。 - 确认匹配:本示例以
session_id作为确认匹配键,实际协议中应结合service_id与instance_id。