第 19.1.5 節

UDP communication

0瀏覽次數0訪問次數--跳出率--平均停留

UDP is a connectionless datagram protocol. In robots, UDP is commonly used for: low-latency state broadcasting on a local network, sensor data broadcasting, custom lightweight communication, host computer device discovery, etc.

The biggest difference between UDP and TCP:

TCP 是字节流,没有消息边界。
UDP 是数据报,一次 send_to 对应对端一次 receive_from 的一个数据报。

But UDP does not guarantee reliability, ordering, or delivery.


Example 1: Writing a synchronous UDP Receiver in a normal main()

program objective

Write a UDP receiver:

  1. Bind local port 9001;
  2. block waiting for a UDP datagram;
  3. Print the sender's address and content;
  4. Program ended.

Full code

#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <iostream>
#include <string>

using boost::asio::ip::udp;

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
    boost::asio::io_context io;
    boost::system::error_code ec;

    // UDP socket 用来发送或接收无连接的数据报。
    udp::socket socket(io, udp::endpoint(udp::v4(), 9001));

    std::array<char, 1024> data;
    udp::endpoint sender_endpoint;

    std::cout << "receiver:监听 UDP 0.0.0.0:9001,等待数据" << std::endl;

    // receive_from 会等待并接收一个 UDP 数据报。
    std::size_t n = socket.receive_from(boost::asio::buffer(data), sender_endpoint, 0, ec);
    if (ec)
    {
        std::cout << "receiver:接收失败:" << ec.message() << std::endl;
        return 1;
    }

    std::string msg(data.data(), n);

    std::cout << "receiver:收到来自 " << sender_endpoint.address().to_string()
              << ":" << sender_endpoint.port()
              << " 的 " << n << " 字节:" << msg << std::endl;

    return 0;
}

运行结果:见下方“运行输出与时间顺序”;如果示例涉及定时器、线程、网络或外部设备,具体时间和顺序可能会随环境略有变化。

Compile and run

Terminal 1: Run the receiver.

g++ demo1_udp_receiver.cpp -o demo1_udp_receiver -std=c++17 -lboost_system -pthread
./demo1_udp_receiver

Terminal 2: Send UDP data with nc.

echo "hello udp" | nc -u 127.0.0.1 9001

Execution output and chronological order

The receiver immediately outputs after startup:

receiver:监听 UDP 0.0.0.0:9001,等待数据

At this point, the program is blocked at:

socket.receive_from(...)

After sending data, the receiving end outputs something like:

receiver:收到来自 127.0.0.1:xxxxx 的 10 字节:hello udp

where xxxxx is the temporary port on the sender side, which may differ each time.

Points to note for this example

The UDP receiver does not need accept(), because UDP is connectionless.

As long as you bind the port, you can receive datagrams sent by others.

Key Function Explanation

udp::socket socket(io, udp::endpoint(udp::v4(), 9001));

Purpose: Create UDP socket and bind to local port 9001.

socket.receive_from(buffer, sender_endpoint, 0, ec);

Function: Synchronously receives a UDP datagram.

Return value: number of bytes received.

sender_endpoint will be filled with the sender's IP and port.


Example 2: Writing a synchronous UDP Sender in an ordinary main()

program objective

Write a UDP sender to send a message to 127.0.0.1:9001.

Full code

#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <iostream>
#include <string>

using boost::asio::ip::udp;

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
    boost::asio::io_context io;
    boost::system::error_code ec;

    // UDP socket 用来发送或接收无连接的数据报。
    udp::socket socket(io);
    socket.open(udp::v4(), ec);
    if (ec)
    {
        std::cout << "sender:打开 socket 失败:" << ec.message() << std::endl;
        return 1;
    }

    udp::endpoint receiver_endpoint(boost::asio::ip::make_address("127.0.0.1"), 9001);

    std::string msg = "hello udp";

    std::cout << "sender:准备发送到 127.0.0.1:9001" << std::endl;

    // send_to 指定目标地址发送 UDP 数据报。
    std::size_t n = socket.send_to(boost::asio::buffer(msg), receiver_endpoint, 0, ec);
    if (ec)
    {
        std::cout << "sender:发送失败:" << ec.message() << std::endl;
        return 1;
    }

    std::cout << "sender:发送完成,字节数 = " << n << std::endl;

    return 0;
}

