2. SOME/IP-SD服务发现报文抓包分析

任务目标

在SOME/IP服务发现(SD)模块中,实现客户端对服务端事件组的订阅(Subscribe)与取消订阅(Unsubscribe)功能,并正确处理订阅确认(SubscribeAck)与错误响应(SubscribeNack)。本案例聚焦于SD协议层状态机与报文处理逻辑,不涉及具体传输层实现。

核心概念

SOME/IP-SD中,订阅流程使用SubscribeEventgroup报文,服务端以SubscribeEventgroupAck确认或SubscribeEventgroupNack拒绝。客户端需维护订阅状态,处理重试与超时。取消订阅使用UnsubscribeEventgroup报文,服务端以UnsubscribeEventgroupAck确认。

关键实现

以下代码实现SD客户端订阅管理核心逻辑,包含:订阅请求发送、确认/错误处理、取消订阅、超时重试。代码使用C++11编写,不依赖特定操作系统API,便于移植。

#include <cstdint>
#include <cstring>
#include <map>
#include <functional>

// SD报文类型常量
enum SDMessageType : uint8_t {
    SUBSCRIBE = 0x06,
    SUBSCRIBE_ACK = 0x07,
    SUBSCRIBE_NACK = 0x08,
    UNSUBSCRIBE = 0x09,
    UNSUBSCRIBE_ACK = 0x0A
};

// 订阅状态
enum class SubState : uint8_t {
    NOT_SUBSCRIBED = 0,
    SUBSCRIBE_PENDING = 1,
    SUBSCRIBED = 2,
    UNSUBSCRIBE_PENDING = 3
};

// 订阅条目
struct Subscription {
    uint16_t service_id;
    uint16_t instance_id;
    uint16_t eventgroup_id;
    SubState state;
    uint8_t retry_count;
    uint32_t ttl_sec;          // 有效时间(秒)
    uint32_t expiry_timestamp; // 过期时间戳(秒)
};

// SD报文头(简化)
struct SDHeader {
    uint8_t msg_type;
    uint16_t service_id;
    uint16_t instance_id;
    uint16_t eventgroup_id;
    uint32_t ttl_sec;
};

// 回调接口
struct SDCallbacks {
    std::function<void(const Subscription&)> on_subscribed;
    std::function<void(const Subscription&, uint8_t error_code)> on_subscribe_failed;
    std::function<void(const Subscription&)> on_unsubscribed;
};

class SDClient {
public:
    explicit SDClient(SDCallbacks cb) : cb_(std::move(cb)) {}

    // 发送订阅请求
    bool subscribe(uint16_t service_id, uint16_t instance_id,
                   uint16_t eventgroup_id, uint32_t ttl_sec = 30) {
        Subscription sub{service_id, instance_id, eventgroup_id,
                         SubState::SUBSCRIBE_PENDING, 0, ttl_sec, 0};
        auto key = make_key(service_id, instance_id, eventgroup_id);
        if (subs_.count(key) && subs_[key].state == SubState::SUBSCRIBED) {
            return false; // 已订阅
        }
        subs_[key] = sub;
        send_sd_message(SUBSCRIBE, service_id, instance_id, eventgroup_id, ttl_sec);
        return true;
    }

    // 取消订阅
    bool unsubscribe(uint16_t service_id, uint16_t instance_id,
                     uint16_t eventgroup_id) {
        auto key = make_key(service_id, instance_id, eventgroup_id);
        auto it = subs_.find(key);
        if (it == subs_.end() || it->second.state == SubState::NOT_SUBSCRIBED) {
            return false;
        }
        it->second.state = SubState::UNSUBSCRIBE_PENDING;
        send_sd_message(UNSUBSCRIBE, service_id, instance_id, eventgroup_id, 0);
        return true;
    }

    // 处理接收到的SD报文(由网络层调用)
    void handle_sd_message(const SDHeader& hdr) {
        auto key = make_key(hdr.service_id, hdr.instance_id, hdr.eventgroup_id);
        auto it = subs_.find(key);
        if (it == subs_.end()) return;

        Subscription& sub = it->second;
        switch (hdr.msg_type) {
        case SUBSCRIBE_ACK:
            if (sub.state == SubState::SUBSCRIBE_PENDING) {
                sub.state = SubState::SUBSCRIBED;
                sub.ttl_sec = hdr.ttl_sec;
                sub.expiry_timestamp = now_sec() + hdr.ttl_sec;
                if (cb_.on_subscribed) cb_.on_subscribed(sub);
            }
            break;
        case SUBSCRIBE_NACK:
            if (sub.state == SubState::SUBSCRIBE_PENDING) {
                sub.state = SubState::NOT_SUBSCRIBED;
                if (cb_.on_subscribe_failed) cb_.on_subscribe_failed(sub, 0x01);
            }
            break;
        case UNSUBSCRIBE_ACK:
            if (sub.state == SubState::UNSUBSCRIBE_PENDING) {
                subs_.erase(it);
                if (cb_.on_unsubscribed) cb_.on_unsubscribed(sub);
            }
            break;
        default:
            break;
        }
    }

