基于UDP协议Python通信网络程序(服务器端+客户端)及通信协议在自动驾驶场景应用示例

一、UDP协议

UDP(用户数据报协议)是一种无连接的传输层协议,具有简单、高效的特点,适用于一些对数据可靠性要求不高的应用场景。UDP的主要特点包括无连接、不可靠和面向数据报。这意味着在发送数据之前不需要建立连接,UDP不会对数据包的传输进行确认,也不会保证数据的顺序和完整性。由于这些特性,UDP提供了较低的延迟和较高的效率,但同时也意味着可能会有数据丢失或损坏的风险。

UDP通过将数据分割成小的数据包进行传输,每个数据包都包含源端口号和目标端口号。当应用程序想要发送数据时,它将数据传递给UDP协议栈,UDP协议栈将数据打包成数据报(datagram),然后发送给目标地址。这种机制使得UDP特别适合于实时性要求较高的应用,如在线游戏、语音通信等。

UDP的应用场景非常广泛,包括流媒体服务、实时视频流、DNS查询、交易市场数据的组播以及物联网等。这些应用场景通常需要快速、连续地传输大量数据,而UDP正好满足这些需求。例如,在流媒体服务中,UDP的简单数据传输机制和不需要建立连接的特性使其成为理想的选择。

尽管UDP提供了许多优点,但它的不可靠性也意味着在某些情况下可能不适合使用。例如,对于那些对数据完整性和准确性有严格要求的应用,如文件传输或网页浏览,使用TCP可能是更好的选择,因为TCP提供了一种更可靠的数据传输方式。

二、设计目标

1.设计目标

  1. 实现高效数据传输:利用UDP协议的无连接特性,设计一个能够快速传输数据的系统。
  2. 保证实时性:针对实时性要求高的应用场景,如在线游戏、语音通信等,确保数据传输的低延迟。
  3. 可靠性设计:虽然UDP本身不保证数据包的顺序和完整性,但通过设计,尽量提高数据传输的可靠性。
  4. 灵活性和可扩展性:设计模块化,便于根据不同的应用需求进行扩展和定制。

2.功能需求

  1. 数据传输:系统应支持基本的UDP数据传输功能。
  2. 端口绑定与监听:服务器端应能绑定特定端口并监听传入的数据包。
  3. 数据包发送:客户端应能向服务器端发送数据包。
  4. 数据接收与处理:服务器端应能接收数据包并进行相应处理。
  5. 错误检测与处理:系统应能检测传输中的错误,并采取适当的错误处理措施。
  6. 多播支持:对于需要同时向多个接收者发送数据的应用,系统应支持UDP多播。

3.性能需求

  1. 低延迟:系统应保证数据传输的低延迟,以满足实时应用的需求。
  2. 高吞吐量:系统应能处理高频率的数据传输,保证高吞吐量。
  3. 可伸缩性:系统架构应支持水平扩展,以应对不同的负载需求。
  4. 容错性:系统应能妥善处理网络波动和临时的连接中断。

三、系统设计

1.软件流程图

2.函数定义

  • udp_rcv:这是UDP模块的入口函数,用于处理接收到的UDP数据包。在该函数中会进行一系列检查,并调用其他函数进行处理。如果找不到相应的socket,则该数据包将被丢弃。

  • udp_sendmsgudp_send_skb :这两个函数主要涉及UDP数据包的发送过程。udp_sendmsg 函数定义在 net/ipv4/udp.c 中,用于发送UDP数据包到指定的地址和端口。

  • sendtorecvfrom:这两个函数是专门为UDP协议提供的,用于发送和接收数据。发送时需指明目的地址,而接收时也会提供发送方的地址信息。

  • bind():虽然不是UDP特有的函数,但在使用UDP进行通信时,通常需要先绑定一个本地地址和端口到套接字上,以便接收来自特定源的数据包。

四、技术实现

1.开发环境和工具

  • 编程语言:Python、Visual Studio,提供了丰富的库来简化网络编程,适合快速开发和测试

  • 操作系统:Windows

  • 网络调试工具:NetAssist是一款常用于Windows平台的TCP/UDP网络调试工具,支持服务端和客户端的监听,对于编写各种通信协议的程序来说非常方便;SocketTool.exe是一款小巧实用且功能强大的TCP/UDP网络通讯调试工具,绿色免费且无需安装

2.数据结构的选择和设计

