Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум на CrossPlatform.RU _ Qt Ввод/Вывод, Сеть. Межпроцессное взаимодействие _ "Склеивание" сообщений ТСР

Автор: pwp2008 19.10.2015, 19:31

Подскажите пож-та, встречалось ли Вам отправленные друг за другом сообщения (клиентом) получать в сервере "склееном" виде, как если бы это было одно сообщение. В чем причина такой работы по протоколу TCP\IP ? Разрулить эту проблему удалось несколько искусственным способом, хотя здесь может подойти и передача длины сообщения и/или контрольный суммы. Видимо так склеены могут быть не 2, а даже больше последовательных сообщений.

Автор: ViGOur 20.10.2015, 9:09

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

struct sTcpData{
    int nLen;    // Размер буфера
    char *pBuf;  // Сам буфер
}
По желанию можно добавить CRC

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

Автор: pwp2008 20.10.2015, 19:09

Цитата(ViGOur @ 20.10.2015, 9:09) *
Это нормально. Пакет может прийти неполный или склеенный с другим, а то и с несколькими.
.............................................
TCP буфер можно расценивать как файл, в который пишутся данные одним потоком, а из другого потока ты
эти данные разбираешь.

Ну да, из практики вроде оно так и есть. Но ведь все таки по каким то правилам входной поток то прерывается ?
Опять же, там вроде 7 или 8 уровней в протоколе ТСР и над ними опять нужно свои уровни добавлять, тем более с
контрольными суммами ? Как то не ice.....
По моему здесь как то завязана gui-технология обработки, пока приложение не выйдет на ожидание событий-поток
в ТСР не прерывается, стоит только это организовать - сообщения перестают склеиваться. Может вообще вынести
запись в сокет в отдельный thread (надо бы попробовать).

Автор: ViGOur 20.10.2015, 21:03

Ты не понял.
TCP стеку все равно, что в него попадает видео, звук или сообщения, он как получил так и отдал, твоя забота уже правильно разобрать переданые тобой данные.
За пример можно взять лог файл, если не сделать правильное форматирование, то логи будут не читабельны, то же самое и здесь.

Есть 3 варианта того, какими ты можешь получить свои сообщения:
1. Не полное сообщение (в том случае, если ты забираешь данные быстрей, чем они приходит)
2. Полное сообщения (идеальное стечение обстоятельств)
3. Склеенное сообщение (в том случае, если ты забираешь медленней, чем они приходят и склееным может быть как часть следующего пакета, так и несколько пакетов...)

Обычно, чтобы всегда были идеальные условия используют структуру приведенную мной выше, по следующей схеме:
1. При поступленни данных читаем первые 4 байта, и приводим их к int, чтобы получить размер буфера.
2. Читаются столько байт, сколько указанно в первых четырех байтах полученных в 1 пункте.
3. Что-то там делаем с полученными данными.
4. Переходим к 1 пункту.

Или чтобы не писать свой протокол над TCP, отправлять сообщения одной длины, например 1024 байта...

Автор: pwp2008 25.10.2015, 18:11

Цитата(ViGOur @ 20.10.2015, 21:03) *

Наработка по "склеиванию" сообщений такова :
Имеем 2 простых приложения : 1-Сервер 2-Клиент (хотя кто из них Сервер, кто Клиент
-чисто условное деление). Сервер по кнопке на своем окне высылает клиенту сообщение
по сети ТСР\IP. Соединение между ними было преварительно установлено : сервер
ждал соединения, Клиент - соединился и ждет сообщений от сервера. После получения
его выдает его в свое окно. Так вот, если сообщение сервера оформлено как одно:
string parcel_x = "wwwwwwwwwwwwwwwwwwwwwwwwwwwww";
int numBytes = firstClientSocket->write(parcel_x.data(),parcel_x.length());
то Клиент его получает и показывает. Если же поставить 2 сообщения подряд :

string parcel_x = "wwwwwwwwwwwwwwwwwwwwwwwwwwwww";
int numBytes = firstClientSocket->write(parcel_x.data(),parcel_x.length());
string parcel_z = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
int numBytes = firstClientSocket->write(parcel_z.data(),parcel_z.length());
......................................
то Клиент получает только одно, но "склеенное" из 2-х сообщение. Хотелось бы
понять причины этого и по возможности "разделить" сообщения, чтобы они приходили
на клиент в виде 2 сообщений.
Проверено следующее :
Если второе сообщение выполнить по слоту однократного таймера через ххх мсек,
то тогда склейки на клиенте нет :
string parcel_x = "wwwwwwwwwwwwwwwwwwwwwwwwwwwww";
int numBytes = firstClientSocket->write(parcel_x.data(),parcel_x.length());
int xxx = 50; // интервал для срабатывания
QTimer::singleShot(xxx,this,SLOT(slotMessage_2()));
..............................

void PRG::slotMessage_2()
{
string parcel_z = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
int numBytes = firstClientSocket->write(parcel_z.data(),parcel_z.length());
}
и каждое сообщений приходит отдельно. Причем , уже при xxx > 11 .
Если xxx=1, то сообщения "склеиваются" и в таком исполнении.
Явное указание в сервере типа соединения = Qt::DirectConnection в методе connect :
bool QObject::connect ( const QObject * sender, const char * signal,
const QObject * receiver, const char * method,
Qt::ConnectionType type = Qt::AutoConnection ) [static]

"склеивания" не убирает( похоже может на Клиенте нужно было так же сделать ? на Клиенте я не ставил, упустил)
Тексты приложений не привожу - это просто пример из Qt по технологии Клиент-Сервер.

Автор: Влад 26.10.2015, 14:45

Цитата(pwp2008 @ 25.10.2015, 18:11) *
......Клиент получает только одно, но "склеенное" из 2-х сообщение. Хотелось бы
понять причины этого и по возможности "разделить" сообщения, чтобы они приходили на клиент в виде 2 сообщений.
......
и каждое сообщений приходит отдельно. Причем , уже при xxx > 11 .
Если xxx=1, то сообщения "склеиваются" и в таком исполнении.

Еще раз: протокол TCP ничего не "знает" ни о каких сообщениях. TCP просто передает поток байтов от передатчика (передающей стороны) к приемнику. Все.
Как он будет "склеивать" или наоборот "разрезать" твои сообщения - никто не дает никаких гарантий. В зависимости от загрузки сети при одном и том же исходном коде и одних и тех же таймаутах может быть и "склеивание", и "разрезание" сообщений - по внутреннему усмотрению драйвера TCP. Поэтому подбирать какие-либо таймауты, слоты и прочее - бессмысленно.

Правильный подход тебе описал коллега ViGOur.

Автор: ViGOur 26.10.2015, 17:17

pwp2008, вот ты упрямец! Перечитай заново, что я описал выше. :)

Таймауты тебе могут помочь в том случае, если сервер или клиент не заняты, но если один из них занят (I\O операции или по процессору) то всеравно будет склейка и тайм ауты не помогут. Это из серии я надеюсь всё будет в порядке, но в программировании это не допустимо, потому нужно создавать условия, чтобы всё было в порядке.

Автор: pwp2008 26.10.2015, 19:49

Цитата(ViGOur @ 26.10.2015, 17:17) *
pwp2008, вот ты упрямец! Перечитай заново, что я описал выше. :)

Не упрямый, а просто - неграмотный. Стаж по диалогу в TCP у меня небольшой, может год- полтора, на 2х или 3х приложениях, просто я ни разу до текущего приложения не попадал с такие ситуации("склеивание\разрезание").
Это, конечно, не довод. Но от всей этой ситуации повеяло безнадежностью :
Цитата(ViGOur)
Обычно, чтобы всегда были идеальные условия используют структуру приведенную мной выше, по следующей схеме:
1. При поступленни данных читаем первые 4 байта, и приводим их к int, чтобы получить размер буфера.
2. Читаются столько байт, сколько указанно в первых четырех байтах полученных в 1 пункте.
3. Что-то там делаем с полученными данными.
4. Переходим к 1 пункту.

