EOL总结:SOME/IP 笔记

一、SOMEIP 知识

1、SOME/IP的报文格式

参考链接1:https://mp.weixin.qq.com/s?__biz=MzI0NTU1NDQ3Mw==&mid=2247483718&idx=1&sn=35ec9b655c6d20b9ea14972133a2ce28&chksm=e94d8d00de3a04162fd1acf8eb1dd3300266cc5be77fb66b018a882dd7274ba5a71f1347ab41&scene=21#wechat_redirect # SOME/IP 协议介绍

SOME/IP的报文由 消息 ID Message ID(Service ID / Method ID)[32 bit] + 消息长度 Length[32 bit] + 请求 ID Request ID(Client ID / Session ID)[32 bit] + 协议版本 Protocol Version[8 bit] + 接口版本 Interface Version[8 bit] + 消息类型 Message Type[8 bit] + 返回码 Return Code[8 bit] + 消息体 Payload[variable size] 组成。除了消息体,其他组成消息头部。

  • 消息长度:(从Request ID开始到Payload结束)

  • SOME/IP 服务分为服务提供者和服务调用者,用以区分相同消息的不同调用。Rquest ID = Client ID + Session ID.

  • Message Type,用于标识消息的类型,分别为 5 种类型:

  • 1)请求类型(0x00):需要响应的请求;

  • 2)无需响应的请求(0x01);

  • 3)通知(0x02):无需响应的通知/事件回调;

  • 4)响应(0x80):响应体

  • 5)错误

  • SOME/IP 的消息类型可分为 Method 类型、Event 类型和 Field 类型。当为 Method 类型时(第 1 bit = 0),Message ID = Service ID + Method ID;当为 Event类型时(第 1 bit = 1),Message ID = Service ID + Event ID。三种消息类型和通信机制之间的映射关系如下:

  • 传输协议:SOME/IP协议属于应用层协议,可基于 TCP/UDP。

2、SOME/IP 控制报文:SOME/IP-SD

参考链接:https://mp.weixin.qq.com/s?__biz=MzI0NTU1NDQ3Mw==&mid=2247483733&idx=1&sn=60740d2c14970b3445fa7a707cff94a0&chksm=e94d8d13de3a0405c04fb1c08e12428fee9bfeac1f756a47b8eaf983911af6c6d0aa71bdff65&cur_album_id=1665737889458028552&scene=189#wechat_redirect

  • SOME/IP-SD 解决的是 1)Client 如何发现服务;2)当服务不可用时,如何通知 Client;3)Client 如何订阅事件。即 SOME/IP-SD 用来实现服务发现和事件订阅机制。

  • SOME/IP-SD 报文格式如下:

2.1、Service Entry 用于服务发现

  • 报文有个重要的 Type 字段(8bits):1)若 Service 就绪,会主动发出 OfferService 通知组播内其他节点该服务已启动,可以创建连接;2)若网络未收到相关服务的 OfferService,而 Client 又需要访问该服务,则 Client 发出 FindService 主动寻找服务,此时若 Service 就绪,则会回复 OfferService 报文;3)若服务不可用,会主动发出 StopOfferService 报文通知组播内其他节点该服务不可用,停止发送请求,并取消订阅。

2.2、Eventgroup Entry 用于事件订阅:

  • 报文有个重要的 Type 字段(8bits):1)当 Client 收到服务 OfferService 之后,Client 可以发送 Subscribe 报文主动跟 Service 订阅感兴趣的事件组,也可发送 StopSubscribe 报文来通知 Service。2)Service 收到 Client 的 Subscribe 报文之后,需要先判断是否符合可订阅的条件,若满足,则返回 SubscribeAck,否则返回 SubscribeEventgroupNack 告知。3)当事件组的事件准备就绪之后,Service 会以某种约定好的形式发送相关事件给 Client。

3、FireAndForget

参考链接:https://geek-docs.com/redis/redis-ask-answer/3_redis_difference_between_fireandforget_and_async_behavior_for_publishing.html # Redis 发布过程中的FireAndForget和Async行为的区别

MCU1Misc.fidl 文件使用 fireAndForget 设计模式定义 SOC 发送数据给 MCU 的方法 EOLRingDataSoCToMCU:

method EOLRingDataSoCToMCU fireAndForget {
  in {
    UInt32 FCTDataLen
    array_uint8 array_so
  }
}
  • FireAndForget:即发即忘
    FireAndForget是指在消息发布之后,发布者不会再等待消息是否被成功投递给订阅者的确认。它是一种快速而简单的发布方式,适用于对消息可靠性要求不高,只需保证消息尽可能快地发给订阅者的场景。