运行结果:见下方“运行输出与时间顺序”;如果示例涉及定时器、线程、网络或外部设备,具体时间和顺序可能会随环境略有变化。

Compile and run

Terminal 1: First run Example 1 receiver.

./demo1_udp_receiver

Terminal 2: Run the sender.

g++ demo2_udp_sender.cpp -o demo2_udp_sender -std=c++17 -lboost_system -pthread
./demo2_udp_sender

Execution output and chronological order

Transmitter output:

sender:准备发送到 127.0.0.1:9001
sender:发送完成,字节数 = 9

The output from the receiving end is similar:

receiver:监听 UDP 0.0.0.0:9001,等待数据
receiver:收到来自 127.0.0.1:xxxxx 的 9 字节:hello udp

Points to note for this example

UDP sending does not require connecting to the server first.

As long as you know the other party's IP and port, you can:

send_to(...)

But that doesn't mean the other party has definitely received it.


Example 3: Writing an Asynchronous UDP Receiver in a Regular main()

program objective

Change Example 1 to an asynchronous version:

  1. Register async_receive_from();
  2. io.run() waiting for data;
  3. After receiving a UDP datagram, execute a callback;
  4. Program ended.

Full code

#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <functional>
#include <iostream>
#include <string>

using boost::asio::ip::udp;

void on_receive(const boost::system::error_code& ec,
                std::size_t bytes_transferred,
                std::array<char, 1024>* data,
                udp::endpoint* sender_endpoint)
{
    if (ec)
    {
        std::cout << "on_receive:接收失败:" << ec.message() << std::endl;
        return;
    }

    std::string msg(data->data(), bytes_transferred);

    std::cout << "on_receive:收到来自 " << sender_endpoint->address().to_string()
              << ":" << sender_endpoint->port()
              << " 的 " << bytes_transferred << " 字节:" << msg << std::endl;
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
    boost::asio::io_context io;
    // UDP socket 用来发送或接收无连接的数据报。
    udp::socket socket(io, udp::endpoint(udp::v4(), 9001));

    std::array<char, 1024> data;
    udp::endpoint sender_endpoint;

    std::cout << "main:注册 async_receive_from" << std::endl;

    socket.async_receive_from(boost::asio::buffer(data),
                              sender_endpoint,
                              std::bind(on_receive,
                                        std::placeholders::_1,
                                        std::placeholders::_2,
                                        &data,
                                        &sender_endpoint));

    std::cout << "main:async_receive_from 已返回,准备 io.run()" << std::endl;

    // 启动事件循环,前面注册的异步任务会在这里被调度执行。
    io.run();

    std::cout << "main:io.run() 返回" << std::endl;

    return 0;
}

运行结果:见下方“运行输出与时间顺序”;如果示例涉及定时器、线程、网络或外部设备,具体时间和顺序可能会随环境略有变化。

Compile and run

Terminal 1: Run the asynchronous receiver.

g++ demo3_async_udp_receiver.cpp -o demo3_async_udp_receiver -std=c++17 -lboost_system -pthread
./demo3_async_udp_receiver

Terminal 2: Send UDP data.

echo "imu udp" | nc -u 127.0.0.1 9001

Execution output and chronological order

The receiver immediately outputs after startup:

main:注册 async_receive_from
main:async_receive_from 已返回,准备 io.run()

Output after sending data:

on_receive:收到来自 127.0.0.1:xxxxx 的 8 字节:imu udp
main:io.run() 返回

请提供您要翻译的简体中文 Markdown 片段。

main:注册 async_receive_from
main:async_receive_from 已返回,准备 io.run()
on_receive:收到来自 127.0.0.1:xxxxx 的 8 字节:imu udp
main:io.run() 返回

Points to note for this example

data and sender_endpoint are both local variables, but this example is safe because:

main() 卡在 io.run()
回调执行完之前,data 和 sender_endpoint 不会析构

In engineering, it is recommended to write them as class member variables.

async_receive_from() Description

socket.async_receive_from(buffer, sender_endpoint, handler);

Purpose: Asynchronously receive a UDP datagram.

