简体   繁体   中英

Why boost asio async_read_some does not invoke the callback in a certain situation?

I'm implementing a serial communication protocol to communicate with a external device over UART. I'm using boost asio for this. So far everything works fine, except very little cases. I found that it is possible that the reading from the socket does "miss" 1 byte in some cases.

I checked the serial communication by using a UART sniffer to see the raw data which comes and goes over the socket. This being sad, I did see the full frame of bytes being transmitted over the socket. But in my application the last byte was just not received.

I found that his byte is not "lost" it's somehow just "stuck" in the kernel buffer or something. Because on receiving the next frame I do get this "missing" byte in front of all the new bytes. This would not be a big problem if the frames would come in continuously. But the next frame is only sent by the device if the previous frame was acknowledged by the program.

Here is the code of my write and handle receive method:

void serial::write(std::vector<uint8_t> message) {
    static boost::mutex mu;
    mu.lock();
    uint8_t cmd = message.at(3);
    //if its an ACK frame - just send it
    if ((message[3]>>4)==0xE) {
        // --- Write message to serial port --- //
        boost::asio::write(serial_,boost::asio::buffer(message));
        usleep(20000);
        mu.unlock();
        return;
    }
    //if its not an ACK frame write the frame and wait for an ACK!
    //create new promise for the request
    promise = new boost::promise<deque<uint8_t>>;
    boost::unique_future<deque<uint8_t>> future = promise->get_future();
    // --- Write message to serial port --- //
    boost::asio::write(serial_,boost::asio::buffer(message));
    usleep(20000);
    mu.unlock();
    //wait for ACK or timeout
    if     (future.wait_for(boost::chrono::milliseconds(100))==boost::future_status::timeout) {
        spdlog::get("logger")->trace("ACK timeout!");
        //delete pointer and set it to 0
        delete promise;
        promise=nullptr;
        //need to resend this frame
    }
    //delete pointer and set it to 0 after getting a message
    delete promise;
    promise=nullptr;
}

handle receive:

void serial::handle_receive(const boost::system::error_code& error, size_t bytes_transferred) {
     static deque<uint8_t> read_buffer;
     static boost::posix_time::ptime start =    boost::posix_time::microsec_clock::local_time( );
    if (boost::posix_time::microsec_clock::local_time( ) - start > boost::posix_time::milliseconds(99)  && !read_buffer.empty()) {
        spdlog::get("logger")->trace("time condition, clearing buffer!");
        read_buffer.clear();
    }
    start = boost::posix_time::microsec_clock::local_time( );
    if (!error) {
        //push all the recieved data into the deque
        for (unsigned int i = 0; i < bytes_transferred; i++) {
            read_buffer.push_back((int)data_[i]);
        }
    }
    else if (error){
    spdlog::get("logger")->error("\n\n\n\nERROR: {0}\n\n\n\n", error);
    }
    while ( !read_buffer.empty()) {
        /*do here some checks if this could be a correct frame
       ...
       ...
       ...
       */
        //received frame is ready
        //check if it is an ACK frame
        //check if its a data frame and ACK it!
        //send an ACK if its a dataframe
        if (ACK)) {
            write(frame);
        }
        //give the data to the upper layer                  
        m_Receive_data_handler(receivedFrame,receivedFrame.size());
        }
    serial_.async_read_some(boost::asio::buffer(data_,max_length),
                        boost::bind(&serial::handle_receive, this,
                                    boost::asio::placeholders::error,
                                    boost::asio::placeholders::bytes_transferred));

}

Maybe somebody has an idea why it works on most cases, but fails just with the last byte of the frame?

Thanks!

You're using an asynchronous call to read data, and there's no guarantee that you'll read everything.

If you check the documentation: http://www.boost.org/doc/libs/1_58_0/doc/html/boost_asio/reference/basic_stream_socket/async_read_some.html

Remarks

The read operation may not read all of the requested number of bytes. Consider using the async_read function if you need to ensure that the requested amount of data is read before the asynchronous operation completes.

What happens probably is that in your case your kernel has little buffers, and it signals the application when it has some data, but you are trying to send more data than that. Once it is stuck with only little data left in the buffers, it'll wait for more data to come and save a call. These are just assumptions though, I don't have first hand experience with UART.

Would switching to a synchronous read call (read_some) be an option?

Could it be that, rather than stuck in a kernel buffer somewhere, the last byte is stuck in the UART's receive FIFO? Perhaps you need to configure the driver with an inter-character timeout?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM