Russian Qt Forum
Апрель 25, 2024, 07:28 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: [РЕШЕНО] Передать по сети большую структуру данных  (Прочитано 5173 раз)
Vladimir
Крякер
****
Offline Offline

Сообщений: 305



Просмотр профиля
« : Ноябрь 24, 2014, 22:22 »

Есть некий список структур, который нужно передать с сервера на клиент, либо с клиента на сервер.
Сериализую список в QByteArray следующим образом: сначала добавляю заголовок в котором указываю, количество объектов в списке и размер передаваемых данных.

Код:
   QByteArray arrBlockHead,arrBlockBody,arrBlockSend;
   headCommand headComm(cmdTypeSign);

    for(int i = 0; i < gui->lstSign.size(); ++i)
    {
        arrBlockBody.append((const char*)(&gui->lstSign[i].prmSign),sizeof(PrmSign));
        arrBlockBody.append((const char*)(&gui->lstSign[i].base),     sizeof(BaseParamObj));
        for(int p = 0; p < gui->lstSign[i].prmSign.countPoint; ++p)
        {
            _doubleValue _x,_y;
            _x.value = gui->lstSign[i].posInGeo[p].x();
            _y.value = gui->lstSign[i].posInGeo[p].y();
            arrBlockBody.append((const char*)(&_x),sizeof(_doubleValue));
            arrBlockBody.append((const char*)(&_y),sizeof(_doubleValue));
        }
    }

    headComm.numberData = gui->lstSign.size();
    headComm.sizeData   = arrBlockBody.size();
    arrBlockHead.append((const char*)(&headComm),sizeof(headCommand));

    arrBlockSend.append(arrBlockHead);
    arrBlockSend.append(arrBlockBody);
arrBlockSend - пишу в сокет.

на принимающей стороне сначала вычитываю заголовок:
Код:
headCommand headComm(-1);
int rc = sock.command->read((char*) &headComm, sizeof(headCommand));

оттуда забираю тип присланных данных, размер sizeData и количество numbData и отдаю нужной функции на обработку:
Код:
void Client::procPacketSign(int sizeData, int numbData)
{    
    if(sock.command->bytesAvailable() >= sizeData)
    {
        QList<AllPrmSign> recvListSign;
        for(int i = 0; i < numbData; i++)
        {
            AllPrmSign allSign;
            qint64 rc_p0 = sock.command->read(reinterpret_cast<char*>(&allSign.prmSign),sizeof(PrmSign));
            qint64 rc_p1 = sock.command->read(reinterpret_cast<char*>(&allSign.base),sizeof(BaseParamObj));
            if((rc_p0 == sizeof(PrmSign)) && (rc_p1 == sizeof(BaseParamObj)))
            {
                for(int p = 0; p < allSign.prmSign.countPoint; ++p)
                {
                    _doubleValue _x,_y;
                    qint64 rc_p2 = sock.command->read(reinterpret_cast<char*>(&_x),sizeof(_doubleValue));
                    qint64 rc_p3 = sock.command->read(reinterpret_cast<char*>(&_y),sizeof(_doubleValue));
                    if((rc_p2 == sizeof(_doubleValue)) && (rc_p3 == sizeof(_doubleValue)))
                    {
                        QPointF posMet;
                        posMet.setX(_x.value);
                        posMet.setY(_y.value);
                        allSign.posInGeo.push_back(posMet);
                    }
                }
                recvListSign.push_back(allSign);
            }
        }

        network->aws->gui->lstSign.clear();
        network->aws->gui->lstSign = recvListSign;

        network->aws->winDraw->recalcCoordGeoToPixSign();
        emit sgRecvNewSign();
    }
}

Все вроде как работает до размера передаваемого списка ~2Kb, если передаваемых данных становится больше, то посылка начинает дробиться на меньшие части, в одной из которых будет заголовок с нужной инфой, во второй части естественно нет. Поэтому происходит неправильное чтение из сокета, ну и дальнейшая каша. Вопрос: как правильно реализовать такого рода обмен? Самому дробить на части, но как это правильно сделать и гарантирует ли TCP\IP ту последовательность прихода данных с которой они были записаны в сокет?
« Последнее редактирование: Ноябрь 25, 2014, 13:58 от Vladimir » Записан
DenKor29
Гость
« Ответ #1 : Ноябрь 24, 2014, 22:37 »


Все вроде как работает до размера передаваемого списка ~2Kb, если передаваемых данных становится больше, то посылка начинает дробиться на меньшие части, в одной из которых будет заголовок с нужной инфой, во второй части естественно нет. Поэтому происходит неправильное чтение из сокета, ну и дальнейшая каша. Вопрос: как правильно реализовать такого рода обмен? Самому дробить на части, но как это правильно сделать и гарантирует ли TCP\IP ту последовательность прихода данных с которой они были записаны в сокет?

Вики.

TCP (IP идентификатор 6) — «гарантированный» транспортный механизм с предварительным установлением соединения, предоставляющий приложению надёжный поток данных, дающий уверенность в безошибочности получаемых данных, перезапрашивающий данные в случае потери и устраняющий дублирование данных. TCP позволяет регулировать нагрузку на сеть, а также уменьшать время ожидания данных при передаче на большие расстояния. Более того, TCP гарантирует, что полученные данные были отправлены точно в такой же последовательности. В этом его главное отличие от UDP.


