Subclassing QIODevice: Wrapper for QUdpSocket

430 views Asked by At

I am trying to implement my own wrapper over QUdpSocket because of it is uncomfortable to use. I can use it, but anyway I need to implement some intermediate buffer for access to QDataStream operations. In additional:

I sublass QIODevice,
header (bit simplified):

class BerUdp : public QIODevice
{
    Q_OBJECT

    void startup();

public:
    void setHostToWrite(const QHostAddress &address, quint16 port);
    void setPortToRead(quint16 port);

    qint64 bytesAvailable() const;

protected: // Reimplement:
    qint64 readData(char *data, qint64 maxSize);
    qint64 writeData(const char *data, qint64 maxSize);

private:
    QUdpSocket* dev_write; // udp socket to write
    QUdpSocket* dev_read;  // udp socket to read

    QBuffer     m_buffer;  // buffer to store received datagrams  
};

.cpp file:

void BerUdp::startup()
{    
    m_buffer.open(QIODevice::ReadWrite);    
    open(QIODevice::ReadWrite);
}

void BerUdp::setHostToWrite(const QHostAddress &address, quint16 port)
{
    dev_write->connectToHost(address, port);
    dev_write->waitForConnected();
}

void BerUdp::setPortToRead(quint16 port)
{
    dev_read->bind(port);
    dev_read->open(QIODevice::ReadOnly);

    bool ok = connect(dev_read, &QIODevice::readyRead,
        this, &BerUdp::onReceive);
}       

// Read new received data to my internal buffer
void BerUdp::onReceive()
{
    bool av = dev_read->hasPendingDatagrams();
    if (av)
    {
        int dSize = dev_read->pendingDatagramSize();
        QByteArray dtg(dSize, 0);
        dev_read->readDatagram(dtg.data(), dtg.size());    

        // write new data to the end
        int buf_read_pos = m_buffer.pos();
        m_buffer.seek(m_buffer.size());
        m_buffer.write(dtg);
        m_buffer.seek(buf_read_pos);
    }
}

From the Qt documuntation on QIODevice::readData()

..When reimplementing this function it is important that this function reads all the required data before returning. This is required in order for QDataStream to be able to operate on the class. QDataStream assumes all the requested information was read and therefore does not retry reading if there was a problem...:

// Main read data function. There are only 4 bytes required, but maxSize == 0x4000 here:
qint64 BerUdp::readData(char *data, qint64 maxSize)
{    
    int n = m_buffer.read(data, maxSize);

    // clear the data which has already read:
    QByteArray& ba = m_buffer.buffer();
    ba.remove(0, n); // m_buffer.size() == 0 after this
    m_buffer.seek(0);

    return n;
}

The problem is that after the first read I have empty buffer, and therefore my bytesAvailable() method returns 0:

qint64 BerUdp::bytesAvailable() const
{       
    qint64 my_size = m_buffer.size();
    qint64 builtin_size = QIODevice::bytesAvailable();

    return (my_size + builtin_size); // == 0 after 1st read
}

So I can not find out how many bytes are available, when use the class, for example:

BerUdp udp;
QDataStream stream;
stream.setDevice(&udp);
...

QIODevice* dev = stream.device(); // 19 bytes available here
if (dev->bytesAvailable() > 0) // == true
{
    quint32 val;
    stream >> val;
}

if (dev->bytesAvailable() > 0) // == false
{
    //...
}

How can own wrapper of QUdpSocket be written correctly?
The idea of using intermediate buffer worked well, until I decided to move the logic to the separate QIODevice-derived class.

1

There are 1 answers

0
Vladimir Bershov On

In the process of debugging with the Qt sources, it was found out that I should set the isSequential() property to true. Now my class works correct.

bool BerUdp::isSequential() const
{
    return true;
}

Full class:

BerUdp.h

#pragma once

#include <QIODevice>
#include <QBuffer>

class QUdpSocket;
class QHostAddress;

class BerUdp : public QIODevice
{
    Q_OBJECT

public:
    BerUdp(QObject *parent = 0);
    void startup();    

