通过QDataStream从客户端发送到服务器的字节之间不匹配

时间:2018-05-21 07:09:56

标签: c++ qt qtcpsocket

我正在QByteArray上发送QTcpSocket。我面临的问题是,虽然我发送了一个长度为25的数组,但在服务器上它会收到59个字节。这是我的示例代码:

//client
QDataStream out(qctcpSocket);
out<<qByteArray;  // qByteArray is of length 25
const int nbBytes = qctcpSocket->write(qByteArray); // nbBytes returbs 25 

//server
TArray<char> data;
uint32 pendingData = 0;
TArray<char> newData; // customized Template for Array
    newData.InsertZeroed(0, pendingData);
    int32 bytesRead = 0;
    rcvSocket->Recv(reinterpret_cast<uint8*>(newData.GetData()), pendingData, bytesRead);
    data += newData;//length is 59 !!

2 个答案:

答案 0 :(得分:1)

我在您的客户端代码中看到两个问题。由于您使用(据称)连接的套接字作为设备的数据流:

QDataStream out(qctcpSocket);

您通过套接字发送数据,使用以下行:

out<<qByteArray;  // qByteArray is of length 25

然后您将使用下一个发送更多数据:

const int nbBytes = qctcpSocket->write(qByteArray); // nbBytes returbs 25 

这是第一个问题:您尝试两次发送相同的数据。但是每次调用之间的数据不同(这是第二个问题):使用流执行第一次发送将在套接字上放置一个长度为QByteArray::size + 4的字节数组,由流对象添加四个额外字节(它是实际字节数组之前的大端32位整数,并以字节为单位保持其长度。)

相反,第二个发送只将字节数组中的25个字节放在套接字上,我想,这就是你想要的第一个。

我的建议是完全摆脱数据流并使用write类的QTcpSocket方法,如果你没有在另一个端点上使用Qt,这应该是最好的选择(服务器)。

如果您仍想使用该流,请删除发送方的write,并确保在接收端点上使用QDataStream对象,因此您必须:

//Build a QByteArray out of the incoming bytes:
QByteArray raw_data = ...

//Use it as the stream device
QDataStream stream(raw_data);

//Use another byte array to fetch the data out of the stream
QByteArray data;
stream >> data;

或者你可以手动剥离我不推荐的前四个字节。

答案 1 :(得分:1)

服务器似乎在虚幻引擎上运行。这很重要!

您需要两次发送数据:一次通过数据流,下次通过write。不要这样做:使用您首选的方法发送一次。

如果您想使用数据流:

void Client::send(const QByteArray &data) {
  QDataStream out(this->qctcpSocket);
  out << data;
}

有4 + data.size()个字节写入流。最初的4个字节带有后续数组的little-endian长度。如果您不想发送数组长度,则必须在套接字上的 writeRawData上使用write

void Client::send(const QByteArray &data) {
  // use when there's a data stream already on the socket
  this->out.writeRawData(data.constData(), data.size());
  // or
  // use when there's no data stream available
  this->qctcpSocket->write(data);
}

在服务器中,您遇到两个主要问题:

  1. 您假设数据全部集中在一个块中。没有这样的保证:据您所知,您的接收数据处理程序可以通知单个字节可用。

  2. 您只处理一个数据包 - 您可能会收到任意数量的数据包,并且必须继续阅读它们,直到没有更多数据可用。

  3. 让我们:

    include <utility> // Unreal headers are omitted in this example
    
    class Server {
      FSocket *rcvSocket;
      ...
    };
    
    bool Server::hardReceiveError() {
      // the socket has lost synchronization - we can't proceed!
      rcvSocket->Close();
      ...
      return false;
    }
    
    void Server::processPacket(const TArray<uint8> &pkt) {
      // process the packet
      ...
    }
    
    // Apparently the Unreal engine doesn't care enough to offer this (?)
    template <typename T> 
    std::enable_if<std::is_integral<T>::value, T>::type fromLittleEndian(const void *buf) {
      T ret;
      memcpy(&ret, buf, sizeof(ret));
      if (!FGenericPlatformProperties::IsLittleEndian()) {
        uint8 *r = reinterpret_cast<uint8*>(&ret);
        for (int i = 0; i < sizeof(T)/2; ++i)
          std::swap(r[i], r[sizeof(T)-1-i]);
      }
      return ret;
    }
    

    要接收通过数据流发送的字节数组数据,您必须处理大小:

    // handle all available data
    void Server::receiveHandlerDataStream() {
      while (receiveDataStream());
    }
    
    // receive one packet
    bool Server::receiveDataStream() {
      uint32 pendingData = 0;
      rcvSocket->HasPendingData(pendingData);
      if (pendingData < 4)
        return false;
      int32 bytesRead = 0;
      uint8 buf[4];
      rcvSocket->Recv(buf, sizeof(buf), bytesRead, ESocketReceiveFlags::Peek);
      if (bytesRead != 4)
        return hardReceiveError();
      auto length = fromLittleEndian<int32>(buf);
      if (pendingData < (4+length)
        return false;
      bytesRead = 0;
      rcvSocket->Recv(buf, sizeof(buf), bytesRead, ESocketReceiveFlags::None);
      if (bytesRead != 4)
        return hardReceiveError();
      TArray<uint8> data;
      data.AddUninitialized(length);
      bytesRead = 0;
      if (length) { // we may have 0-length packets
        rcvSocket->Recv(data.GetData(), data.ArrayNum, bytesRead, ESocketReceiveFlags::None);
        if (bytesRead != length)
          return hardReceiveError();
      }
      processPacket(data);
      return true;
    }
    

    接收原始数据包 - 不通过数据流作为bytearray发送:

    // handle all available data
    void Server::receiveHandlerRaw() {
      while (receiveRaw());
    }
    
    // receive one packet
    bool Server::receiveRaw() {
      uint32 pendingData = 0;
      constexpr uint32 expects = 25;
      rcvSocket->HasPendingData(pendingData);
      if (pendingData < expects)
        return false;
      int32 bytesRead = 0;
      TArray<uint8> data;
      data.AddUninitialized(expects);
      rcvSocket->Recv(data.GetData(), data.ArrayNum, bytesRead, ESocketReceiveFlags::None);
      if (bytesRead != data.ArrayNum)
        return hardReceiveError();
      processPacket(data);
      return true;
    }
    

    注意:这是未经测试的。