串口上的Boost.Asio async_read意外完成?

时间:2014-10-08 23:24:21

标签: c++ boost boost-asio

我正在使用Boost.Asio通过RS232与设备通信。我使用boost::asio::write()发送命令,然后等待boost::asio::async_read()boost::asio::transfer_at_least(6)完成条件的答案。有时,读取操作会成功完成,并且会读取6个字节的数据。但是,还有一些情况下,读取操作会在成功时立即意外完成,并传输0个字节。

我的理解是完成条件transfer_at_least应该阻止读取操作完成,直到读取6个字节为止。它显然不......为什么?我的临时解决方案是,如果没有传输字节,则从串口再次读取。对我来说这似乎是一个讨厌的解决方案。

我正在使用集成在类中的以下函数:

void SerialPort::do_read()
{
  printf("reading feedback on the serial port \n");
  boost::asio::async_read(port,boost::asio::buffer(&buffer_read,6), boost::asio::transfer_at_least(6), boost::bind(&SerialPort::read_callback, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));

  timeout_timer.expires_from_now(boost::posix_time::milliseconds(SERIAL_TIMEOUT_MS));
  timeout_timer.async_wait(boost::bind(&SerialPort::wait_callback, this, boost::asio::placeholders::error));

  printf("start event loop\n");
  io_service.run();
  printf("close event loop \n..................\n");
  ready_to_write = true;
}

void SerialPort::read_callback(const boost::system::error_code& error, std::size_t bytes_transferred)
{  
  //*** DEBUG
  printf("Function read_callback\n");
  if(!error) 
    cout << "Error Code: " << error.message() <<endl;

  cout << "Bytes transferred: " << bytes_transferred << endl;
  //***

  if(!error && bytes_transferred == 0)
  {
    //boost::asio::async_read(port,boost::asio::buffer(&buffer_read,6), boost::asio::transfer_at_least(6), boost::bind(&SerialPort::read_callback, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
    printf("Serial Link : No data to read.\n");
    return;
  }

  if(error)
  {
    printf("Serial Link : Error when reading data.\n");
    return;
  }

  printf("Going to cancel the timer\n");
  timeout_timer.cancel();  // will cause wait_callback to fire with an error boost::asio::error::operation_aborted
  printf("%u %u %u %u %u %u\n", buffer_read[0], buffer_read[1], buffer_read[2], buffer_read[3], buffer_read[4], buffer_read[5]);
  return;
}

void SerialPort::wait_callback(const boost::system::error_code& error) //Called when the timer's deadline expire
{
  if (error)
  {
    printf("Timeout cancelled.\n");
    return;
  }

  printf("Timeout fired.\n");
  timer_has_fired = true;
  printf("Going to cancel the serial port \n");
  port.cancel();  // Close all asynchronous operation with serial port. will cause read_callback to fire with an error
  printf("All operations on serial port cancelled.\n");
  return;
}

buffer_read是该类的成员,在我的头文件中创建:

unsigned char buffer_read[6];

启用处理程序跟踪并且程序运行正常时,我得到以下输出:

@asio|1412962730.893007|0*1|handle@000000000027EC60.async_read_some  
@asio|1412962730.905008|0*2|deadline_timer@000000000027EC60.async_wait  
@asio|1412962730.927009|>1|ec=system:0,bytes_transferred=6  
@asio|1412962730.934009|1|deadline_timer@000000000027EC60.cancel  
@asio|1412962730.941010|<1|  
@asio|1412962730.944010|>2|ec=system:995  
@asio|1412962730.948010|<2|

当它工作不正常时,我得到这个输出:

@asio|1412963195.973608|0*3|handle@000000000027EC60.async_read_some  
@asio|1412963195.973608|0*4|deadline_timer@000000000027EC60.async_wait  
@asio|1412963195.976608|>3|ec=system:0,bytes_transferred=0  
@asio|1412963195.981608|<3|  
@asio|1412963196.973665|>4|ec=system:0  
@asio|1412963196.976665|4|handle@000000000027EC60.cancel  
@asio|1412963196.977665|<4|

