如何通过 QTcpSocket 接收结构?

how to receive structure via QTcpSocket?

我想使用 QTCPSocket 发送和接收结构。

该系统确实有一个中央套接字服务器和许多 Qt 客户端应用程序。 每个客户端发送一个结构,由中央套接字服务器广播给其余的。

Connector 扩展 QTcpSocket

这是我的连接方式

void Connector::Open(QString address, int port) // It works!
{
    connectToHost(address, port);
    this->onWaiting();

    connect(this, SIGNAL(connected()), this, SLOT(connected()));
    connect(this, SIGNAL(disconnected()), this, SLOT(disconnected()));
    connect(this, SIGNAL(readyRead()), this, SLOT(readyRead()));
    connect(this, SIGNAL(bytesWritten(qint64)), this, SLOT(bytesWritten(qint64)));
}

这是我发送数据的方法

void Connector::SendData(const void* data,qint64 len) // It works!
{
    writeData((char*)data,len);
    waitForBytesWritten();
}

这是为了接收

void Connector::readyRead()  // Don't work
{
    Information n; //My struct 
    memcpy(&n, data.data(), data.size());
    onInformationReceived(n);
}

但是收到的信息总是无效的。那是对的吗?

存在一些问题:

  1. readyRead() 插槽可以用任意数量的可读字节调用。唯一的保证是它大于零字节,尽管在实践中没有理由指望它。您必须检查 bytesAvailable。它可以是一个字节。可以是兆字节。

    参见例如this answer and for narrative, and 代码。

    至少,您需要:

    void Connector::readyRead() {
      if (bytesAvailable() < sizeof(Information)) return;
      Information info;
      auto const data = read(sizeof(Information));
      memcpy(&info, data.constData(), data.size());
      onInformationReceived(info);
    }
    
  2. 通过网络将 struct 作为不透明二进制文件发送将不起作用。无法保证接收端会以相同的方式布置结构。你甚至不知道数据的二进制格式是什么,除非你参考你的编译器 ABI 文档。

    参见例如this answer 了解如何正确执行此操作。

    至少,您需要为 Information 实现 QDataStream 流操作符,然后在连接的两端使用它:

    QDataStream & operator<<(QDataStream &, const Information &);
    QDataStream & operator>>(QDataStream &, Information &);
    
    void Connector::send(const Information & info) {
      auto dev = this; // We're a QIODevice :(
      QDataStream stream{dev};
      stream << info;
      dev.waitForBytesWritten(); // this is superbly bad!
    }
    
    void Connector::readyRead() {
      auto dev = this; // We're a QIODevice :(
      QDataStream stream{dev};
      stream.startTransaction();
      Information info;
      dev >> info;
      if (! stream.commitTransaction()) return;
      onInformationReceived(info);
    }
    
  3. 你不应该扩展 QTcpSocket。您的 class 应该是一个 QObject,按值包含 QTcpSocket

  4. 你永远不应该阻止。

因此,修复后的 Connector class 可能如下所示。同样,this answer 描述了如何正确使用 QDataStream 来考虑与未来代码更新的兼容性。但也请参阅 这个答案 了解如何利用 Qt 5.7 中的读取事务 QDataStream

class Connector : public QObject {
  Q_OBJECT
  QTcpDevice m_dev;
  QDataStream m_str{&m_dev};
  void onReadyRead() {
    m_str.startTransaction();
    Information info;
    m_str >> info;
    if (! stream.commitTransaction()) return;
    onInformationReceived(info);
  }
  void onBytesWritten() {
    if (m_dev.bytesToWrite() == 0)
      emit allDataSent();
  }
  void onInformationReceived(const Information &);
public:
  Connector(const QString & address, int port, QObject * parent = nullptr) :
    QObject{parent}
  {
    connect(&m_dev, &QAbstractSocket::connected, this, &Connector::connected);
    connect(&m_dev, &QAbstractSocket::disconnected, this, &Connector::disconnected);
    connect(&m_dev, &QIODevice::readyRead, this, onReadyRead);
    connect(&m_dev, &QIODevice::bytesWritten, this, onBytesWritten);
    m_dev.connect(address, port);
  }
  void send(const Information & info) {
    m_str << info;
  }
  Q_SIGNAL void connected();
  Q_SIGNAL void disconnected();
  Q_SIGNAL void allDataSent();
}