Правильно ваш случай реализовать через механизм комманд протокола 7 уровня.

Например 1 команда - передача одной структуры (фиксированный заголовок формата пакета) + сам QByteArray.
2 команда подтверждение приема структуры.
3 команда - Инициализация соединения с обменом информацией о версиях протокола и фичах

P.s. Сразу вспомнил как я програмировал kflip и binkf для сети фидонет  Смеющийся
« Последнее редактирование: Ноябрь 24, 2014, 22:47 от DenKor29 » Записан
DenKor29
Гость
« Ответ #2 : Ноябрь 24, 2014, 22:46 »

Соответсвенно работа идет через буфер ввода-вывода.

1. Считываете заголовок с фиксированным размером (размер буфера больше длины заголовка).
2. Выясняете размер передаваемых данных и считываете в буфер оставшиеся данные
В текущей ситуацией с размером памяти можно считывать напрямую в QByteArray.


Имхо прежде чем все реализовать продумайте логику комманд протокола.
Записан
Vladimir
Крякер
****
Offline Offline

Сообщений: 305



Просмотр профиля
« Ответ #3 : Ноябрь 24, 2014, 23:09 »

Соответсвенно работа идет через буфер ввода-вывода.

1. Считываете заголовок с фиксированным размером (размер буфера больше длины заголовка).
2. Выясняете размер передаваемых данных и считываете в буфер оставшиеся данные
В текущей ситуацией с размером памяти можно считывать напрямую в QByteArray.


Имхо прежде чем все реализовать продумайте логику комманд протокола.
Как-то не совсем понятно..
Через буфер ввода-вывода - это QDataStream?! Как-то с ним мало работал.. Всегда было проще фиксированную структуру записать в сокет, затем ее же и вычитать.. в чем преимущество в данном случае QDataStream, он не будет бить данные и все считает за раз?
Записан
DenKor29
Гость
« Ответ #4 : Ноябрь 24, 2014, 23:55 »

Соответсвенно работа идет через буфер ввода-вывода.

1. Считываете заголовок с фиксированным размером (размер буфера больше длины заголовка).
2. Выясняете размер передаваемых данных и считываете в буфер оставшиеся данные
В текущей ситуацией с размером памяти можно считывать напрямую в QByteArray.


Имхо прежде чем все реализовать продумайте логику комманд протокола.
Как-то не совсем понятно..
Через буфер ввода-вывода - это QDataStream?! Как-то с ним мало работал.. Всегда было проще фиксированную структуру записать в сокет, затем ее же и вычитать.. в чем преимущество в данном случае QDataStream, он не будет бить данные и все считает за раз?

Например вам нужно передать  бинарные файлы неизвестной длины.

Буфер ввода-вывода в данном случае это массив байт которые вы считываете, но пока не обрабатываете.

Например ваш буфер - это массив байт (допустим 1Мб).
По сигналу readyread() вы этот массив заполняете данными с начала массива.

Точно известно (по вашей версии протокола), что первые 4 байта это размер файла. Затем идут данные файла.
Окончание потока данных это размер следующего файла 0.

Принцип работы протокола

1. Считывете из вашего буфера размер файла - 4байта. Если 0 то выходите.
2. Создаете новый QByteArray
3. Считываете данные из буфера в этот  QByteArray
4. Данные из буфера удаляете (считанные)
5. Получете файл
6. Переходите к пункту 1

=========================================================
одной из которых будет заголовок с нужной инфой, во второй части естественно нет. Поэтому происходит неправильное чтение из сокета, ну и дальнейшая каша.
===========================================================

Читайте данные через промежуточный буфер ввода-вывода. Все приходящие части нужно добавлять в единое целое.
И только потом обрабатывать. В Вашем случае нужно начинать обработку только когда вы получаете весь массив данных.
Пишите протокол передачи данных  на бумаге . В вашем алгоритме не учитвается раздробление данных при передаче Смеющийся



Записан
Vladimir
Крякер
****
Offline Offline

Сообщений: 305



Просмотр профиля
« Ответ #5 : Ноябрь 25, 2014, 13:58 »

DenKor29, спасибо за разъяснения. Но получилось доделать свой вариант таким образом:
Код:
while(sock.command->bytesAvailable())
    {
        // читаем заголовок команды
        headCommand headComm(-1);
        int rc = sock.command->read((char*) &headComm, sizeof(headCommand));
        if((rc == -1) || (rc == 0))
          return;

        // подождем полного пакета данных, если он пришел не полностью
        if(!waitRecvData(headComm.sizeData))
        {
           sock.command->readAll();
           return;
        }

        switch(headComm.type)
        {
              ...
        }
ф-ция ожидания прихода данных
Код:
bool Client::waitRecvData(int sizeData)
{
    // если доступных данных для чтения из сокета меньше, чем должно быть
    // подождем прихода данных 1 msec, но не более 5 msec
    int time = 0;
    while((sock.command->bytesAvailable() < sizeData) && (time < 5))
    {
       sock.command->waitForReadyRead(1); // msec
       time++;
    }
    if(time >= 5) return false;

    return true;
}
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.097 секунд. Запросов: 22.