Несоответствие между байтами, отправленными через QDataStream от клиента к серверу - PullRequest
0 голосов
/ 21 мая 2018

Я пытаюсь отправить 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 голосов
/ 21 мая 2018

Сервер работает на движке Unreal.Это важно!

Вы отправляете свои данные дважды: один раз через поток данных, в следующий раз через write.Не делайте этого: отправьте его один раз, используя ваш предпочтительный метод.

Если вы хотите использовать поток данных:

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

4 + data.size() байт записано впоток.Начальные 4 байта несут младшую байтовую длину массива, который следует.Если вы не хотите отправлять длину массива, вы должны использовать 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. Вы обрабатываете только один пакет - вы можете получить любое числоиз них, и вы должны продолжать читать их, пока больше нет данных.

Давайте иметь:

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

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

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

Примечание: Это не проверено.

0 голосов
/ 21 мая 2018

Я вижу две проблемы в вашем клиентском коде.Поскольку вы используете (предположительно) подключенный сокет в качестве основного устройства для потока данных:

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;

Или вы можете вручную удалить первые четыре байта, что я не рекомендую на самом деле.

...