首页 自动驾驶

C++ MFC 结合 Boost.Asio 实现高可靠串口通信:异步收发、重连机制全解析

分类:自动驾驶
字数: (2372)
阅读: (4501)
内容摘要:C++ MFC 结合 Boost.Asio 实现高可靠串口通信:异步收发、重连机制全解析,

在工业控制、嵌入式系统等领域,串口通信仍然是一种常用的数据传输方式。使用 C++ 和 MFC 构建上位机软件时,结合 Boost.Asio 库可以方便地实现异步串口通信,提高程序的响应速度和稳定性。然而,在实际应用中,串口设备的连接不稳定、数据传输错误等问题时常发生,因此需要设计一套完善的串口功能,包括发送、异步接收、打开、重连、关闭等,以保证通信的可靠性。

本篇文章将深入探讨如何利用 Boost.Asio 库在 MFC 框架下实现稳定可靠的串口功能,并分享一些实战避坑经验。

C++ MFC 结合 Boost.Asio 实现高可靠串口通信:异步收发、重连机制全解析

Boost.Asio 串口通信底层原理剖析

Boost.Asio 是一个跨平台的 C++ 库,用于网络和底层 I/O 编程。其核心思想是异步操作,通过事件驱动的方式处理 I/O 事件,避免阻塞主线程。在使用 Boost.Asio 进行串口通信时,主要涉及以下几个关键概念:

C++ MFC 结合 Boost.Asio 实现高可靠串口通信:异步收发、重连机制全解析
  • asio::io_context: I/O 上下文,所有 Asio 操作都需要在一个 io_context 中执行。类似于 Linux 系统中的 epoll 或者 Windows 系统中的 IOCP,管理着所有异步操作。
  • asio::serial_port: 串口对象,用于表示一个串口设备。我们可以通过指定串口名称、波特率、校验位等参数来配置串口。
  • asio::async_readasio::async_write: 异步读写函数,用于从串口读取数据或向串口写入数据。这些函数接受一个回调函数作为参数,当读写操作完成时,回调函数会被调用。
  • asio::error_code: 错误码,用于指示操作是否成功。在回调函数中,我们需要检查 error_code 的值,以判断是否发生了错误。

在异步读取数据时,asio::async_read 函数会立即返回,不会阻塞当前线程。当串口接收到数据时,操作系统会通知 Asio,然后 Asio 会调用我们指定的回调函数。在回调函数中,我们可以处理接收到的数据。

C++ MFC 结合 Boost.Asio 实现高可靠串口通信:异步收发、重连机制全解析

这种异步模式可以有效地提高程序的并发性,避免阻塞主线程,从而提高程序的响应速度。

C++ MFC 结合 Boost.Asio 实现高可靠串口通信:异步收发、重连机制全解析

MFC 界面线程与 Asio I/O 线程的协作

由于 MFC 的界面操作需要在主线程中进行,而 Boost.Asio 的 I/O 操作可以在独立的线程中进行,因此需要在两者之间进行协作。一种常见的做法是使用 PostMessage 函数将数据或事件从 I/O 线程发送到 MFC 主线程,然后在主线程中处理这些数据或事件。

基于 Boost.Asio 的 MFC 串口功能实现

下面是一个简单的示例代码,展示了如何在 MFC 中使用 Boost.Asio 实现串口的打开、发送、异步接收和关闭功能。

// SerialPort.h
#pragma once
#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
#include <string>
#include <vector>

class SerialPort
{
public:
    SerialPort(asio::io_context& io_context, const std::string& port_name);
    ~SerialPort();

    bool open();
    void close();
    bool isOpen() const { return is_open_; }

    void async_read(std::vector<unsigned char>& buffer, std::function<void(const asio::error_code&, size_t)> callback);
    void async_write(const std::vector<unsigned char>& data, std::function<void(const asio::error_code&, size_t)> callback);

private:
    asio::io_context& io_context_;
    asio::serial_port serial_port_;
    std::string port_name_;
    bool is_open_ = false;

    void do_read();
};

// SerialPort.cpp
#include "SerialPort.h"
#include <iostream>

SerialPort::SerialPort(asio::io_context& io_context, const std::string& port_name)
    : io_context_(io_context),
      serial_port_(io_context, port_name),
      port_name_(port_name)
{
}

SerialPort::~SerialPort()
{
    close();
}

bool SerialPort::open()
{
    try
    {
        serial_port_.set_option(asio::serial_port_base::baud_rate(115200)); // 设置波特率
        serial_port_.set_option(asio::serial_port_base::parity(asio::serial_port_base::parity::none)); // 设置校验位
        serial_port_.set_option(asio::serial_port_base::stop_bits(asio::serial_port_base::stop_bits::one)); // 设置停止位
        serial_port_.set_option(asio::serial_port_base::flow_control(asio::serial_port_base::flow_control::none)); // 设置流控制
        is_open_ = true;
        return true;
    }
    catch (const std::exception& e)
    {
        std::cerr << "Error opening serial port: " << e.what() << std::endl;
        is_open_ = false;
        return false;
    }
}

void SerialPort::close()
{
    if (serial_port_.is_open())
    {
        asio::error_code ec;
        serial_port_.close(ec);
        if (ec)
        {
            std::cerr << "Error closing serial port: " << ec.message() << std::endl;
        }
        is_open_ = false;
    }
}

void SerialPort::async_read(std::vector<unsigned char>& buffer, std::function<void(const asio::error_code&, size_t)> callback)
{
  serial_port_.async_read_some(asio::buffer(buffer), callback);
}