Т.е. 4 байта уж точно не разрежутся ? Вообще, можно и 2-мя обойтись, надежней.
Сколько ждать полного буфера, длина, которого была заявлена в заголовке, ведь можем и не дождаться?
Надеюсь, что ТСР не переставит блоки, а будет все-таки давать их в порядке отправления.
Контрольная сумма меня особенно радует.
Что ж, видимо с этим придется жить... Спасибо за помощь.
Есть такой протокол - МЭК 104. Похоже нужно от него брать ряд идей.

Автор: ViGOur 27.10.2015, 10:30

4 байта было сказано для примера, ты сам решай, какой длины будет у тебя поле длина пакета. К тому же жестко указывать размер нельзя, а только sizeof(int), так как под разными системами int разного размера.
4 байта могут так же прийти по частям, это маловероятно, но может быть, например при медленном соединении или при крайней загруженности системы или сети.
Блоки не переставляются и контрольные суммы по идее не нужны, так как TCP протокол гарантирует доставку пакета
Набросал на коленке пример реализации:

struct sDataPacket
{
    int nLen;
    char *pData;
};

sDataPacket *getPacket( int socket )
{
    sDataPacket *pPacket = new sDataPacket;
    // Читаем первые 4 байта
    int nLen = sizeof(pPacket->nLen);
    char *pBuff = new char[nLen];
    do
    {
        int nRet = recv( socket, pBuff, nLen, 0 );
        if( nRet == sizeof(pPacket->nLen) )
        {
            // Переводим прочитанные 4 байта в длину
            memcpy((void*)&pPacket->nLen, (void*)pBuff, nLen);
            // Читаем указанное количество байт данных
            if( pPacket->nLen > 0 )
            {
                pPacket->pData = new char[pPacket->nLen];
                nRet = recv( socket, pPacket->pData, pPacket->nLen, 0 );
                if( nRet < pPacket->nLen )
                {
                    // Дочитываем
                }
            }
            break;
        }
        else
        {
            // Дочитываем
        }
    }while(true);
    delete []pBuff;

    return pPacket;
}
Тебе только осталось дописать докачку...

Как вариант, во избежании склеивания, можно сделать клиент серверный диалог с подтверждением, например:
1. Клиент отправляет пакет
2. Сервер получает пакет
3. Сервер отправляет подтверждение о получении пакета
4. Клиент получает подтверждени и идет к 1 пункту

Автор: Влад 27.10.2015, 10:33

Цитата(pwp2008 @ 26.10.2015, 19:49) *
Т.е. 4 байта уж точно не разрежутся ? Вообще, можно и 2-мя обойтись, надежней.
Сколько ждать полного буфера, длина, которого была заявлена в заголовке, ведь можем и не дождаться?
Надеюсь, что ТСР не переставит блоки, а будет все-таки давать их в порядке отправления.
Контрольная сумма меня особенно радует.

Разрежутся или нет четыре байта длины - никто тебе не даст никаких гарантий. Да это и не имеет значения. Просто вычитывай из сокета, пока не наберутся в начале посылки эти четыре байта. А потом вычитывай столько байтов, сколько заявлено в заголовке. Либо они дойдут, либо ты получишь ошибку связи и должен на нее как-то отреагировать. Да, TCP не переставит блоки, в этом можно быть уверенным.

Автор: pwp2008 27.10.2015, 19:49

Цитата(ViGOur @ 27.10.2015, 10:30) *
4 байта было сказано для примера, ты сам решай, какой длины будет у тебя поле длина пакета. К тому же жестко указывать размер нельзя, а только sizeof(int), так как под разными системами int разного размера.
............................