我的打印语句结果为:

Reading feedback on the serial port
start event loop
Function read_callback
Error Code: The operation completed successfully
Bytes transferred: 0
Serial Link : No data to read
Timeout fired.
Going to cancel the serial port
All operations on serial port cancelled
close event loop

我能够使用以下代码重现针对x64平台编译的问题。串行通道上没有数据(设备不发送任何内容,因此async_read功能不应调用read_callback功能。)

#define BOOST_ASIO_ENABLE_HANDLER_TRACKING

#include <boost\asio.hpp>
#include <boost\bind.hpp>
#include <boost\thread.hpp>
#include <stdexcept>
#include <iostream>
#include <string>
using namespace std;

class SerialPort
{
public:
  SerialPort(boost::asio::io_service& io_service, unsigned int baud, const string& peripheral) : 
    io_service(io_service), port(io_service, peripheral)
  {
    if(!port.is_open())
    {
      throw std::runtime_error("The serial port is already open.");
      return;
    }

    port.set_option(boost::asio::serial_port_base::baud_rate(baud));
    cout << ".. Serial Port " << peripheral << " opened" << endl;

    do_read(); 
  }

  void close()
  {
    do_close(boost::system::error_code());
  }

private:
  boost::asio::io_service& io_service;
  boost::asio::serial_port port;
  unsigned char buffer_read[6];