UDP协议中数据结构的选择和设计主要涉及到UDP报文的基本结构,包括源端口号、目的端口号、UDP报文整体长度和数据包校验和等关键字段。我们可以总结出以下几点:

  1. 源端口号和目的端口号:这两个字段分别占用2个字节,用于标识数据包的发送者端口号和接收者端口号。这是UDP报文中最基本也是最重要的两个字段,确保了数据能够正确地从一个端口发送到另一个端口。

  2. UDP报文整体长度:这个字段用于表示UDP报文的总长度,包括头部和数据区的长度4。这个字段的存在使得接收方能够知道整个报文的大小,从而进行正确的处理。

  3. 数据包校验和:校验和字段用于检测UDP数据报在传输过程中是否发生了错误。通过计算发送方和接收方之间的校验和,可以确保数据的完整性和准确性。

  4. 伪头部:虽然伪头部并不实际包含在UDP报文中,但它在计算校验和时被考虑进去,以提取IP数据报中的源IP、目的IP信息并加上协议等字段构造的数据。这有助于在不改变原始UDP报文的情况下,正确计算校验和。

  5. 数据字段:数据字段是不定长度的,为上层协议封装好的数据。这意味着UDP支持不同大小的数据传输,提供了灵活性。

在设计UDP协议的数据结构时,需要考虑到这些关键字段的准确性和完整性。例如,源端口号和目的端口号必须准确无误地反映数据包的发送者和接收者;UDP报文整体长度应该准确反映报文的实际大小;校验和字段的设计应确保能够有效地检测数据传输过程中的任何错误。此外,伪头部的设计虽然不直接出现在报文中,但对于确保数据传输的可靠性也是非常重要的。

3.关键代码段及其解释

3.1UDP服务器端

python 复制代码
# UDP服务器
import socket  # 导入Python标准库中的socket模块

# 创建一个socket对象,用于UDP通信
# socket.AF_INET 指定使用IPv4地址
# socket.SOCK_DGRAM 指定使用UDP协议
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 绑定端口,使服务器监听在本地的9999端口
# "127.0.0.1" 是本地回环地址,表示服务器只接受来自本机的连接
s.bind(("127.0.0.1", 9999))

while 1:  # 无限循环,直到服务器被手动停止
    # recvfrom()方法用于接收数据,返回值是(data, address)
    # data 是接收到的数据,address 是发送数据的客户端地址和端口
    # 1024是接收缓冲区的大小
    sock, addr = s.recvfrom(1024)

    # 将接收到的二进制数据解码成字符串
    # 这里可能存在错误,因为recvfrom()返回的sock是二进制数据,不是字符串
    # 正确的做法是指定解码的编码方式,如sock.decode('utf-8')
    message = sock.decode()

    # 打印收到的消息和发送方的地址
    print(message, addr)

    # 如果收到的消息是'exit',则退出循环,关闭服务器
    if message == 'exit':
        break

    # 从标准输入读取数据,这将暂停程序直到用户输入数据
    data = input("I:")

    # 将接收到的消息发送回客户端
    # encode()方法将字符串转换为二进制数据,以便发送
    s.sendto(data.encode(), addr)

# 关闭socket,释放资源
s.close()

3.2UDP客户端

python 复制代码
# UDP客户端(访问端)
import socket  # 导入Python标准库中的socket模块

# 创建一个socket对象,用于UDP通信
# socket.AF_INET 指定使用IPv4地址
# socket.SOCK_DGRAM 指定使用UDP协议
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

while 1:  # 无限循环,直到客户端被手动停止
    # 从标准输入读取数据,这将暂停程序直到用户输入数据
    message = input("I:")

    # 将输入的消息编码为二进制格式,然后发送
    # "127.0.0.1" 是服务器的IP地址,这里假设服务器运行在本机
    # 9999 是服务器监听的端口号
    s.sendto(message.encode(), ("127.0.0.1", 9999))

    # 如果输入的消息是'exit',则退出循环,关闭客户端
    if message == 'exit':
        break

    # 使用recvfrom()方法接收服务器发回的数据
    # recvfrom()方法返回一个元组,包含接收到的数据和发送方的地址
    # 1024 是接收缓冲区的大小
    sock, addr = s.recvfrom(1024)

    # 将接收到的二进制数据解码为字符串,并打印
    # 这里假设数据使用的是UTF-8编码,如果使用其他编码,需要相应修改
    print(sock.decode(), addr)