    void setHostToWrite(const QHostAddress &address, quint16 port);
    void setPortToRead(quint16 port);

    bool flush();
    qint64 bytesAvailable() const;
    bool waitForReadyRead(int msecs);
    bool isSequential() const;

protected: // Main necessary reimplement
    qint64 readData(char *data, qint64 maxSize);
    qint64 writeData(const char *data, qint64 maxSize);

private slots:
    void onReceive();

private:
    void read_udp_datagram();
    void write_new_data_to_buffer(QByteArray dtg);

private:
    QUdpSocket* dev_write; // One udp socket to write
    QUdpSocket* dev_read;  // Another udp socket to read

    // intermediate buffer to store received datagrams
    // and to provide access to read- and QDataStream- operations
    QBuffer     m_buffer;  
};

BerUdp.cpp

#include "BerUdp.h"
#include <QUdpSocket>

BerUdp::BerUdp(QObject *parent)
    : QIODevice(parent)
{
    startup();
}

// Initialization
void BerUdp::startup()
{
    dev_write = new QUdpSocket(this);
    dev_read = new QUdpSocket(this);

    m_buffer.open(QIODevice::ReadWrite);
    open(QIODevice::ReadWrite);
}

// Set a virtual connection to "host"
void BerUdp::setHostToWrite(const QHostAddress &address, quint16 port)
{
    dev_write->connectToHost(address, port);
    dev_write->waitForConnected();
}

// Bind a port for receive datagrams
void BerUdp::setPortToRead(quint16 port)
{
    dev_read->bind(port);
    dev_read->open(QIODevice::ReadOnly);

    connect(dev_read, &QIODevice::readyRead,
        this, &QIODevice::readyRead);
    connect(dev_read, &QIODevice::readyRead,
        this, &BerUdp::onReceive);
}

// Flush written data
bool BerUdp::flush()
{
    return dev_write->flush();
}

// Returns the number of bytes that are available for reading.
// Subclasses that reimplement this function must call 
// the base implementation in order to include the size of the buffer of QIODevice.
qint64 BerUdp::bytesAvailable() const
{
    qint64 my_size = m_buffer.size();
    qint64 builtin_size = QIODevice::bytesAvailable();

    return (my_size + builtin_size);
}

bool BerUdp::waitForReadyRead(int msecs)
{
    return dev_read->waitForReadyRead(msecs);
}

// Socket device should give sequential access
bool BerUdp::isSequential() const
{
    return true;
}

// This function is called by QIODevice. 
// It is main function for provide access to read data from QIODevice-derived class.
// (Should be reimplemented when creating a subclass of QIODevice).
qint64 BerUdp::readData(char *data, qint64 maxSize)
{
    int n = m_buffer.read(data, maxSize);

    // clear the data which has already been read
    QByteArray& ba = m_buffer.buffer();
    ba.remove(0, n);
    m_buffer.seek(0);

    return n;
}

// This function is called by QIODevice.
// It is main function for provide access to write data to QIODevice-derived class.
// (Should be reimplemented when creating a subclass of QIODevice).
qint64 BerUdp::writeData(const char *data, qint64 maxSize)
{
    return dev_write->write(data, maxSize);
}

// Read new available datagram
void BerUdp::read_udp_datagram()
{
    int dSize = dev_read->pendingDatagramSize();
    QByteArray dtg(dSize, 0);
    dev_read->readDatagram(dtg.data(), dtg.size());

    write_new_data_to_buffer(dtg);
}

// Write received data to the end of internal intermediate buffer
void BerUdp::write_new_data_to_buffer(QByteArray dtg)
{
    int buf_read_pos = m_buffer.pos();
    m_buffer.seek(m_buffer.size());
    m_buffer.write(dtg);
    m_buffer.seek(buf_read_pos);
}

// Is called on readyRead signal
void BerUdp::onReceive()
{
    bool available = dev_read->hasPendingDatagrams();
    if (available)
    {
        read_udp_datagram();
    }
}