二、CommonAPI 知识

参考链接1:https://www.jianshu.com/p/44b80df683c2 # CommonAPI入门
参考链接2:https://juejin.cn/post/7310156414251876352 # 深入理解CommonAPI 设计:打造高效、灵活的进程间通信
参考链接3:https://blog.csdn.net/djfjkj52/article/details/120698447 # SOME/IP不等同于SOA,CommonAPI-RPC通信和vsomeip基于消息通信
参考链接4:https://blog.csdn.net/xllhd100s/article/details/112798677?spm=1001.2014.3001.5502 # CommonAPI-SomeIP 使用

  • CommonAPI C++是一个标准化的C++ API规范,用于开发分布式的应用程序。这些应用程序通过进程间通信的中间件进行通信。
  • CommonAPI C++ 依赖 Franca-IDL 来描述静态接口以及通信和协议配置参数。通过代码生成器(CommonAPI-Tools)生成 proxy 和 stub- 代码。
  • CommonAPI C++ 包括 CommonAPI C++ Core(与中间件无关)和 CommonAPI C++ Binding(与中间件相关)。Binding 代码生成器需要待定的中间件参数,参数定义在Francal 部署文件中(*.fdepl)
  • GENIVI的 CommonAPI C++是基于vsomeip实现的RPC框架

三、SOMEIP 实战

EOL 的 eol_someip_client.cpp 的文件树如下:

├── build.sh
├── CMakeLists.txt
├── config
│   ├── commonapi.ini
│   └── vsomeip-client.json
├── fidl
│   ├── MCU1Misc.fdepl
│   └── MCU1Misc.fidl
├── run_client.sh
├── src
│   └── eol_someip_client.cpp
└── src-gen
    └── v1
        └── commonapi
            ├── MCU1Misc.hpp
            ├── MCU1MiscProxyBase.hpp
            ├── MCU1MiscProxy.hpp
            ├── MCU1MiscSomeIPDeployment.cpp
            ├── MCU1MiscSomeIPDeployment.hpp
            ├── MCU1MiscSomeIPProxy.cpp
            ├── MCU1MiscSomeIPProxy.hpp
            ├── MCU1MiscSomeIPStubAdapter.cpp
            ├── MCU1MiscSomeIPStubAdapter.hpp
            ├── MCU1MiscStubDefault.hpp
            └── MCU1MiscStub.hpp

1、配置文件 config

commonapi.inivsomeip-client.json 属于 vsomeip 配置,MCU1Misc.fidl 属于服务的配置。vsomeip的配置关注网络通信和服务发现的底层细节,而IDL文件的配置关注服务接口的定义和数据的描述。

  • 交互点:在使用vsomeip实现基于CommonAPI的服务时,IDL文件定义的服务接口将被转换为vsomeip可以理解的格式,并通过vsomeip的通信机制进行传输。此时,vsomeip的配置将确保这些服务接口能够正确地被发现和调用。

参考链接:https://blog.csdn.net/zjfengdou30/article/details/125912203 # vsomeip源码梳理 -- application初始化

1.1 commonapi.ini

这是用来配置 CommonAPI 相关应用程序的文件。CommonAPI 用于定义跨多个通信协议和编程语言的接口。

[default]
binding=someip  // 绑定 CommonAPI 使用的协议为 SOMEIP 
[logging]
console = true
file = log/CommonAPISomeIP.log
dlt = false  // 不启动 DLT(Diagnostic Log and Trace)
level = verbose

1.2 vsomeip-client.json

vsomeip-client.json 用于配置 VSomeIP 客户端的行为和参数。VSomeIP 基于 UDP,允许服务在不同组件之间发布和发现。

{
    "unicast": "192.168.1.11",  // 指定客户端单播地址
    "netmask": "255.255.255.0",
    "logging": {
        "level": "debug",
        "console": "true",
        "file": {
            "enable": "true",
            "path": "log/vsomeipClient.log"
        },
        "dlt": "true"
    },
    "applications": [  // 指定客户端要使用的应用程序
        {
            "name": "proxy.SOC",
            "id": "0x1343"  // SomeIP 服务 ID
        }
    ],
    "routing": "proxy.SOC",  // 指定路由到的应用程序名称
    "service-discovery": {  // 配置服务发现的行为
        "enable": "true",   // 启用服务发现
        "multicast": "224.244.224.245",   // 指定用于服务发现的组播地址
        "port": "30490",  // 用于服务发现的 UDP 端口
        "protocol": "udp"
    }
}