void SerialPort::async_write(const std::vector<unsigned char>& data, std::function<void(const asio::error_code&, size_t)> callback)
{
    asio::async_write(serial_port_, asio::buffer(data), callback);
}
// Example Usage in MFC
asio::io_context io_context;
SerialPort serial_port(io_context, "COM1"); // 替换为你的串口名称

if (serial_port.open()) {
    std::vector<unsigned char> data_to_send = {0x01, 0x02, 0x03}; // 要发送的数据
    serial_port.async_write(data_to_send, [](const asio::error_code& error, size_t bytes_transferred) {
        if (!error) {
            std::cout << "Sent " << bytes_transferred << " bytes" << std::endl;
        } else {
            std::cerr << "Error sending data: " << error.message() << std::endl;
        }
    });

    std::vector<unsigned char> read_buffer(256);
    serial_port.async_read(read_buffer, [&read_buffer](const asio::error_code& error, size_t bytes_transferred) {
        if (!error) {
            std::cout << "Received " << bytes_transferred << " bytes: ";
            for (size_t i = 0; i < bytes_transferred; ++i) {
                std::cout << std::hex << (int)read_buffer[i] << " ";
            }
            std::cout << std::endl;
            // 再次启动异步读取,确保持续接收数据
            //serial_port.async_read(read_buffer, /* ... */);
        } else {
            std::cerr << "Error receiving data: " << error.message() << std::endl;
        }
    });

    std::thread t([&io_context](){ io_context.run(); }); // 启动 I/O 线程
    // 确保 io_context.run() 在单独的线程中运行,避免阻塞 MFC 主线程

    // 可以在 MFC 消息循环中调用 io_context.post() 来执行一些需要在 I/O 线程中执行的任务

    t.detach(); // 让线程独立运行
}

实现串口重连机制

为了应对串口设备断开连接的情况,我们需要实现串口重连机制。一种简单的做法是在异步读写的回调函数中,当检测到错误时,尝试重新打开串口。为了避免频繁重连,可以设置一个重连间隔时间。

// 在 SerialPort 类中添加重连函数
void SerialPort::reconnect()
{
    close();
    asio::steady_timer timer(io_context_, std::chrono::seconds(5)); // 设置重连间隔为 5 秒
    timer.async_wait([this](const asio::error_code& error) {
        if (!error)
        {
            if (open())
            {
                std::cout << "Serial port reconnected successfully." << std::endl;
                // 重新启动异步读取
                //do_read();
            }
            else
            {
                std::cerr << "Failed to reconnect serial port." << std::endl;
                reconnect(); // 再次尝试重连
            }
        }
    });
}

// 在异步读写的回调函数中调用 reconnect 函数
serial_port.async_read(read_buffer, [this, &read_buffer](const asio::error_code& error, size_t bytes_transferred) {
    if (!error) {
        // 处理接收到的数据
    } else {
        std::cerr << "Error receiving data: " << error.message() << std::endl;
        reconnect(); // 尝试重连
    }
});

实战避坑经验总结

  • 串口名称大小写敏感:在 Windows 平台上,串口名称通常以 "COM" 开头,例如 "COM1"、"COM2" 等。需要注意的是,串口名称是大小写敏感的,因此需要确保串口名称的大小写与实际设备一致。
  • 线程同步问题:由于 MFC 的界面操作需要在主线程中进行,而 Boost.Asio 的 I/O 操作可以在独立的线程中进行,因此需要注意线程同步问题。可以使用 PostMessage 函数将数据或事件从 I/O 线程发送到 MFC 主线程,或者使用 std::mutex 等同步原语来保护共享资源。
  • 异常处理:在使用 Boost.Asio 进行串口通信时,需要注意异常处理。可以使用 try-catch 块来捕获异常,并进行相应的处理。例如,当串口无法打开时,可以尝试重新打开串口。
  • 缓冲区大小async_read_some不会保证读到期望大小的数据,需要循环读取或设置超时机制。
  • 资源释放: 程序退出时,务必确保 io_context.stop() 被调用,否则可能会导致程序崩溃。

通过以上步骤,我们可以使用 C++ MFC 结合 Boost.Asio 库实现一个稳定可靠的串口功能。在实际应用中,可以根据具体需求进行定制和优化,例如添加数据校验、流量控制等功能。同时,需要注意线程同步、异常处理等问题,以保证程序的稳定性和可靠性。实际开发中,可以使用类似 Nginx 的事件驱动模型,保证高并发下的稳定连接,也可以使用宝塔面板方便管理服务器,但桌面软件的并发量级远小于服务器,所以通常不需要考虑这些。

C++ MFC 结合 Boost.Asio 实现高可靠串口通信:异步收发、重连机制全解析

转载请注明出处: 代码一只喵

本文的链接地址: http://m.acea4.store/blog/648381.SHTML

本文最后 发布于2026-04-08 07:52:58,已经过了19天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • 老王隔壁 4 天前
    学习了,最近正好在做一个类似的上位机项目,正愁怎么用 Boost.Asio 实现串口通信呢。
  • 选择困难症 6 天前
    异步读取那块,感觉还是不太理解,回调函数执行完后,怎么保证能持续接收数据呢?需要在回调函数里再次调用 async_read 吗?
  • 欧皇附体 6 天前
    异步读取那块,感觉还是不太理解,回调函数执行完后,怎么保证能持续接收数据呢?需要在回调函数里再次调用 async_read 吗?
  • 兰州拉面 6 天前
    重连机制那块,如果串口设备是热插拔的,怎么才能更及时地检测到设备断开并重连呢?轮询检测设备是否存在会不会更好?
  • 肝帝 2 天前
    感谢分享,文章写的很详细,对MFC+Boost.Asio串口通信的流程有了更清晰的理解。