Подкласс QIODevice: оболочка для QUdpSocket

Я пытаюсь реализовать свою собственную оболочку над QUdpSocket, поскольку она неудобен в использовании. Я могу использовать его, но в любом случае мне нужно реализовать некоторый промежуточный буфер для доступа к QDataStream операции. В дополнение:

Я подкласс QIODevice,
заголовок (немного упрощенно):

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:

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);
    }
}

Из документации Qt на QIODevice::readData()

..При переопределении этой функции важно, чтобы эта функция считывала все необходимые данные перед возвратом. Это необходимо для того, чтобы QDataStream мог работать с классом. QDataStream предполагает, что вся запрошенная информация была прочитана, и поэтому не повторяет попытку чтения, если возникла проблема...:

// 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;
}

Проблема в том, что после первого чтения у меня пустой буфер, и поэтому мой bytesAvailable( ) возвращает 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
}

Поэтому я не могу узнать, сколько байтов доступно при использовании класса, например:

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
{
    //...
}

Как правильно написать собственную оболочку QUdpSocket?
Идея использования промежуточного буфера работала хорошо, пока я не решил вынести логику в отдельный класс, производный от QIODevice.


person Vladimir Bershov    schedule 18.09.2017    source источник


Ответы (1)


В процессе отладки с исходниками Qt выяснилось, что я должен установить свойство isSequential() в true. Теперь мой класс работает правильно.

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

Полный класс:

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();
    }
}
person Vladimir Bershov    schedule 18.09.2017