# 关闭socket,释放资源
s.close()

4.功能展示

  • 运行服务器端程序,等待接收通信信息
  • 运行客户端程序,准备发送通信信息
  • 在客户端程序中输入需要发送的信息1
  • 在服务器端接收发送的信息1
  • 在服务器端发送需要的信息2
  • 在客户端接收发送的信息2

总体通信效果

五、自动驾驶场景通信协议应用示例:以百度Apollo9.0为例

1.Apollo9.0

从工程层面来看,Apollo开放平台9.0对原有的12万行代码进行了重构,以提高系统的效率和可维护性。此外,该版本首次支持ARM架构,这对于广泛应用于移动设备和嵌入式系统的自动驾驶技术来说是一个重要的突破。

在算法方面,Apollo开放平台9.0引入了新的主模型,如激光雷达的CenterPoint模型和相机的yolo模型,这些更新旨在提升感知算法的性能和准确性。同时,该平台还提供了针对预训练神经网络的特化训练方案,使得开发者可以更专注于实际应用场景的开发。

工具方面,Apollo开放平台9.0新增了高精地图制图、传感器标定和集成等工具,这些工具的加入使得自动驾驶系统的搭建和调试更加便捷和高效。此外,新版本还优化了PnC(Perception Network Controller)和感知软件框架接口,使得开发体验更加友好和灵活。

从应用场景的角度,Apollo开放平台9.0不仅加强了通用层的能力,还通过封闭园区低速场景的通用能力与服务,加速企业开发者快速扩展与落地。这一点对于推动自动驾驶技术在不同环境下的应用具有重要意义。

Apollo开源地址:ApolloAuto/apollo: An open autonomous driving platform (github.com)

2.Apollo9.0的Bridge模块

Apollo的Bridge模块是一个关键组件,用于实现与外部系统的通信。这个模块主要通过UDP协议进行数据传输。

Bridge模块包含两个子模块:sender和receiver。Sender负责发送UDP数据,而receiver则负责接收UDP客户端发送的数据包,并解析这些数据,然后将其发送到已注册响应消息的模块。这种设计允许Apollo系统与其他系统或设备进行有效的通信和数据交换。

此外,Bridge模块还支持配置文件的修改,以适应不同的通信需求。例如,可以在modules/bridge/conf文件夹下找到receiver的配置文件,其中可以修改发送的Apollo话题、监听端口号等信息。Bridge模块具有一定的灵活性和可配置性,以适应不同的应用场景。

2.1bridge_sender

launch文件
bash 复制代码
<cyber>
    <module>
        <name>bridge_sender</name>
        <dag_conf>/apollo/modules/bridge/dag/bridge_sender.dag</dag_conf>
        <process_name>udp_bridge_sender</process_name>
    </module>
</cyber>

这段代码启动了一个dag文件

dag文件
bash 复制代码
module_config {
    module_library: "modules/bridge/libudp_bridge_sender_component.so"
    components {

        class_name: "UDPBridgeSenderComponent<planning::ADCTrajectory>"
        config {
            name: "bridge_sender_ADCTrajectory"
            config_file_path: "/apollo/modules/bridge/conf/udp_bridge_sender_adctrajectory.pb.txt"
            readers {
              channel: "/apollo/planning"
            }
      }
   }

    components {

        class_name: "UDPBridgeSenderComponent<localization::LocalizationEstimate>"
        config {
            name: "bridge_sender_LocalizationEstimate"
            config_file_path: "/apollo/modules/bridge/conf/udp_bridge_sender_localization.pb.txt"
            readers {
              channel: "/apollo/localization/pose"
            }
      }
   }
}

这段代码的作用是告诉Cyber RT操作系统如何加载和配置bridge_sender模块,特别是如何创建和初始化两个UDPBridgeSenderComponent组件,以及如何找到这两个组件的配置文件和它们应该监听的数据通道。这两个配置文件内容如下

udp_bridge_sender_adctrajectory.pb.txt

路径:modules/bridge/conf/udp_bridge_sender_adctrajectory.pb.txt

bash 复制代码
remote_ip: "127.0.0.1"
remote_port: 8900
proto_name: "ADCTrajectory"
udp_bridge_sender_localization.pb.txt

路径2:modules/bridge/conf/udp_bridge_sender_localization.pb.txt