    // 定时器驱动:处理超时重试与过期(每秒调用一次)
    void tick() {
        uint32_t now = now_sec();
        for (auto& kv : subs_) {
            Subscription& sub = kv.second;
            if (sub.state == SubState::SUBSCRIBE_PENDING) {
                if (sub.retry_count < 3) {
                    sub.retry_count++;
                    send_sd_message(SUBSCRIBE, sub.service_id, sub.instance_id,
                                    sub.eventgroup_id, sub.ttl_sec);
                } else {
                    sub.state = SubState::NOT_SUBSCRIBED;
                    if (cb_.on_subscribe_failed) cb_.on_subscribe_failed(sub, 0x02);
                }
            } else if (sub.state == SubState::SUBSCRIBED) {
                if (now >= sub.expiry_timestamp) {
                    sub.state = SubState::NOT_SUBSCRIBED;
                    if (cb_.on_subscribe_failed) cb_.on_subscribe_failed(sub, 0x03);
                }
            }
        }
    }

private:
    static uint64_t make_key(uint16_t s, uint16_t i, uint16_t eg) {
        return (uint64_t)s << 32 | (uint64_t)i << 16 | eg;
    }

    void send_sd_message(uint8_t type, uint16_t s, uint16_t i,
                         uint16_t eg, uint32_t ttl) {
        SDHeader hdr{type, s, i, eg, ttl};
        // 实际工程中在此处调用网络发送接口
        // 本示例仅构造报文,不实际发送
        (void)hdr;
    }

    static uint32_t now_sec() {
        // 简化时间获取,实际应使用系统时间
        static uint32_t counter = 0;
        return ++counter;
    }

    SDCallbacks cb_;
    std::map<uint64_t, Subscription> subs_;
};

验证方法

编写以下测试代码验证订阅状态机转换:

#include <cassert>
#include <iostream>

int main() {
    bool sub_ok = false, unsub_ok = false, nack_ok = false;
    SDCallbacks cb;
    cb.on_subscribed = [&](const Subscription&) { sub_ok = true; };
    cb.on_unsubscribed = [&](const Subscription&) { unsub_ok = true; };
    cb.on_subscribe_failed = [&](const Subscription&, uint8_t) { nack_ok = true; };

    SDClient client(cb);

    // 测试1:正常订阅-确认流程
    client.subscribe(0x1234, 0x5678, 0x01);
    SDHeader ack{SUBSCRIBE_ACK, 0x1234, 0x5678, 0x01, 30};
    client.handle_sd_message(ack);
    assert(sub_ok);

    // 测试2:取消订阅-确认流程
    client.unsubscribe(0x1234, 0x5678, 0x01);
    SDHeader unsub_ack{UNSUBSCRIBE_ACK, 0x1234, 0x5678, 0x01, 0};
    client.handle_sd_message(unsub_ack);
    assert(unsub_ok);

    // 测试3:订阅被拒绝(Nack)
    client.subscribe(0x1111, 0x2222, 0x03);
    SDHeader nack{SUBSCRIBE_NACK, 0x1111, 0x2222, 0x03, 0};
    client.handle_sd_message(nack);
    assert(nack_ok);

    std::cout << "All tests passed" << std::endl;
    return 0;
}

编译与运行:

g++ -std=c++11 -o sd_test sd_test.cpp
./sd_test

通过条件:程序输出All tests passed,且无断言失败。测试覆盖三条核心路径:订阅确认、取消订阅确认、订阅被拒绝。

关键说明

  • send_sd_message函数中仅构造报文头,实际发送需接入网络层(如DoIP或UDP socket),本案例聚焦SD逻辑。
  • 重试策略为最多3次,超时后回调on_subscribe_failed并携带错误码0x02;订阅过期(TTL到期)错误码为0x03。
  • 实际工程中now_sec()应替换为系统时钟(如std::time),此处用计数器模拟以便测试。