  void do_read()
  {
    boost::asio::async_read(port,boost::asio::buffer(buffer_read, 6), boost::asio::transfer_at_least(6), boost::bind(&SerialPort::read_callback, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
  }

  void read_callback(const boost::system::error_code& error, std::size_t bytes_transferred)
  {
    if(error)
    {
      return;
    }
    if(!error && bytes_transferred == 0)
    {
      cout << "Unexpected behaviour: no error but 0 byte transferred. \n" << endl;
      return;
    }
    do_read();
  }

  void do_close(const boost::system::error_code& error)
  {
    if(error)
      cerr << "Error detected: " << error.message() << endl;
    port.close();
    cout << ".. Serial Port closed" << endl;
    return;
  }
};


int main(int argc, char* argv[])
{
  try
  {
    boost::asio::io_service io_service;

    SerialPort motor(io_service, 9600, "COM5");

    boost::thread thread_motor(boost::bind(&boost::asio::io_service::run, &io_service));    

    system("pause");
    motor.close();
    thread_motor.join();

  }
  catch (std::exception& e)
  {
    std::cerr << "Exception caught: " << e.what() << '\n';
  }

  system("pause");
  return 0;
}

修改 我花了一些时间来改进我的代码。我用以下代码重现了这个问题。我正在使用Boost 1.56.0并编译为x64平台(Windows 7)。我删除了系统(“暂停”),我试图找到一种更好的方法来使用io_service。

对于串口,我曾经有一个特定的串口设备。为了便于重现,我编写了一个非常简单的代码(在Arduino Due上运行)。

有趣的是,async_read的第一次调用以我正在谈论的奇怪行为结束,第二次调用等待数据到达(所以没关系!)。

请随意评论我的代码:

#define BOOST_ASIO_ENABLE_HANDLER_TRACKING

#define SERIAL_TIMEOUT_MS 10000

#include <boost\asio.hpp>
#include <boost\bind.hpp>
#include <boost\thread.hpp>
#include <boost/noncopyable.hpp>
#include <stdexcept>
#include <iostream>
#include <string>
using namespace std;

class SerialPort : private boost::noncopyable
{
public:
    SerialPort(boost::asio::io_service& io_service, unsigned int baud, const string& peripheral) : 
        io_service(io_service), port(io_service, peripheral), timeout_timer(io_service)
    {
            if(!port.is_open())
            {

                throw std::runtime_error("The serial port is already open.");
                return;
            }

            port.set_option(boost::asio::serial_port_base::baud_rate(baud));
            cout << "Serial Port " << peripheral << " opened." << endl;
    }

    void close()
    {
        io_service.post(boost::bind(&SerialPort::do_close, this, boost::system::error_code()));
    }

    void do_read()
    {
        cout << "..Do_read called." << endl;
        boost::asio::async_read(port,boost::asio::buffer(buffer_read, 6), boost::asio::transfer_at_least(6), boost::bind(&SerialPort::read_callback, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));

        timeout_timer.expires_from_now(boost::posix_time::milliseconds(SERIAL_TIMEOUT_MS));
        timeout_timer.async_wait(boost::bind(&SerialPort::wait_callback, this, boost::asio::placeholders::error));  
    }

private:
    boost::asio::io_service& io_service;
    boost::asio::serial_port port;
    boost::asio::deadline_timer timeout_timer;
    unsigned char buffer_read[6];

    void read_callback(const boost::system::error_code& error, std::size_t bytes_transferred)
    {
        cout << "..Read_callback called." << endl;
        if(error)
        {
            cerr << "Read_callback called with an error." << endl;
            do_close(error);
            return;
        }
        if(!error && bytes_transferred == 0)
        {
            cerr << ".. ..Unexpected behaviour: no error but 0 byte transferred. \n" << endl;
            //timeout_timer.cancel(); // THIS LINE SHOULD BE INSTEAD OF THE NEXT ONE
            do_read(); // NASTY SOLUTION TO GET IT WORKING
            return;
        }

        cout << ".. ..Bytes transferred: " << bytes_transferred << " bytes." << endl;
        cout << ".. ..Message: " << buffer_read[0] << buffer_read[1] << buffer_read[2] << buffer_read[3] << buffer_read[4] << buffer_read[5] << endl;
        timeout_timer.cancel(); 
        do_read();
    }

    void wait_callback(const boost::system::error_code& error) //Called when the timer's deadline expire
    {
        cout << "..Wait_callback called." << endl;
        if (error)
        {
            cout << ".. ..Timeout cancelled." << endl;
            if(error != boost::asio::error::operation_aborted) // if timer fired
                do_close(error); // close the serial port only if error is not "operation_aborted"
            return;
        }

        cout << ".. ..Timeout fired." << endl;
        port.cancel();  // Close all asynchronous operation with serial port. will cause read_callback to fire with an error
                        // Does io_service keep running ?
        cout << ".. ..All operations on serial port cancelled.\n" <<endl;
        return;
    }

    void do_close(const boost::system::error_code& error)
    {
        cout << "..Do_close called" << endl;
        if(error)
            cerr << "Error detected: " << error.message() << endl;
        if(error == boost::asio::error::operation_aborted)
            return;
        timeout_timer.cancel(); 
        port.close();
        cout << "Serial Port closed" << endl;
        return;
    }
};

int main(int argc, char* argv[])
{
    try
    {
        boost::asio::io_service io_service;
        auto_ptr<boost::asio::io_service::work> work(new boost::asio::io_service::work(io_service)); 
            // to prevent io_service to stop if no work left

        SerialPort motor(io_service, 9600, "COM6");
        boost::thread thread_motor(boost::bind(&boost::asio::io_service::run, &io_service));        

        motor.do_read(); 

        cin.get();

        motor.close(); // close the serial port
        work.reset(); // wait for handlers to finish normally. io_service stops when no more work to do.
        thread_motor.join(); // delete the current thread

    }
    catch (std::exception& e)
    {
        std::cerr << "Exception caught: " << e.what() << '\n';
    }

    cout <<"Press any key to exit..."<<endl;
    cin.get();
    return 0;
}

Arduino代码:

void setup() 
{
    Serial.begin(9600);
}

void loop()
{
    delay(5000);
    Serial.write("azerty");
}

非常感谢你告诉我我做错了什么!

亚历

0 个答案:

没有答案