bash 复制代码
remote_ip: "127.0.0.1"
remote_port: 8901
proto_name: "LocalizationEstimate"
modules/bridge/udp_bridge_sender_component.cc
cpp 复制代码
// 引入UDP桥接发送组件的头文件。
#include "modules/bridge/udp_bridge_sender_component.h"

// 引入其他必要的头文件。
#include "modules/bridge/common/bridge_proto_serialized_buf.h"
#include "modules/bridge/common/macro.h"
#include "modules/bridge/common/util.h"

namespace apollo {
namespace bridge {

// 宏定义,用于模板类实例化。
#define BRIDGE_IMPL(pb_msg) template class UDPBridgeSenderComponent<pb_msg>

// 使用apollo::bridge命名空间中的UDPBridgeSenderRemoteInfo。
using apollo::bridge::UDPBridgeSenderRemoteInfo;
// 使用apollo::cyber::io命名空间中的Session。
using apollo::cyber::io::Session;
// 使用apollo::localization命名空间中的LocalizationEstimate。
using apollo::localization::LocalizationEstimate;

// UDPBridgeSenderComponent模板类的初始化函数实现。
template <typename T>
bool UDPBridgeSenderComponent<T>::Init() {
  // 记录信息,UDP桥接发送器开始初始化。
  AINFO << "UDP bridge sender init, startin...";
  // 创建UDPBridgeSenderRemoteInfo类型的实例用于存储远程信息。
  apollo::bridge::UDPBridgeSenderRemoteInfo udp_bridge_remote;
  // 获取远程服务器的配置信息,如果失败则返回false。
  if (!this->GetProtoConfig(&udp_bridge_remote)) {
    AINFO << "load udp bridge component proto param failed";
    return false;
  }
  // 获取远程服务器的IP地址、端口号和协议名称。
  remote_ip_ = udp_bridge_remote.remote_ip();
  remote_port_ = udp_bridge_remote.remote_port();
  proto_name_ = udp_bridge_remote.proto_name();
  // 调试信息,记录远程IP地址、端口和协议名称。
  ADEBUG << "UDP Bridge remote ip is: " << remote_ip_;
  ADEBUG << "UDP Bridge remote port is: " << remote_port_;
  ADEBUG << "UDP Bridge for Proto is: " << proto_name_;
  return true;
}

// UDPBridgeSenderComponent模板类的消息处理函数实现。
template <typename T>
bool UDPBridgeSenderComponent<T>::Proc(const std::shared_ptr<T> &pb_msg) {
  // 检查远程端口和IP地址是否有效,如果无效则返回false。
  if (remote_port_ == 0 || remote_ip_.empty()) {
    AERROR << "remote info is invalid!";
    return false;
  }
  // 检查protobuf消息是否已准备好,如果没有准备好则返回false。
  if (pb_msg == nullptr) {
    AERROR << "proto msg is not ready!";
    return false;
  }
  // 创建服务器地址结构体。
  struct sockaddr_in server_addr;
  server_addr.sin_addr.s_addr = inet_addr(remote_ip_.c_str()); // 设置服务器IP地址。
  server_addr.sin_family = AF_INET; // 设置地址族为IPv4。
  server_addr.sin_port = htons(static_cast<uint16_t>(remote_port_)); // 设置服务器端口。
  // 创建一个UDP套接字。
  int sock_fd = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
  // 将本地套接字地址与远程服务器地址进行绑定。
  int res = connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
  // 如果连接失败,则关闭套接字并返回false。
  if (res < 0) {
    close(sock_fd);
    return false;
  }
  // 创建序列化缓冲区对象,用于序列化protobuf消息。
  BridgeProtoSerializedBuf<T> proto_buf;
  // 将protobuf消息序列化。
  proto_buf.Serialize(pb_msg, proto_name_);
  // 循环发送序列化后的每个缓冲区数据。
  for (size_t j = 0; j < proto_buf.GetSerializedBufCount(); j++) {
    ssize_t nbytes = send(sock_fd, proto_buf.GetSerializedBuf(j),
                          proto_buf.GetSerializedBufSize(j), 0);
    // 如果发送的字节数与缓冲区大小不匹配,则中断循环。
    if (nbytes != static_cast<ssize_t>(proto_buf.GetSerializedBufSize(j))) {
      break;
    }
  }
  // 关闭套接字。
  close(sock_fd);
  return true;
}

// 对LocalizationEstimate和planning::ADCTrajectory类型进行模板类实例化。
BRIDGE_IMPL(LocalizationEstimate);
BRIDGE_IMPL(planning::ADCTrajectory);

}  // namespace bridge
}  // namespace apollo

