1 面向服务架构(SOA)的概述及意义
1.1 面向服务架构概述
开局一张图,先有个大概的印象。服务的设计一般包括图中的几个部分:
- 软件组件的设计
- 软件组件的服务接口的设计(详细可进一步为方法和事件及属性的设计)
一般传统的架构设计方法是:系统被划分为子系统,各个子系统通过定义的接口,实现交互通信,一般子系统之间的依赖性较高。而面向服务的体系架构的设计方法是:不同的系统资源被打包到一个“服务”中,该“服务”提供特定的系统功能,同时保持它们自己的内部状态。实现服务的组件代表服务的单个实例,其由服务实例ID标识。当客户端想要使用服务实例时,它只需要遵循定义语言规范来请求服务。我们先看一下规范怎么定义服务和服务接口及服务实例的?
缩写/首字母缩略词: | 描述: |
Service | 零个或多个方法methods、零个或多个事件events以及零个或多个字段fields的逻辑组合(允许空服务,例如用于在 SOME/IP-SD 中声明非 SOME/IP 服务)。说人话就是一个离散功能单元,我们可以封装成一个函数来实现这个功能 |
Service Interface | 服务「包括其方法,事件和字段」的正式规范(formal specification ),说人话就是能够被其他模块调用的函数名称/API ,服务通过这个函数名称/API被其他ECU所使用 |
Service Instance | 服务接口的软件实现,可以在车辆上或ECU 上存在不止一次 ,说人话就是一个函数名称/API的定义和实现 |
服务的接口以标准定义语言指定,该语言将在系统的每个元素之间共享。其包含三个要素:方法,事件和属性(也叫Filed)。
我们先看一下规范怎么定义方法,事件和属性的?
缩写/首字母缩略词: | 描述: |
Method | 方法、过程、函数或被调用的子例程。(即从客户端到服务的消息),根据服务器是否有反馈结果分为请求/响应(Request/Response, R/R)通信和Fire&Forget(F&F)通信 |
Event | 一种单向数据传输,根据实际的应用场景,可以有不同的发送方式。即仅在数据改变时调用或循环调用,并从数据的producer生产者发送到consumers消费者(即从服务端到客户端的消息) |
Field | Field,有三种属性:getter、setter 和 notfier。它用以表示某一功能的状态量,可以通过Method发布控制命令,即Setter;也可以通过Method去请求获取状态,即Getter;在状态发生改变时也可以发送通知,即Notification(同Event) |
Notification Event | 字段通知器(the notifier of an field) 发送的事件消息。此类通知者的消息无法与事件消息区分开来;因此,当提到( refering to )一个事件的消息时,这应该也适用于字段通知者的消息。 |
Getter | 允许对字段Field进行读取访问的请求/响应调用。 |
Setter | 允许对字段Field进行写访问的请求/响应调用。 |
Notifier | Notifier在消息字段Field的值更改时发送具有新值的事件消息 |
注意:
1 、事件和属性的区别就是属性是具有记忆性和初始状态,而事件就像快照一样,没有历史状态。
2、同一服务的多个实例可能存在于单个系统中。在这些情况下,将需要一个服务发现和实例选择过程。虽然可以让客户端处理这个过程,但它会增加系统的复杂性。因此,考虑到 SOA 开发的技术已经提供了这种“服务聚合器”。聚合器执行双重角色。它充当服务客户端的服务提供者,也充当实际不同服务提供者的服务客户端。使用哪个服务实例的选择由请求者决定。
1.2 面向服务架构(SOA)的意义
- 1 、面向服务架构确保松散耦合的软件模块,因为每个服务只保存执行其逻辑所需的信息,并且无论系统作为一个整体的状态如何,只要请求遵循定义语言规范,它就会继续执行其功能。由于这个事实,SOA 与平台和语言无关。只要请求或响应遵循规范,客户端或服务都没有必要深入了解彼此的实现细节。因此,可以无缝集成可能由不同供应商设计的异构组件。
- 2、此外,SOA 不需要静态系统配置。通过使用服务发现,服务提供者可以在运行时被发现。这实际上意味着可以将组件“热插拔”到系统中。不再需要关闭整个系统来更新或添加新组件。
2 服务(SOME/IP)的通信模式
在SOME/IP 通信过程中,服务端和客户端之间的通信方式主要为Events 和Methods方式,其分类如下图所示:
SOME/IP 通信过程中,服务端和客户端之间交互的序列图为:
下面详细介绍每一种通信模式。
2.1 Request/Response Methods
2.1.1 RR流程概述
最常见的通信模式之一是请求/响应模式。一个通信伙伴(客户端)发送请求消息,由另一个通信伙伴(服务器)应答。客户端会根据需要请求的服务内容构建请求报文的报头和有效载荷,具体如下:
- 构建有效载荷
- 根据客户端要调用的方法设置Message ID
- 将 Length 字段设置为 8 字节(对于 SOME/IP 标头的长度字段之后的部分)+ 序列化有效负载的长度
- 可选择将请求 ID 设置为唯一编号(仅对客户端唯一)
- 设置协议版本
- 根据接口定义设置接口版本
- 将消息类型设置为请求(即0x00)
- 将返回码设置为0x00
Number | Value | Description |
0x00 | REQUEST | 期望得到响应的请求 |
0x01 | REQUEST_NO_RETURN | 不期望得到响应的请求 |
0x02 | NOTIFICATION | 不期望得到响应的请求的通知/事件回调 |
0x80 | RESPONSE | 响应消息 |
0x81 | ERROR | 响应消息包含错误 |
服务器根据收到的请求报文的内容,执行相应的服务之后发送响应报 文,具体如下:
- 构建有效载荷
- 从对应的请求中提取Message ID,复制到响应报文中
- 将长度设置为 8 字节 + 新的有效负载大小
- 从对应的请求中提取Request ID,复制到响应报文中
- 将消息类型设置为RESPONSE(即0x80)或ERROR(即0x81)
- 将 Return Code 设置为被调用方法的返回代码,或者在错误消息的情况下设置为有效的错误代码,例如下表所示:
ID | 名称 | 描述 |
0x00 | E_OK | 没有发生错误 |
0x01 | E_NOT_OK | 发生未指定的错误 |
0x06 | E_TIMEOUT | 发生超时(仅限内部错误代码)。 |
2.1.2 RR代码解析
2.1.2.1 服务和客户端的配置
//Someip_Cfg.c static Someip_ServiceType Someip_Services[] = { { .Id = 0x1111, .InstanceId = 0x2222, .MethodId = 0x3333, .MajorVersion = 0, .MinorVersion = 0, .EventId = 0x8778, }, }; const Someip_InstanceType Someip_Instance = { .NoOfServices = 1, .Service = Someip_Services, }; //someip.c static uint16_t MethodId = 0x3333; static uint16_t ClientId = 0x4444;
2.1.2.2 服务和客户端的注册
//someip.c //注册客户端 someip_app_t *someip_register_app(client_t my_id) { someip_app_t *srv; srv = (someip_app_t *)malloc(sizeof(someip_app_t)); if(!srv) { return NULL; } srv->client_id = my_id; srv->req_id = 0; return srv; } //注册服务(请求) someip_req_t request_service(someip_app_t *app, service_t service_id, instance_t instance, method_t method, void (*avail_handler)(service_t service, instance_t instance, int available) ) { someip_requested_service_t *srv; srv = (someip_requested_service_t *)malloc(sizeof(someip_requested_service_t)); if(!srv) { return NULL; } srv->req = app; srv->service_id = service_id; srv->instance = instance; srv->avail_handler = avail_handler; someip_add_req_service(srv); return (someip_req_t)srv; } //读取配置实现注册的对象的实例化 void Someip_Init() { int i; printf("[Someip] Someip_Init \n"); app = someip_register_app(ClientId); for(i=0; i < Someip_Instance.NoOfServices; i++) { service_t service_id = (service_t)Someip_Instance.Service[i].Id; instance_t instance = (instance_t)Someip_Instance.Service[i].InstanceId; request_service(app, service_id, instance, MethodId, sample_avail_handler); } }
2.1.2.3 RR请求响应服务
void Someip_SendRequest(someip_requested_service_t *service) { // 创建临时缓存buf[256],往里填充someip请求报文数据 uint8 buf[256]; // data指向数组的buf头,并把这一段内存类型转换成someip_t类型 someip_t *data = (someip_t *)buf; //填充someip报文头,注意消息类型为0x0:REQUEST(期望得到响应的请求) data->length = htonl(0xd); data->msg_id = htonl(MAKE_ID(service->service_id, service->method)); data->req_id = htonl(MAKE_ID(service->req->client_id, service->req->req_id)); data->protocol_ver = 0x1; data->interface_ver = 0x0; data->msg_type = 0x0; data->ret_code = 0x0; //填充someip有效载荷Payload strcpy(data->payload, "world"); //序列化:内存类型转换成为uint8的pduinfo.SduDataPtr PduInfoType pduInfo; pduInfo.SduDataPtr = (uint8 *)data; pduInfo.SduLength = 8 + ntohl(data->length); //调用底层发送函数,参数2时tx_socket的句柄 Someip_SendPacket(&pduInfo, 2); } void Someip_SendPacket(PduInfoType *pduInfo, SoAd_SoConIdType tx_socket) { TcpIp_SockAddrType destination; uint8 netmask; TcpIp_SockAddrType default_router; destination.domain = TCPIP_AF_INET; default_router.domain = TCPIP_AF_INET; /* Set the remote multicast address before sending */ //ip地址为:10.10.0.22,端口号为SoAd_GetLastPort的返回值 destination.addr[0] = 10; destination.addr[1] = 10; destination.addr[2] = 0; destination.addr[3] = 22; destination.port = htons(SoAd_GetLastPort()); printf("[Someip] Send Packet %d\n", htons(SoAd_GetLastPort())); //tx_socket和远程地址进行绑定 (void)SoAd_SetRemoteAddr(tx_socket, &destination); const TcpIp_SockAddrType wildcard = { (TcpIp_DomainType) TCPIP_AF_INET, TCPIP_PORT_ANY, {TCPIP_IPADDR_ANY, TCPIP_IPADDR_ANY, TCPIP_IPADDR_ANY, TCPIP_IPADDR_ANY } }; //调用底层发送函数 SoAd_IfTransmit(tx_socket, pduInfo); //tx_socket和远程地址进行解绑 (void)SoAd_SetRemoteAddr(tx_socket, &wildcard); } void Someip_SendResponse(someip_requested_service_t *service, uint8 *payload, uint32 length) { uint8 buf[256]; someip_t *data = (someip_t *)buf; data->length = htonl(8 + length);//lh htonl就是把本机字节顺序转化为网络字节顺序 data->msg_id = htonl(MAKE_ID(service->service_id, service->method)); data->req_id = htonl(MAKE_ID(service->req->client_id, service->req->req_id)); data->protocol_ver = 0x1; data->interface_ver = 0x0; //将消息类型设置为RESPONSE(即0x80) data->msg_type = 0x80; data->ret_code = 0x0; PduInfoType pduInfo; pduInfo.SduDataPtr = (uint8 *)data; pduInfo.SduLength = 8 + ntohl(data->length); strncpy(data->payload, payload, length); Someip_SendPacket(&pduInfo, 1); } //接收到Someip报文的回调函数 void Someip_RxIndication(PduIdType RxPduId, const PduInfoType *PduData) { uint8 *data = PduData->SduDataPtr; //创建someip_t类型变量SomeipPtr对接收到的数据进行解析 someip_t *SomeipPtr = (someip_t *)data; int i; uint32 RequestId, NotifyId; printf("[Someip] Someip_RxIndication %d\n", RxPduId); for(i=0; i < Someip_Instance.NoOfServices; i++) { //抽取配置的服务ID和服务实例ID,方法ID/事件ID service_t id = Someip_Instance.Service[i].Id; instance_t instance = Someip_Instance.Service[i].InstanceId; method_t method = Someip_Instance.Service[i].MethodId; uint16_t event = Someip_Instance.Service[i].EventId; //得到someip的消息 ID(NotifyId/RequestId) NotifyId = MAKE_ID(id, event); RequestId = MAKE_ID(id, method); printf("%x %x %x\n", SomeipPtr->msg_id, NotifyId, RequestId); if(ntohl(SomeipPtr->msg_id) == NotifyId) { printf("[Someip] Get Notification\n"); someip_requested_service_t *service = someip_find_req_service(id, ClientId, instance); if(service != NULL) service->avail_handler(service); Someip_SendRequest(service); } else if(ntohl(SomeipPtr->msg_id) == RequestId) { printf("[Someip] Get Request\n"); //ClientId:0x4444,要先寻找服务 someip_requested_service_t *service = someip_find_req_service(id, ClientId, instance); if(service != NULL) service->avail_handler(service); Someip_SendResponse(service, SomeipPtr->payload, ntohl(SomeipPtr->length) - 8); } else { printf("[Someip] Unknown Services\n"); } } }