Callback parameter:

const boost::system::error_code& ec
std::size_t bytes_transferred

Example 4: Writing an Asynchronous UDP Echo Server in a Class

program objective

Write an asynchronous UDP Echo Server:

  1. Continuously receive UDP datagrams;
  2. Print sender and content;
  3. 原样发回发送方;
  4. 请提供您需要翻译的简体中文 Markdown 片段,我会按照要求翻译成地道的美式英语。

Full code

#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <functional>
#include <iostream>
#include <string>

using boost::asio::ip::udp;

class UdpEchoServer
{
public:
    UdpEchoServer(boost::asio::io_context& io, unsigned short port)
        : socket_(io, udp::endpoint(udp::v4(), port))
    {
        std::cout << "UdpEchoServer:监听 UDP 端口 " << port << std::endl;
        start_receive();
    }

private:
    void start_receive()
    {
        socket_.async_receive_from(boost::asio::buffer(data_),
                                   remote_endpoint_,
                                   std::bind(&UdpEchoServer::on_receive,
                                             this,
                                             std::placeholders::_1,
                                             std::placeholders::_2));
    }

    void on_receive(const boost::system::error_code& ec, std::size_t bytes_transferred)
    {
        if (ec)
        {
            std::cout << "UdpEchoServer:接收失败:" << ec.message() << std::endl;
            start_receive();
            return;
        }

        std::string msg(data_.data(), bytes_transferred);

        std::cout << "收到 " << remote_endpoint_.address().to_string()
                  << ":" << remote_endpoint_.port()
                  << " 的数据:" << msg << std::endl;

        socket_.async_send_to(boost::asio::buffer(data_, bytes_transferred),
                              remote_endpoint_,
                              std::bind(&UdpEchoServer::on_send,
                                        this,
                                        std::placeholders::_1,
                                        std::placeholders::_2));
    }

    void on_send(const boost::system::error_code& ec, std::size_t bytes_transferred)
    {
        if (ec)
        {
            std::cout << "UdpEchoServer:发送失败:" << ec.message() << std::endl;
        }
        else
        {
            std::cout << "回显完成,字节数 = " << bytes_transferred << std::endl;
        }

        start_receive();
    }

private:
    // UDP socket 用来发送或接收无连接的数据报。
    udp::socket socket_;
    udp::endpoint remote_endpoint_;
    std::array<char, 1024> data_;
};

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
    boost::asio::io_context io;
    UdpEchoServer server(io, 9001);

    std::cout << "main:调用 io.run()" << std::endl;

    // 启动事件循环,前面注册的异步任务会在这里被调度执行。
    io.run();

    std::cout << "main:io.run() 返回" << std::endl;

    return 0;
}

运行结果:见下方“运行输出与时间顺序”;如果示例涉及定时器、线程、网络或外部设备,具体时间和顺序可能会随环境略有变化。

Compile and run

Terminal 1: Run UDP Echo Server.

g++ demo4_udp_echo_server.cpp -o demo4_udp_echo_server -std=c++17 -lboost_system -pthread
./demo4_udp_echo_server

Terminal 2: send and receive echo using nc.

nc -u 127.0.0.1 9001

Then input:

hello
robot

Execution output and chronological order

Server startup output:

UdpEchoServer:监听 UDP 端口 9001
main:调用 io.run()

After the client inputs hello and presses Enter, the server outputs something like:

收到 127.0.0.1:xxxxx 的数据:hello
回显完成,字节数 = 6

The client will see an echo:

hello

After continuing to enter robot, the server outputs:

收到 127.0.0.1:xxxxx 的数据:robot
回显完成,字节数 = 6

Points to note for this example

In UDP Echo Server, these two member variables are important:

udp::endpoint remote_endpoint_;
std::array<char, 1024> data_;

They must survive until the asynchronous callback completes.

async_send_to() Description

socket_.async_send_to(buffer, remote_endpoint_, handler);

Function: Asynchronously send a UDP datagram to the specified endpoint.

Callback parameter:

const boost::system::error_code& ec
std::size_t bytes_transferred

Example 5: Writing a Periodic UDP Sender in a Class

program objective

Robots often need to periodically broadcast status, for example:

robot alive
robot alive
robot alive