这段代码是基于C++语言,类`UDPBridgeSenderComponent`实现,用于通过UDP协议发送特定类型的消息。这个类接收一个类型参数`T`,该类型参数指定了要发送的消息类型。

数据格式

  • 消息类型(`T`):这是模板参数,可以是任何protobuf消息类型,例如`LocalizationEstimate`或`planning::ADCTrajectory`。
  • 远程信息(`UDPBridgeSenderRemoteInfo`):在`Init`函数中,使用这个结构体来加载远程服务器的IP地址和端口号,以及要发送的protobuf消息的名称。
  • 序列化缓冲区(`BridgeProtoSerializedBuf<T>`):这是一个辅助类,用于将protobuf消息序列化为可以通过网络发送的格式。

数据传输过程

  1. 初始化(`Init`):加载远程服务器的配置信息,包括IP地址(`remote_ip_`)、端口号(`remote_port_`)和protobuf消息名称(`proto_name_`)。
  2. 处理消息(`Proc`):检查远程信息是否有效(IP地址和端口号)。确认要发送的protobuf消息(`pb_msg`)已经准备好。
  3. 创建套接字(`socket`):创建一个UDP套接字用于数据传输。
  4. 设置服务器地址(`sockaddr_in`):使用远程IP地址和端口号设置服务器的地址。
  5. 连接套接字(`connect`):将本地套接字与远程服务器地址关联。
  6. 序列化消息:使用`BridgeProtoSerializedBuf<T>`类将protobuf消息序列化,准备进行网络传输。
  7. 发送消息:通过套接字发送序列化后的消息。消息可能被分割成多个缓冲区,每个缓冲区大小由`proto_buf.GetSerializedBufSize(j)`确定。
  8. 关闭套接字(`close`):数据发送完成后,关闭套接字。
  9. 模板实例化(`BRIDGE_IMPL`):使用宏`BRIDGE_IMPL`对特定的protobuf消息类型实例化`UDPBridgeSenderComponent`模板类,这里指定了`LocalizationEstimate`和`planning::ADCTrajectory`两种类型。

传输过程的关键点

  • UDP协议:使用无连接的UDP协议进行数据传输,它提供了快速的数据传输但不保证数据包的顺序或完整性。
  • 异步传输:`Proc`函数中的数据发送是异步的,函数返回后,数据可能仍在传输中。
  • 错误处理:如果连接失败或发送的数据量不匹配,将关闭套接字并返回`false`。

2.2bridge_receiver

launch文件
bash 复制代码
<cyber>
    <module>
        <name>bridge_receiver</name>
        <dag_conf>/apollo/modules/bridge/dag/bridge_receiver.dag</dag_conf>
        <process_name>udp_bridge_receiver</process_name>
    </module>
</cyber>

这段代码启动了一个dag文件

dag文件
bash 复制代码
module_config {
    module_library: "modules/bridge/libudp_bridge_receiver_component.so"

    components {

        class_name: "UDPBridgeReceiverComponent<canbus::Chassis>"
        config {
            name: "bridge_receiver_Chassis"
            config_file_path: "/apollo/modules/bridge/conf/udp_bridge_receiver_chassis.pb.txt"
      }
   }
}

这段代码的作用是告诉Cyber RT操作系统如何加载和配置bridge_receiver模块,特别是如何创建和初始化UDPBridgeReceiverComponent组件,以及如何找到该组件的配置文件。

udp_bridge_receiver_chassis.pb.txt

配置文件内容如下,路径modules/bridge/conf/udp_bridge_receiver_chassis.pb.txt:

bash 复制代码
topic_name: "/apollo/canbus/Chassis"
bind_port: 8900
proto_name: "Chassis"
enable_timeout: false
modules/bridge/udp_bridge_receiver_component.h
cpp 复制代码
// 引入必要的头文件,用于网络编程。
#include <netinet/in.h>
#include <sys/socket.h>

// 引入C++标准库中的头文件。
#include <cstdlib>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

// 引入protobuf生成的头文件,用于UDP桥接接收组件的配置信息。
#include "modules/bridge/proto/udp_bridge_remote_info.pb.h"
// 引入底盘消息的protobuf生成的头文件。
#include "modules/common_msgs/chassis_msgs/chassis.pb.h"

