基于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服务器端

# 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客户端

# 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文件
<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文件
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

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

remote_ip: "127.0.0.1"
remote_port: 8901
proto_name: "LocalizationEstimate"
modules/bridge/udp_bridge_sender_component.cc
// 引入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文件
<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文件
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:

topic_name: "/apollo/canbus/Chassis"
bind_port: 8900
proto_name: "Chassis"
enable_timeout: false
modules/bridge/udp_bridge_receiver_component.h
// 引入必要的头文件,用于网络编程。
#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多播非常适合需要同时支持多个服务的应用场景。

六、参考资料

  • python之实现TCP、UDP通信_tcp /udppython 实现-CSDN博客
  • 【Python网络编程】TCP和UDP的使用(附实例)_python udp chrdely-CSDN博客
  • Apollo模块之bridge——通过UDP与外部通讯1_apollo bridge-CSDN博客
  • apollo介绍之cyber设计(一)_Apollo开发者社区 (baidu.com)
  • 百度Apollo系统学习-Cyber RT 通信-服务发现_apollo 服务发现-CSDN博客
  • 自动驾驶仿真 (四)—— 基于PreScan与Simulink的UDP数据通信_simulation pace-CSDN博客
  • Apollo详解之bridge模块——udp_bridge_sender_component_apollo中的bridge-CSDN博客
  • ApolloAuto/apollo: An open autonomous driving platform (github.com)
  • Apollo Cyber RT:引领实时操作系统在自动驾驶领域的创新_自动驾驶 实时操作系统-CSDN博客
  • 基于TCP通信的简单服务端和客户端程序_下列是关于基于tcp的客户和服务端程序-CSDN博客
  • C++ 网络编程下的socket编程(TCP\UDP),连接下位机_c++中socket面向无连接函数-CSDN博客
  • 一文读懂Apollo开放平台9.0_Apollo开发者社区 (baidu.com)
  • Apollo 自动驾驶开放平台
  • 在本地搭建基于TCP协议的服务器端和客户端(实现通信)——如果天总也不亮,那就摸黑过生活_搭建一个本地服务器用来tcp通信-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/624486.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

撤销 git add 操作(忽略被追踪的文件)

文章目录 引言I git rm命令来取消暂存【推荐】II 撤销特定文件的暂存状态2.1 git rese2.2 git restoresee also引言 应用场景: 修改.gitignoregitignore只能忽略那些原来没有被追踪的文件,如果某些文件已经被纳入了版本管理中,则修改.gitignore是无效的。那么解决方法就是先…

国产化开源鸿蒙系统智能终端RK3568主板在电子班牌项目的应用

国产化开源鸿蒙系统智能终端主板AIoT-3568A、人脸识别算法的的电子班牌方案可支持校园信息发布、人脸识别考勤、考场管理、查询互动等多项功能&#xff0c;助力学校在硬件上实现信息化、网络化、数字化&#xff0c;构建“学校、教师、学生”三个维度的智慧教育空间。 方案优势 …

微软推出的Microsoft Fabric 到底是什么?

近期&#xff0c;总有客户问小编&#xff0c;微软推出的 Microsoft Fabric 是什么&#xff1f;这个产品有什么特别之处呢&#xff1f;希望下面这篇文章能为大家解开一些疑惑。 微软Fabric是2023年5月推出的一个数据分析平台&#xff0c;它将关键数据管理和分析工作负载整合到一…

618值得入手的数码产品怎么选?2024 买过不后悔的数码好物分享

在数字时代的浪潮中&#xff0c;每一次的购物狂欢节都如同一场科技盛宴&#xff0c;让我们有机会接触到最前沿、最实用的数码产品&#xff0c;而“618”无疑是这场盛宴中最为引人瞩目的日子之一。面对琳琅满目的商品&#xff0c;如何选择那些真正值得入手的数码好物&#xff0c…

Java全局异常处理,@ControllerAdvice异常拦截原理解析【简单易懂】

https://www.bilibili.com/video/BV1sS411c7Mo 文章目录 一、全局异常处理器的类型1-1、实现方式一1-2、实现方式二 二、全局异常拦截点2-1、入口2-2、全局异常拦截器是如何注入到 DispatcherServlet 的 三、ControllerAdvice 如何解析、执行3-1、解析3-2、执行 四、其它4-1、设…

pdf怎么标注红色方框?五种PDF标注红色方框方法

pdf怎么标注红色方框&#xff1f;在当今数字化时代&#xff0c;PDF文档已成为我们日常工作和学习中不可或缺的一部分。然而&#xff0c;如何在海量的PDF文件中快速、准确地标注出重要信息&#xff0c;让内容更加醒目呢&#xff1f;今天&#xff0c;我将向大家介绍五种PDF标注红…

AI 图像生成-环境配置

一、python环境安装 Windows安装Python&#xff08;图解&#xff09; 二、CUDA安装 CUDA安装教程&#xff08;超详细&#xff09;-CSDN博客 三、Git安装 git安装教程&#xff08;详细版本&#xff09;-CSDN博客 四、启动器安装 这里安装的是秋叶aaaki的安装包 【AI绘画…

