赞
踩
UDP(用户数据报协议)是一种无连接的传输层协议,具有简单、高效的特点,适用于一些对数据可靠性要求不高的应用场景。UDP的主要特点包括无连接、不可靠和面向数据报。这意味着在发送数据之前不需要建立连接,UDP不会对数据包的传输进行确认,也不会保证数据的顺序和完整性。由于这些特性,UDP提供了较低的延迟和较高的效率,但同时也意味着可能会有数据丢失或损坏的风险。
UDP通过将数据分割成小的数据包进行传输,每个数据包都包含源端口号和目标端口号。当应用程序想要发送数据时,它将数据传递给UDP协议栈,UDP协议栈将数据打包成数据报(datagram),然后发送给目标地址。这种机制使得UDP特别适合于实时性要求较高的应用,如在线游戏、语音通信等。
UDP的应用场景非常广泛,包括流媒体服务、实时视频流、DNS查询、交易市场数据的组播以及物联网等。这些应用场景通常需要快速、连续地传输大量数据,而UDP正好满足这些需求。例如,在流媒体服务中,UDP的简单数据传输机制和不需要建立连接的特性使其成为理想的选择。
尽管UDP提供了许多优点,但它的不可靠性也意味着在某些情况下可能不适合使用。例如,对于那些对数据完整性和准确性有严格要求的应用,如文件传输或网页浏览,使用TCP可能是更好的选择,因为TCP提供了一种更可靠的数据传输方式。
udp_rcv:这是UDP模块的入口函数,用于处理接收到的UDP数据包。在该函数中会进行一系列检查,并调用其他函数进行处理。如果找不到相应的socket,则该数据包将被丢弃。
udp_sendmsg和udp_send_skb:这两个函数主要涉及UDP数据包的发送过程。udp_sendmsg
函数定义在 net/ipv4/udp.c 中,用于发送UDP数据包到指定的地址和端口。
sendto和recvfrom:这两个函数是专门为UDP协议提供的,用于发送和接收数据。发送时需指明目的地址,而接收时也会提供发送方的地址信息。
bind():虽然不是UDP特有的函数,但在使用UDP进行通信时,通常需要先绑定一个本地地址和端口到套接字上,以便接收来自特定源的数据包。
编程语言:Python、Visual Studio,提供了丰富的库来简化网络编程,适合快速开发和测试
操作系统:Windows
网络调试工具:NetAssist是一款常用于Windows平台的TCP/UDP网络调试工具,支持服务端和客户端的监听,对于编写各种通信协议的程序来说非常方便;SocketTool.exe是一款小巧实用且功能强大的TCP/UDP网络通讯调试工具,绿色免费且无需安装
UDP协议中数据结构的选择和设计主要涉及到UDP报文的基本结构,包括源端口号、目的端口号、UDP报文整体长度和数据包校验和等关键字段。我们可以总结出以下几点:
源端口号和目的端口号:这两个字段分别占用2个字节,用于标识数据包的发送者端口号和接收者端口号。这是UDP报文中最基本也是最重要的两个字段,确保了数据能够正确地从一个端口发送到另一个端口。
UDP报文整体长度:这个字段用于表示UDP报文的总长度,包括头部和数据区的长度4。这个字段的存在使得接收方能够知道整个报文的大小,从而进行正确的处理。
数据包校验和:校验和字段用于检测UDP数据报在传输过程中是否发生了错误。通过计算发送方和接收方之间的校验和,可以确保数据的完整性和准确性。
伪头部:虽然伪头部并不实际包含在UDP报文中,但它在计算校验和时被考虑进去,以提取IP数据报中的源IP、目的IP信息并加上协议等字段构造的数据。这有助于在不改变原始UDP报文的情况下,正确计算校验和。
数据字段:数据字段是不定长度的,为上层协议封装好的数据。这意味着UDP支持不同大小的数据传输,提供了灵活性。
在设计UDP协议的数据结构时,需要考虑到这些关键字段的准确性和完整性。例如,源端口号和目的端口号必须准确无误地反映数据包的发送者和接收者;UDP报文整体长度应该准确反映报文的实际大小;校验和字段的设计应确保能够有效地检测数据传输过程中的任何错误。此外,伪头部的设计虽然不直接出现在报文中,但对于确保数据传输的可靠性也是非常重要的。
- # 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()
- # 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()
总体通信效果
从工程层面来看,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)
Apollo的Bridge模块是一个关键组件,用于实现与外部系统的通信。这个模块主要通过UDP协议进行数据传输。
Bridge模块包含两个子模块:sender和receiver。Sender负责发送UDP数据,而receiver则负责接收UDP客户端发送的数据包,并解析这些数据,然后将其发送到已注册响应消息的模块。这种设计允许Apollo系统与其他系统或设备进行有效的通信和数据交换。
此外,Bridge模块还支持配置文件的修改,以适应不同的通信需求。例如,可以在modules/bridge/conf文件夹下找到receiver的配置文件,其中可以修改发送的Apollo话题、监听端口号等信息。Bridge模块具有一定的灵活性和可配置性,以适应不同的应用场景。
- <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文件
- 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
组件,以及如何找到这两个组件的配置文件和它们应该监听的数据通道。这两个配置文件内容如下
路径:modules/bridge/conf/udp_bridge_sender_adctrajectory.pb.txt
- remote_ip: "127.0.0.1"
- remote_port: 8900
- proto_name: "ADCTrajectory"
路径2:modules/bridge/conf/udp_bridge_sender_localization.pb.txt
- remote_ip: "127.0.0.1"
- remote_port: 8901
- proto_name: "LocalizationEstimate"
- // 引入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`,该类型参数指定了要发送的消息类型。
数据格式
数据传输过程
传输过程的关键点
- <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文件
- 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
组件,以及如何找到该组件的配置文件。
配置文件内容如下,路径modules/bridge/conf/udp_bridge_receiver_chassis.pb.txt:
- topic_name: "/apollo/canbus/Chassis"
- bind_port: 8900
- proto_name: "Chassis"
- enable_timeout: false
- // 引入必要的头文件,用于网络编程。
- #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桥接接收组件。
数据格式
数据传输关键点
启动bridge_receiver中的launch文件,运行cyber_monitor,我们可以看到UDP服务器接收到的传输的数据
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参数来实现的。
service_discovery服务发现基于UDP通信协议。Apollo CyberRT默认机制是UDP多播,但系统调用(sendto)会导致一些延迟,因此可以通过更新CyberRT到最新版本并注释掉cyber.pb. conf中的transport_conf来减少延迟。这表明Apollo9.0 CyberRT确实支持并可能默认使用UDP协议进行服务发现。
service_discovery目录依赖关系图
UDP多播(也称为组播)是一种网络通信技术,它允许数据从一个或多个发送者传输到多个接收者。这种技术特别适用于实时媒体流,如视频和音频的广播,因为它可以有效地减少带宽需求并提高传输效率。
在UDP多播中,数据包被发送到一个特定的IP地址,该地址对应于一个多播组。只有加入了该多播组的主机才能接收到数据包。这意味着,如果一个主机没有加入特定的多播组,它就不会接收到任何数据包。
UDP多播的实现依赖于IP协议中的多播地址功能。这些地址是通过将IP地址的一部分设置为1来创建的,这样就可以表示多个接收者的地址。例如,一个常见的多播地址可能是"224.2.2.2",其中最后一个字节被设置为全1,以表示所有接收者都属于同一组。
此外,UDP多播还支持端口复用,即多个应用程序可以监听同一个端口,但只处理来自特定多播组的数据包。这使得UDP多播非常适合需要同时支持多个服务的应用场景。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。