// 引入Cyber RT框架的相关头文件。
#include "cyber/class_loader/class_loader.h"
#include "cyber/component/component.h"
#include "cyber/cyber.h"
#include "cyber/init.h"
#include "cyber/scheduler/scheduler_factory.h"
// 引入Apollo桥接模块的公共头文件。
#include "modules/bridge/common/bridge_gflags.h"
#include "modules/bridge/common/bridge_header.h"
#include "modules/bridge/common/bridge_proto_diserialized_buf.h"
#include "modules/bridge/common/udp_listener.h"
#include "modules/common/monitor_log/monitor_log_buffer.h"

namespace apollo {
namespace bridge {

// 宏定义,用于注册UDP桥接接收组件。
#define RECEIVER_BRIDGE_COMPONENT_REGISTER(pb_msg) \
  CYBER_REGISTER_COMPONENT(UDPBridgeReceiverComponent<pb_msg>)

// 定义一个模板类UDPBridgeReceiverComponent,它是一个用于接收UDP消息的组件。
template <typename T>
class UDPBridgeReceiverComponent final : public cyber::Component<> {
 public:
  // 构造函数。
  UDPBridgeReceiverComponent();
  // 析构函数。
  ~UDPBridgeReceiverComponent();

  // 初始化函数,用于启动时设置。
  bool Init() override;

  // 返回组件名称。
  std::string Name() const { return FLAGS_bridge_module_name; }
  // 处理接收到的消息。
  bool MsgHandle(int fd);

 private:
  // 初始化会话,绑定端口并监听。
  bool InitSession(uint16_t port);
  // 消息分发处理。
  void MsgDispatcher();
  // 检查特定协议是否存在。
  bool IsProtoExist(const BridgeHeader &header);
  // 根据协议头部信息创建对应的协议缓冲区。
  BridgeProtoDiserializedBuf<T> *CreateBridgeProtoBuf(
      const BridgeHeader &header);
  // 检查是否超时。
  bool IsTimeout(double time_stamp);
  // 移除无效的缓冲区。
  bool RemoveInvalidBuf(uint32_t msg_id);

 private:
  // 监控日志缓冲区。
  common::monitor::MonitorLogBuffer monitor_logger_buffer_;
  // 绑定的端口号。
  unsigned int bind_port_ = 0;
  // 协议名称。
  std::string proto_name_ = "";
  // 主题名称。
  std::string topic_name_ = "";
  // 是否启用超时检测。
  bool enable_timeout_ = true;
  // 消息写入器。
  std::shared_ptr<cyber::Writer<T>> writer_;
  // 互斥锁,用于线程同步。
  std::mutex mutex_;

  // UDP监听器的智能指针。
  std::shared_ptr<UDPListener<UDPBridgeReceiverComponent<T>>> listener_ =
      std::make_shared<UDPListener<UDPBridgeReceiverComponent<T>>>();

  // 存储所有协议缓冲区的列表。
  std::vector<BridgeProtoDiserializedBuf<T> *> proto_list_;
};

// 使用宏注册canbus::Chassis消息类型的UDP桥接接收组件。
RECEIVER_BRIDGE_COMPONENT_REGISTER(canbus::Chassis)
}  // namespace bridge
}  // namespace apollo

这段代码基于C++语言定义类`UDPBridgeReceiverComponent`,用于Apollo自动驾驶平台的UDP桥接接收组件。

数据格式

  • 类模板参数 `T`:这是一个类型参数,用于指定接收消息的数据类型。消息类型是通过protobuf定义的,例如`canbus::Chassis`。
  • `BridgeHeader`:一个结构或类,用于存储UDP消息的头部信息,如消息类型、长度等。
  • `BridgeProtoDiserializedBuf<T>`:一个辅助类,用于存储和操作反序列化后的protobuf消息。

数据传输关键点

  • UDP协议:使用UDP协议进行数据传输,这是一种无连接的协议,适用于需要快速传输的场景。
  • 端口绑定:组件绑定到一个特定的端口上,监听传入的UDP数据包。
  • 消息反序列化:接收到的UDP消息需要被反序列化为对应的protobuf消息对象。
  • 超时检测:组件可以检测消息是否超时,以确保数据的时效性。
  • 线程安全:使用互斥锁确保在多线程环境下的线程安全。

