3. SOME/IP UDP单播通信

SOME/IP UDP单播通信 学习卡片

任务目标

本案例使用Python实现基于UDP单播的SOME/IP请求/响应通信。客户端通过UDP向服务端发送SOME/IP请求报文,服务端解析请求后返回对应的响应报文,完成一次完整的SOME/IP方法调用。

环境依赖

  • Python 3.8+(仅使用标准库,无需第三方依赖)
  • 操作系统:Windows/Linux/macOS均可

SOME/IP报文结构

本案例实现SOME/IP协议的核心报文头(16字节),包含以下字段:

  • Message ID(4字节):Service ID(高16位)+ Method ID(低16位)
  • Length(4字节):从Request ID开始到报文末尾的字节数
  • Request ID(4字节):Client ID(高16位)+ Session ID(低16位)
  • Protocol Version(1字节):固定为0x01
  • Interface Version(1字节):固定为0x01
  • Message Type(1字节):0x00(REQUEST)/ 0x80(RESPONSE)
  • Return Code(1字节):0x00表示成功

完整实现代码

import socket
import struct
import threading

# SOME/IP 常量定义
SERVICE_ID = 0x1234
METHOD_ID = 0x0001
PROTOCOL_VERSION = 0x01
INTERFACE_VERSION = 0x01
MSG_TYPE_REQUEST = 0x00
MSG_TYPE_RESPONSE = 0x80
RETURN_CODE_OK = 0x00

# 请求/响应负载中的操作数
OPERAND_A = 10
OPERAND_B = 20

def build_header(msg_id, length, request_id, msg_type, return_code):
    """构建SOME/IP报文头(16字节)"""
    return struct.pack('!IIBBBB', msg_id, length, request_id,
                       PROTOCOL_VERSION, INTERFACE_VERSION, msg_type, return_code)

def parse_header(data):
    """解析SOME/IP报文头"""
    msg_id, length, request_id, proto_ver, iface_ver, msg_type, ret_code = \
        struct.unpack('!IIBBBB', data[:16])
    return {
        'service_id': (msg_id & 0xFFFF0000) >> 16,
        'method_id': msg_id & 0x0000FFFF,
        'length': length,
        'client_id': (request_id & 0xFFFF0000) >> 16,
        'session_id': request_id & 0x0000FFFF,
        'protocol_version': proto_ver,
        'interface_version': iface_ver,
        'message_type': msg_type,
        'return_code': ret_code
    }

def handle_request(data, client_addr, sock):
    """处理收到的SOME/IP请求并发送响应"""
    header = parse_header(data)
    payload = data[16:]  # 跳过报文头获取负载
    
    # 解析请求负载中的两个操作数
    operand_a, operand_b = struct.unpack('!ii', payload)
    result = operand_a + operand_b
    
    # 构建响应报文
    msg_id = (header['service_id'] << 16) | header['method_id']
    request_id = (header['client_id'] << 16) | header['session_id']
    resp_payload = struct.pack('!i', result)
    resp_length = 8 + len(resp_payload)  # Request ID(4) + ProtoVer(1) + IfaceVer(1) + MsgType(1) + RetCode(1) + Payload
    resp_header = build_header(msg_id, resp_length, request_id, MSG_TYPE_RESPONSE, RETURN_CODE_OK)
    
    sock.sendto(resp_header + resp_payload, client_addr)
    print(f"[服务端] 收到请求: {operand_a} + {operand_b},发送响应: {result}")

def server():
    """SOME/IP服务端:监听UDP端口并处理请求"""
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('127.0.0.1', 5000))
    print("[服务端] 启动,监听 127.0.0.1:5000")
    
    while True:
        data, addr = sock.recvfrom(1024)
        handle_request(data, addr, sock)

def client():
    """SOME/IP客户端:发送请求并接收响应"""
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    server_addr = ('127.0.0.1', 5000)
    
    # 构建请求报文
    msg_id = (SERVICE_ID << 16) | METHOD_ID
    client_id = 0x0001
    session_id = 0x0001
    request_id = (client_id << 16) | session_id
    req_payload = struct.pack('!ii', OPERAND_A, OPERAND_B)
    req_length = 8 + len(req_payload)
    req_header = build_header(msg_id, req_length, request_id, MSG_TYPE_REQUEST, RETURN_CODE_OK)
    
    # 发送请求
    sock.sendto(req_header + req_payload, server_addr)
    print(f"[客户端] 发送请求: {OPERAND_A} + {OPERAND_B}")
    
    # 接收响应
    sock.settimeout(3)
    try:
        data, _ = sock.recvfrom(1024)
        header = parse_header(data)
        result = struct.unpack('!i', data[16:])[0]
        print(f"[客户端] 收到响应: 结果 = {result}")
        print(f"[客户端] 响应头: MessageType=0x{header['message_type']:02x}, ReturnCode=0x{header['return_code']:02x}")
    except socket.timeout:
        print("[客户端] 等待响应超时")
    finally:
        sock.close()

if __name__ == '__main__':
    # 启动服务端线程
    server_thread = threading.Thread(target=server, daemon=True)
    server_thread.start()
    
    # 主线程运行客户端
    client()

运行与验证

  1. 将上述代码保存为 someip_udp_unicast.py
  2. 在终端执行:python someip_udp_unicast.py
  3. 预期输出(确定性结果):
[服务端] 启动,监听 127.0.0.1:5000
[客户端] 发送请求: 10 + 20
[服务端] 收到请求: 10 + 20,发送响应: 30
[客户端] 收到响应: 结果 = 30
[客户端] 响应头: MessageType=0x80, ReturnCode=0x00

通过条件:客户端成功打印出响应结果30,且响应头中MessageType为0x80(RESPONSE)、ReturnCode为0x00(OK),即表示UDP单播SOME/IP请求/响应通信闭环验证通过。

关键说明

  • 服务端在独立线程中运行,客户端在主线程中执行,二者通过本机UDP端口5000通信
  • 报文长度字段(Length)的计算方式为:Request ID(4字节) + Protocol Version(1) + Interface Version(1) + Message Type(1) + Return Code(1) + Payload长度,即8 + payload长度
  • 本案例使用加法运算作为方法调用示例,实际应用中可将负载替换为任意序列化数据