This example uses a timer to send a message via UDP every 1 second, for a total of 5 times.

Full code

#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <chrono>
#include <functional>
#include <iostream>
#include <string>

using boost::asio::ip::udp;

class UdpHeartbeatSender
{
public:
    UdpHeartbeatSender(boost::asio::io_context& io,
                       const std::string& host,
                       unsigned short port)
        : socket_(io),
          endpoint_(boost::asio::ip::make_address(host), port),
          timer_(io),
          count_(0)
    {
        socket_.open(udp::v4());
    }

    void start()
    {
        schedule_send();
    }

private:
    void schedule_send()
    {
        timer_.expires_after(std::chrono::seconds(1));
        // 注册异步等待:这一行不会阻塞,回调会在定时器到期后执行。
        timer_.async_wait(std::bind(&UdpHeartbeatSender::on_timer,
                                    this,
                                    std::placeholders::_1));
    }

    void on_timer(const boost::system::error_code& ec)
    {
        if (ec)
        {
            std::cout << "timer 取消:" << ec.message() << std::endl;
            return;
        }

        ++count_;
        msg_ = "heartbeat " + std::to_string(count_);

        socket_.async_send_to(boost::asio::buffer(msg_),
                              endpoint_,
                              std::bind(&UdpHeartbeatSender::on_send,
                                        this,
                                        std::placeholders::_1,
                                        std::placeholders::_2));
    }

    void on_send(const boost::system::error_code& ec, std::size_t bytes_transferred)
    {
        if (ec)
        {
            std::cout << "发送失败:" << ec.message() << std::endl;
            return;
        }

        std::cout << "发送 " << msg_ << ",字节数 = " << bytes_transferred << std::endl;

        if (count_ < 5)
        {
            schedule_send();
        }
        else
        {
            std::cout << "发送 5 次完成" << std::endl;
        }
    }

private:
    udp::socket socket_;
    udp::endpoint endpoint_;
    // 创建定时器,并设置到期时间。
    boost::asio::steady_timer timer_;
    std::string msg_;
    int count_;
};

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
    boost::asio::io_context io;
    UdpHeartbeatSender sender(io, "127.0.0.1", 9001);

    sender.start();

    std::cout << "main:调用 io.run()" << std::endl;

    io.run();

    std::cout << "main:io.run() 返回" << std::endl;

    return 0;
}

运行结果:见下方“运行输出与时间顺序”;如果示例涉及定时器、线程、网络或外部设备,具体时间和顺序可能会随环境略有变化。

Compile and run

Terminal 1: Listen on UDP.

nc -u -l 9001

Terminal 2: Run the program.

g++ demo5_udp_heartbeat.cpp -o demo5_udp_heartbeat -std=c++17 -lboost_system -pthread
./demo5_udp_heartbeat

Execution output and chronological order

The program immediately outputs:

main:调用 io.run()

Output in about 1 second:

发送 heartbeat 1,字节数 = 11

Then output approximately every 1 second:

发送 heartbeat 2,字节数 = 11
发送 heartbeat 3,字节数 = 11
发送 heartbeat 4,字节数 = 11
发送 heartbeat 5,字节数 = 11
发送 5 次完成
main:io.run() 返回

The monitoring terminal will sequentially receive:

heartbeat 1heartbeat 2heartbeat 3heartbeat 4heartbeat 5

Some versions of nc won't automatically display line by line, because we didn't add \n at the end of the message. You can:

msg_ = "heartbeat " + std::to_string(count_);

Please provide the Simplified Chinese Markdown fragment you'd like me to translate.

msg_ = "heartbeat " + std::to_string(count_) + "\n";

Points to note for this example

This example combines timer and UDP:

timer 到期 -> 发送 UDP -> 发送完成 -> 再注册下一次 timer

This is the basic structure of "periodic status reporting" in many robotics projects.


Section Summary

  1. UDP does not require a connection, nor does it have accept().
  2. UDP sends one datagram per transmission, but delivery is not guaranteed.
  3. The receiver should save the sender's endpoint and use it when replying.
  4. The buffer and endpoint of asynchronous UDP must live until the callback completes.
  5. Periodic UDP transmission can be implemented using steady_timer.
音乐页