Большое спасибо за пример. Еще, если можно, ряд уточнений.
1. Для моей задачи длина сообщений от 14 до 130 байт, поэтому вполне для длины хватит и 1-го байта.
Прилично ли считывать 1 байт? Хотя, судя по тому, что и 4 байта могут идти частями, думаю, что вполне
прилично.
2. Посоветуйте, какая технология может быть, чтобы не было 2-го входа в слот по сигналу ReadyRead ?
Ведь вполне возможно, что пока я буду выделять и возможно обрабатывать "склееные" сообщения и\или
ждать пока соберется все "разрезанное" сообщение, система может мне устроить второй вход в незаконченный
слот. Реентерабельность тут как то не просматривается.
Не снимать же connect по входу в слот обработки и назначать его потом снова перед выходом?
signal(readyRead)\slot -> обработка сообщений ТСР по идеологии Qt.
Я конечно попробую разобраться с вышеприведенным примером, т.к. в QT для сокета нет метода recv.

Автор: ViGOur 28.10.2015, 9:41

1. Почему нет? Ты можешь считать за один раз сколько тебе нужно, а там уже разобрать эти данные на 1 байт + данные + склеенное сообщение (если будет и отправить склеенной для следующего разбора)
2. Синхронизация, используй QMutex, чтобы пока один поток не отработал, другой не вошел для чтения.

Метод recv был для примера, ты можешь так же в место него использовать read... :)

Автор: pwp2008 29.10.2015, 19:14

Цитата(ViGOur @ 28.10.2015, 9:41) *
1. Почему нет? Ты можешь считать за один раз сколько тебе нужно, а там уже разобрать эти данные на 1 байт + данные + склеенное сообщение (если будет и отправить склеенной для следующего разбора)

----------------------------------------------------------------------------------------
По обработке склеенных\разделенных блоков ТСР в Qt 4.5. Привожу лишь существенный код.
Файл - mainwin_02.h
class MainWin_02 : public QMainWindow
{
    Q_OBJECT
public:
    QByteArray srv_R1;   // для ответа сервера
    QQueue <QByteArray> queueMsg;
    void handleQueue();
    bool f_queue;
..........................
public slots:
    void slotGetResponse();
};

Файл - mainwin_02.cpp
MainWin_02::MainWin_02(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWin_02)
{
................................................
//------------------------------------
    // Описание сокета.
    m_pTcpSocket = new QTcpSocket(this);
    connect(m_pTcpSocket,SIGNAL(connected()),SLOT(slotConnected()));
    connect(m_pTcpSocket,SIGNAL(readyRead()),SLOT(slotGetResponse()));
//------------------------------------
void MainWin_02::slotGetResponse()
{
    // читаем склееные и неполные сообщения по сигналу SIGNAL(readyRead()
    int k1,tLen;
    int kk = m_pTcpSocket->bytesAvailable();
    tLen = kk;
    bool f_queue = false;    // признак постановки в очередь
    while (tLen >0) {
        srv_R1.clear();
        srv_R1 = m_pTcpSocket->peek(1);
        k1= srv_R1[0];
        if (tLen >=k1) {
            srv_R1.clear();
            srv_R1 = m_pTcpSocket->read(k1+1);  // читаем вместе с длиной
            tLen = tLen-(k1+1);
            queueMsg.enqueue(srv_R1);               // пишем в очередь
            f_queue = true;
        } else break;    // выйдем, если неполное сообщение и ждем остаток
    }
    if (f_queue) {
        // выполнить обработку очереди
        handleQueue();
    }
}
void MainWin_02::handleQueue()
{
    // обработка очереди сообщений
    while (!queueMsg.isEmpty()) {
        srv_R3.clear();
        srv_R3 = queueMsg.dequeue();
        // далее обработка сообщения ..................................
        qDebug() << "r3="<< srv_R3.toHex() <<srv_R3.data()<< "ostatok="<<queueMsg.count();
    }
}


Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)