子类化QIODevice:QUdpSocket的包装器

时间:2017-09-18 14:25:46

标签: qt qdatastream qiodevice

我正在尝试在QUdpSocket上实现我自己的包装器,因为它是uncomfortable to use。我可以使用它,但无论如何我需要实现一些中间缓冲区来访问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);
    }
}

来自QIODevice::readData()

上的Qt文件
  

..重新实现此功能时,此功能非常重要   在返回之前读取所有必需的数据。这是必需的   命令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派生类。

1 个答案:

答案 0 :(得分:0)

在使用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();
    }
}