苹果cms:搜索功能的开关与设置

今天有个小伙伴问了个关于苹果cms搜索的问题&#xff1a;直接搜演员搜索不到影片信息&#xff08;如下图&#xff09; 1、我们拿演员王宝强为例&#xff1a;搜索王宝强后结果显示无相关视频 2、但是我们搜索王宝强主演的“大闹天竺”后却能得到关于王宝强的影片信息。这是为什…

【无重复字符的最长字串】

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 Problem: 3. 无重复字符的最长子串 文章目录 1、思路2、解题方法3、复杂度4、Code5、结语 1、思…

string容器-构造函数

基本概念 string本质上是一个类string类内部封装了很多成员方法&#xff0c;例如&#xff1a;查找find、拷贝copy&#xff0c;删除delete&#xff0c;替换replace&#xff0c;插入insertstring管理char*所分配的内存&#xff0c;不用担心复制越界和取值越界等&#xff0c;由类…

CentOS7中如何docker-compose

在 CentOS 7 上安装 docker-compose 需要几个步骤 步骤 1: 安装 Docker 首先&#xff0c;确保你已经安装了 Docker。如果没有安装&#xff0c;可以通过以下命令安装&#xff1a; sudo yum update -y sudo yum install -y yum-utils sudo yum-config-manager --add-repo http…

利用MMDetection进行模型微调和权重初始化

目录 模型微调修改第一处&#xff1a;更少的训练回合Epoch修改第二处&#xff1a;更小的学习率Learning Rate修改第三处&#xff1a;使用预训练模型 权重初始化init_cfg 的使用配置初始化器 本文基于 MMDetection官方文档&#xff0c;对模型微调和权重初始化进行第三方讲解。 …

漏桶算法:稳定处理大量突发流量的秘密武器!

漏桶算法的介绍 我们经常会遇到这样一种情况&#xff1a;数据包的发送速率不稳定&#xff0c;而网络的带宽有限。如果在短时间内有大量的数据包涌入&#xff0c;那么网络就会出现拥塞&#xff0c;数据包的丢失率就会增大。为了解决这个问题&#xff0c;人们提出了一种叫做“漏…

RockChip Android8.1 EthernetService分析

一:概述 本篇文章将围绕RK Android8.1 SDK对Ethernet做一次框架分析,包含Framework层和APP层。 当前版本SDK默认只支持一路Ethernet,熟悉Ethernet工作流程后通过修改最终会在系统Setting以太网中呈现多路选项(可以有多种实现方式),博主通过增加ListPreference实现的效果…

鸿蒙内核源码分析(特殊进程篇)

三个进程 鸿蒙有三个特殊的进程&#xff0c;创建顺序如下: 2号进程&#xff0c;KProcess&#xff0c;为内核态根进程.启动过程中创建.0号进程&#xff0c;KIdle为内核态第二个进程&#xff0c;它是通过KProcess fork 而来的.这有点难理解.1号进程&#xff0c;init&#xff0c…

Linux-远程登录

远程登录Linux服务器的两款小工具&#xff1a; 1、Xshell &#xff08;可以远程登录到Linux终端控制台&#xff09; 2、 Xftp (可以与Linux服务器互相传递文件) 家庭/学校免费 - NetSarang Website 下载地址 1、傻瓜式安装Xshell6 2、在Linux主机上查看 Linux主机的…

天府锋巢直播基地运营方——树莓集团:构建3+3+1运营体系

天府锋巢直播产业基地作为一座充满活力和创新精神的成都数字产业园区&#xff0c;自其诞生之初便承载着引领直播产业发展的使命。作为该基地的运营方&#xff0c;树莓集团以其前瞻性的视野和深厚的行业积淀&#xff0c;成功构建了331运营体系&#xff0c;为入驻企业提供全生命周…

MHD093C-058-PG1-AA具备哪些特点?

MHD093C-058-PG1-AA是一种高性能的伺服电机控制器。 该产品具备以下特点&#xff1a; 高精度与高性能&#xff1a;MHD093C-058-PG1-AA设计用于提供精确的运动控制和定位&#xff0c;适用于需要高精度定位和控制的场合。快速响应&#xff1a;采用先进的控制技术&#xff0c;确…

八字排盘软件-​无敌八字排盘软件

功能介绍 1.完全免费使用&#xff0c;即使用不需要付费且无任何限制。 2.同时推出手机版电脑版&#xff0c;两版本数据互通互用&#xff0c;即电脑版的数据可以备份到手机版上导入&#xff0c;手机版的数据也可以备份到电脑版上恢复导入&#xff0c;方便手机和电脑共用的朋友。…

Vue3实战笔记(20)—封装头部导航组件

文章目录 前言一、封装头部导航栏二、使用步骤总结 前言 Vue 3 封装头部导航栏有助于提高代码复用性、统一风格、降低维护成本、提高可配置性和模块化程度&#xff0c;同时还可以实现动态渲染等功能&#xff0c;有利于项目开发和维护。 一、封装头部导航栏 封装头部导航栏&am…