1.3 MCU1Misc.fidl

参考链接:https://blog.csdn.net/sinat_20184565/article/details/93907495 # FIDL指南

FIDL 文件用来定义接口以及相关数据结构。MCU1Misc.fidl 定义了一个简单的 CommonAPI 方法,该接口支持一个广播和一个方法,并定义了一个数组类型作为参数的一部分。
使用这个 FIDL 文件,你可以使用 CommonAPI 工具(如 capigen)来生成代码,以便在应用程序中实现这个接口。

package commonapi
interface MCU1Misc {  // 定义名为 MCU1Misc 的接口
    version { major 1 minor 0 }
    broadcast EOLRingDataMCUToSoC{  // 定义一个需要接收报文的广播(MCU 发给 SOC 的广播)
        out {  // 定义此广播的输出参数
            UInt32 FCTDataLen  
            array_uint8 array_mcu
        }
    }
    method EOLRingDataSoCToMCU fireAndForget{ // 定义一个即发即忘方法,fireAndForget 语义表示该方法发送请求后无需等待响应
        in {  // 定义方法的输入参数
            UInt32 FCTDataLen
            array_uint8 array_soc
        }
    }
    array array_uint8 of UInt8
}

1.4 MCU1Misc.fdepl

import "platform:/plugin/org.genivi.commonapi.someip/deployment/CommonAPI-SOMEIP_deployment_spec.fdepl"
import "MCU1Misc.fidl"

define org.genivi.commonapi.someip.deployment for interface commonapi.MCU1Misc { // 定义一个针对 MCU1Misc 接口的部署描述符
    SomeIpServiceID = 49414 // MCU1Misc 的 SOMEIP 服务 ID
    method EOLRingDataSoCToMCU { // 定义一个 SOMEIP 方法
        SomeIpMethodID = 1  // 定义方法 ID
    }
    broadcast EOLRingDataMCUToSoC{ // 定义一个广播事件 
        SomeIpEventID = 32769    // 定义事件 ID     
        SomeIpEventGroups = { 1 }  // 定义事件组
    }
    array array_uint8{  // 定义一维数组
        SomeIpArrayMaxLength = 1024   
        SomeIpArrayLengthWidth = 0
    }
}

define org.genivi.commonapi.someip.deployment for provider as Service {  // 定义了一个服务提供者
    instance commonapi.MCU1Misc {  // 实例化接口
        InstanceId = "MCU1Misc"
        SomeIpInstanceID = 385  // 实例 ID
    }
}

1、someip_client 实例

1.1 vsomeip-client.json

{
"unicast": "192.168.1.11,   # 本地网络接口 IP
"netmask": "255.255.255.0,  # 子网掩码
"logging": {
  "level": "debug",
  "console": "true",
  "file": {
    "enable": "true",
    "path": "log/vsomeipClient.log"
  },
  "dlt": "true"  # 启用数据链路跟踪(Diagnostic Log and Trace, Dlt 模块)
},
"applications": [
  {
  "name": "proxy.SOC",
  "id": "0x1343"
  }
],
"routing": "proxy.SOC",  # 将数据路由到名为 "proxy.SOC" 的应用程序
"service-discovery":{
  "enable": "true",  # 启用服务发现功能
  "multicast": "224.244.224.245",  # 多播地址
  "port": "30490",
  "protocol": "udp"  # 指定服务发现使用 UDP 协议
}
}

6.2 someip_client 入口程序

6.2.1 CommonAPI 的 proxy 和 stub 概念

Proxy(代理):是客户端的本地对象,代表远程服务器,客户端通过调用 Proxy 的方法来与远程服务器通信。

  • Proxy 负责将客户端的请求数据序列化,并通过中间件发送到远程服务端;
  • 客户端无需知道底层通信方式,只需调用本地对象一样调用 Proxy 即可

Stub(存根):是服务端的本地对象,用于接收来自客户端的请求。

  • Stub 负责反序列化客户端的请求数据;
  • 服务端无需关心底层通信方式,只需处理实际业务逻辑即可

someip 客户端的实现仅需 3 步:
1)实例化客户端代理

  • pClientSomeIPClientProxy)是客户端代理基类,并不是具体的 proxy,它负责处理客户端与服务端的通信;
