c++streamistreamistream-iterator

Using stream to treat received data


I am receiving messages from a socket. The socket is packed within a header (that is basically the size of the message) and a footer that is a crc (a kind of code to check if the message is not corrupted)

So, the layout is something like :

size (2 bytes) | message (240 bytes) | crc (4 byte)

I wrote a operator>>

The operator>> is as following :

std::istream &operator>>(std::istream &stream, Message &msg) {
    std::int16_t size;
    stream >> size;
    stream.read(reinterpret_cast<char*>(&msg), size);

    // Not receive enough data
    if (stream.rdbuf()->in_avail() < dataSize + 4) {
        stream.setstate(std::ios_base::failbit);
        return stream;
    }

    std::int16_t gotCrc;
    stream >> gotCrc;

    // Data not received correctly
    if(gotCrc != computeCrc(msg)) {
        stream.setstate(std::ios_base::failbit);
    }
    return stream;
}

The message can arrive byte by byte, or can arrive totally. We can even receive several messages in once.

Basically, what I did is something like this :

struct MessageReceiver {
    std::string totalDataReceived;
    void messageArrived(std::string data) {
        // We add the data to totaldataReceived
        totalDataReceived += data;

        std::stringbuf buf(totalDataReceived);
        std::istream stream(&buf);
        std::vector<Message> messages(
            std::istream_iterator<Message>(stream), 
            std::istream_iterator<Message>{});
        std::for_each(begin(messages), end(messages), processMessage);
        // +4 for crc and + 2 for the size to remove
        auto sizeToRemove = [](auto init, auto message) {return init + message.size + 4 + 2;};
        // remove the proceed messages
        totalDataReceived.remove(accumulate(begin(messages), end(messages), 0, sizeToRemove);
    }
};

So basically, we receive data, we insert it into a total array of data received. We stream it, and if we got at least one message, we remove it from the buffer totalDataReceived.

However, I am not sure it is the good way to go. Indeed, this code does not work when a compute a bad crc... (The message is not created, so we don't iterate over it). So each time, I am going to try to read the message with a bad crc...

How can I do this? I can not keep all the data in totalDataReceived because I can receive a lot of messages during the execution life time.

Should I implement my own streambuf?


Solution

  • I found what you want to create is a class which acts like a std::istream. Of course you can choose to create your own class, but I prefer to implement std::streambuf for some reasons.

    First, people using your class are accustomed to using it since it acts the same as std::istream if you inherit and implement std::streambuf and std::istream.

    Second, you don't need to create extra method or don't need to override operators. They're already ready in std::istream's class level.

    What you have to do to implement std::streambuf is to inherit it, override underflow() and setting get pointers using setg().