2.3通信传输功能展示

启动bridge_receiver中的launch文件,运行cyber_monitor,我们可以看到UDP服务器接收到的传输的数据

3.CyberRT通信

Apollo 9.0的CyberRT通信机制是一个专为自动驾驶场景设计的高性能运行时框架。CyberRT采用了发布订阅模式(Publish/Subscribe)来实现不同节点之间的数据交互。这种机制允许系统中的各个模块通过消息订阅和发布来进行通信,从而支持高并发、低延迟和高吞吐量的需求。CyberRT还包括了服务客户端机制(Service/Client),这进一步增强了其在自动驾驶系统中的灵活性和效率。此外,CyberRT还实现了服务自发现功能,这有助于自动驾驶系统中各种组件的动态发现和管理。

CyberRT默认使用的是UDP多播机制,这是一种高效的网络通信方式,特别适用于实时数据传输,如自动驾驶系统中的数据交换。此外,CyberRT还使用了protobuf作为不同主机间通信的协议。protobuf是一种高性能、开源的序列化库,常用于微服务架构中,以实现快速的数据交换。

CyberRT的通信结构涉及到多个组件和模块,包括但不限于planning(规划)、control(控制)、canbus(CAN总线)、perception(感知)、prediction(预测)等核心模块。这些模块共同工作,支持CyberRT的各种功能,如车辆的导航、控制和感知等。

CyberRT的通信逻辑主要基于其组件机制和插件机制。通过这些机制,CyberRT能够灵活地集成不同的硬件和软件资源,以适应不同的应用场景。此外,CyberRT还支持通过条件通知(condition notification)的方式来减少延迟,这是通过修改配置文件中的notifier_type参数来实现的。

3.1service_discovery

service_discovery服务发现基于UDP通信协议。Apollo CyberRT默认机制是UDP多播,但系统调用(sendto)会导致一些延迟,因此可以通过更新CyberRT到最新版本并注释掉cyber.pb. conf中的transport_conf来减少延迟。这表明Apollo9.0 CyberRT确实支持并可能默认使用UDP协议进行服务发现。

service_discovery目录依赖关系图

3.2UDP多播

UDP多播(也称为组播)是一种网络通信技术,它允许数据从一个或多个发送者传输到多个接收者。这种技术特别适用于实时媒体流,如视频和音频的广播,因为它可以有效地减少带宽需求并提高传输效率。

在UDP多播中,数据包被发送到一个特定的IP地址,该地址对应于一个多播组。只有加入了该多播组的主机才能接收到数据包。这意味着,如果一个主机没有加入特定的多播组,它就不会接收到任何数据包。

UDP多播的实现依赖于IP协议中的多播地址功能。这些地址是通过将IP地址的一部分设置为1来创建的,这样就可以表示多个接收者的地址。例如,一个常见的多播地址可能是"224.2.2.2",其中最后一个字节被设置为全1,以表示所有接收者都属于同一组。

此外,UDP多播还支持端口复用,即多个应用程序可以监听同一个端口,但只处理来自特定多播组的数据包。这使得UDP多播非常适合需要同时支持多个服务的应用场景。

六、参考资料

相关推荐
hgdlip几秒前
家里电脑ip地址怎么设置?详细指导
网络·tcp/ip·智能路由器·家里电脑
单音GG38 分钟前
推荐一个基于协程的C++(lua)游戏服务器
服务器·c++·游戏·lua
安步当歌1 小时前
【WebRTC】视频发送链路中类的简单分析(下)
网络·音视频·webrtc·视频编解码·video-codec
shitian08111 小时前
用轻量云服务器搭建一个开源的商城系统,含小程序和pc端
服务器·小程序·开源
Biomamba生信基地2 小时前
Linux也有百度云喔~
linux·运维·服务器·百度云
米饭是菜qy2 小时前
TCP 三次握手意义及为什么是三次握手
服务器·网络·tcp/ip
yaoxin5211232 小时前
第十九章 TCP 客户端 服务器通信 - 数据包模式
服务器·网络·tcp/ip
鹿鸣天涯2 小时前
‌华为交换机在Spine-Leaf架构中的使用场景
运维·服务器·网络
星海幻影2 小时前
网络基础-超文本协议与内外网划分(超长版)
服务器·网络·安全
WeeJot嵌入式2 小时前
网络百问百答(一)
网络