std::shared_ptr<CommonAPI::ServiceInterface::SomeIPClientProxy> pClient = std::make_shared<CommonAPI::ServiceInterface::SomeIPClientProxy>();

2)发现服务
Client 发出 FindService 主动寻找服务,此时若 Service 就绪,则会回复 OfferService 报文。

  • 通过代理基类主动寻找服务并将其作为代理实例。g_MCU1Client是代理实例,用来调用服务端的方法以及接收服务端的事件;
g_MCU1Client = pClient->FindService<v1::commonapi::MCU1MiscProxy>(2000);
  • 发现服务后,还需判断服务是否可用。通过 isAvailable() 判断服务是否被代理可用。
while (!g_MCU1Client->isAvailable())

3)订阅服务
当 Client 收到服务 OfferService 之后,Client 可以发送 Subscribe 报文主动跟 Service 订阅感兴趣的事件组。Service 收到 Client 的 Subscribe 报文之后,需要先判断是否符合可订阅的条件,若满足,则返回确认报文。
fdepl 文件定义感兴趣的事件 EOLRingDataMCUToSoCEvent 的事件 ID 为 32769,事件组为 1

  • 若服务可用,则订阅服务。订阅服务后,一旦事件发生,将会调用处理事件
g_MCU1Client->getEOLRingDataMCUToSoCEvent().subscribe(
  [&](const uint32_t &len, const v1::commonapi::MCU1Misc::array_uint8 &data) { process_data(len, data); });
int main()
{
  ...
  std::shared_ptr<v1::commonapi::MCU1MiscProxy<>> g_MCU1Client= nullptr;
  
// step1:pClient(SomeIPClientProxy)并不是具体的 proxy,它负责处理客户端与服务端的通信 
  std::shared_ptr<CommonAPI::ServiceInterface::SomeIPClientProxy> pClient = std::make_shared<CommonAPI::ServiceInterface::SomeIPClientProxy>();

  // step2:g_MCU1Client 是代理实例,用来调用服务端的方法以及接收服务端的事件;
  // 一个 SOMEIP 服务是一个服务端。发现服务, 设置超时时长为 2s
  g_MCU1Client = pClient->FindService<v1::commonapi::MCU1MiscProxy>(2000);
  if (!g_MCU1Client) return -1;
  
  while (!g_MCU1Client->isAvailable())
  {
    std::this_thread::sleep_for(std::chrono::microseconds(1000 *100));
  }

  // step3:订阅服务,一旦服务发生,将会调用处理函数
  g_MCU1Client->getEOLRingDataMCUToSoCEvent().subscribe(
  [&](const uint32_t &len, const v1::commonapi::MCU1Misc::array_uint8 &data) { process_data(len, data); 
  });
  
  return 0;
}

6.3 对 SOMEIP 进行数据校验

SOC 需通过校验 MCU 发来的 SOMEIP 数据报文来确保数据的正确性。

7、CommonAPI::ServiceInterface::SomeIPClientProxy

在 CommonAPI 框架中,服务接口通常通过 IDL (Interface Definition Language) 文件定义,然后这些定义被转换为 C++ 或 C 的源代码。生成的代码包括代理(Proxy)和存根(Stub)类,用于在客户端和服务端之间传递消息。

CommonAPI::ServiceInterface::SomeIPClientProxy 是 CommonAPI 框架中的客户端代理类,它用于通过 SOME/IP 协议与远程服务进行通信。它负责以下任务:

  • 服务发现:通过 SOME/IP 机制在网络中查找并连接到特定的远程服务。
  • 消息传递:将客户端的请求序列化为 SOME/IP 消息,并通过网络发送到远程服务。同时,它也将从远程服务接收到的响应反序列化为客户端可以理解的格式。
  • 事件订阅:允许客户端订阅远程服务的事件。当这些事件发生时,远程服务会发送消息到客户端,客户端的代理类会负责处理这些消息。
  • 错误处理:处理与服务通信期间可能发生的错误,例如连接丢失或请求超时。

如 6.2 的例子,pClient 被用来查找 v1::commonapi::MCU1MiscProxy 类型的远程服务,并设置了一个对远程服务事件的订阅。一旦找到并连接到服务,客户端就可以通过 MCU1MiscProxy 代理类的接口与远程服务进行通信,调用远程服务的方法或订阅其事件。

posted @   MasterBean  阅读(2624)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示