Qt中的readyRead()是如何工作的?

4

这是我在这个网站上的第一个问题!

我在读取COM端口的数据时遇到了一些问题,我从另一个COM端口发送了完整的消息,但当我用Qt接收它时,它总是被分成多个子消息。

void SerialPortReader::init()
{
    connect(m_serialPort, SIGNAL(readyRead()), this, SLOT(readData()));
}   

void SerialPortReader::readData()
{
//    m_serialPort->waitForReadyRead(200);
    QByteArray byteArray = m_serialPort->readAll();
    qDebug() << byteArray;

    if(byteArray.startsWith(SOF) && byteArray.endsWith(EOF_LS)
        && byteArray.size() >= MIN_SIZE_DATA) {
    decodeData(byteArray.constData());
    } else {
        qDebug() << "LIB SWCom : Unvalid trame !";
    }
}

发送的消息长度为25或27字节,如果我使用Putty或Hyperterminal来读取它们,就没有问题。而且如果我使用两个模拟串行端口进行通信,也没有这个问题...只有在Qt读取系统和两个物理COM端口时才会出现这个问题...
我不确定readyRead信号何时被发出...我很困惑,感谢您的帮助!

你是在询问我们如何在QtSerialPort中具体实现这个功能,还是只是一般的API概念? - László Papp
是的,它帮助了很多,谢谢。 - palador
3个回答

11

文档上已经解释得很清楚了:

void QIODevice::readyRead() [信号]

每当从设备中有新数据可供读取时,都会发出此信号。仅在再次有新数据可用时才会重新发出信号,例如当网络套接字上到达新的负载或者将新的数据块附加到设备时。

readyRead() 不是递归发出的;如果您在连接到 readyRead() 信号的槽函数内重新进入事件循环或调用 waitForReadyRead(),则不会重新发出信号(但 waitForReadyRead() 可能仍会返回 true)。

对于实现自 QIODevice 派生类的开发人员,请注意:只有在有新数据到达时才发出 readyRead()(不要仅因为缓冲区中仍有数据可读而发出它)。不要在其他情况下发出 readyRead()。

这意味着不能保证有多少数据可供读取,只是有一些可用。

如果你想读取比一次接收更多的数据,可能需要使用超时值和/或 readyRead。这取决于你想要实现的目标。

此外,您还可以查看我之前编写的一个命令行异步读取器示例来了解此操作。

#include "serialportreader.h"

#include <QCoreApplication>

QT_USE_NAMESPACE

SerialPortReader::SerialPortReader(QSerialPort *serialPort, QObject *parent)
    : QObject(parent)
    , m_serialPort(serialPort)
    , m_standardOutput(stdout)
{
    connect(m_serialPort, SIGNAL(readyRead()), SLOT(handleReadyRead()));
    connect(m_serialPort, SIGNAL(error(QSerialPort::SerialPortError)), SLOT(handleError(QSerialPort::SerialPortError)));
    connect(&m_timer, SIGNAL(timeout()), SLOT(handleTimeout()));

    m_timer.start(5000);
}

SerialPortReader::~SerialPortReader()
{
}

void SerialPortReader::handleReadyRead()
{
    m_readData.append(m_serialPort->readAll());

    if (!m_timer.isActive())
        m_timer.start(5000);
}

void SerialPortReader::handleTimeout()
{
    if (m_readData.isEmpty()) {
        m_standardOutput << QObject::tr("No data was currently available for reading from port %1").arg(m_serialPort->portName()) << endl;
    } else {
        m_standardOutput << QObject::tr("Data successfully received from port %1").arg(m_serialPort->portName()) << endl;
        m_standardOutput << m_readData << endl;
    }

    QCoreApplication::quit();
}

void SerialPortReader::handleError(QSerialPort::SerialPortError serialPortError)
{
    if (serialPortError == QSerialPort::ReadError) {
        m_standardOutput << QObject::tr("An I/O error occurred while reading the data from port %1, error: %2").arg(m_serialPort->portName()).arg(m_serialPort->errorString()) << endl;
        QCoreApplication::exit(1);
    }
}

在这种情况下,命令行读取器示例会一次性获取传递的任何数据,但它不能保证长度或其他任何内容。

另外,请注意,在您评论后面的同步API与您所询问的异步API不太相关。我指的是 m_serialPort->waitForReadyRead(200);


谢谢您的回答,我忘了说数据是连续发射的(一次性),在最终情况下每20毫秒发射一次。我知道waitForReadyRead()会阻止信号readyRead被重新发射,所以我在读取数据之前尝试了waitForReadyRead(20),并且它运行良好。但在这种情况下,我需要确保发送方每20毫秒发射一次而不是更多。 - palador
@palador:根据我的回答,你可以使用一个计时器来实现。我的示例也展示了如何使用计时器。 - László Papp
@palador:问题解决后请提供一些反馈。 - László Papp
@palador:请不要将澄清作为答案提交。请更新问题。虽然你应该一开始就表述清楚。 - László Papp
@palador:现在解决了吗?如果是这样,请选择一个答案 - László Papp
显示剩余2条评论

1
使用线程概念,在这里可以使用线程来实现...在单独的线程中运行您的端口读取,那么它将正常工作,否则消息将被截断。 其他方法是使用QProcess并使用QProcess打开一个新终端,并将端口号和连接详细信息发送到该终端,然后从Qt运行它,也可以实现这一目标。
我的意思是我们可以在这里使用线程和套接字通信,请查看此代码。
    QTcpSocket *SocketTest::getSocket() {
        return socket;
    }

    void SocketTest::Connect()

    {

        socket = new QTcpSocket(this);
        socket->connectToHost("127.0.0.1",22);
        if(socket->waitForConnected(3000))
        {

            qDebug()<<"connect";
            socket->waitForBytesWritten(3000);
            socket->waitForReadyRead(1000);
         Settings::sockdata=socket->readAll();
           qDebug()<<Settingssock::sockets;
           socket->close();

        }
        else
        {
             qDebug()<<" not connect";

        }
    }


............ call socket 

 SocketTest sock;
    sock.Connect();
    QTcpSocket * socket = sock.getSocket();
       QObject *ob= new QThread;
       mythread thread1(socket);
    thread = new mythread(socket);
    thread.run();
    thread->start();
   thread->wait();

这是一个telnet连接的简单示例,同样您可以指定要连接的端口和连接到哪个ip。

我对解析这个答案感到困惑,因为原帖没有提及协议,所以我不确定你在谈论哪个端口。此外,我不明白QProcess/QThread在这种情况下的相关性是什么?还有,“从Qt运行它”是什么意思?“你可以实现这个”->实现什么? - László Papp

1

每当有待处理数据且上一次的readyRead执行已经完成时,就会发出readyRead信号。

文档说明如下:

每当设备上有新数据可供读取时,都会发出此信号。只有在有新数据可用时才会再次发出,例如当网络套接字上到达新的网络数据有效负载或将新数据块附加到设备时。

readyRead()不会递归发出; 如果您在连接到readyRead()信号的插槽中重新进入事件循环或调用waitForReadyRead(),则不会重新发出该信号(尽管waitForReadyRead()仍可能返回true)。

如果这在您的情况下会导致任何问题,您可以使用while循环来检查bytesAvailable()。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接