Russian Qt Forum

Qt => Работа с сетью => Тема начата: ilyagoo от Ноябрь 20, 2009, 12:02



Название: Как работает QTcpSocket::write()?
Отправлено: ilyagoo от Ноябрь 20, 2009, 12:02
вопрос в том, когда сокет действительно отдает данные TCP/IP протоколу?
делаю так:
1) пишу в сокет заголовок пакета:
Код:
struct TPackageHeader
{
   int PackageNumber;
   int PackageSenderID;
   int PackageDataSize;
};
2) пишу данные, их размер указан в TPackageHeader::PackageDataSize.
Код:
void f()
{
   TPackage header;
   header.PackageDataSize = 1024;
   socket->write( &header, sizeof( header ) );
   socket->write( data, header.PackageDataSize );
}
На противоположном конце шланга другой сокет читает заголовок, читает данные, основываясь на TPackageHeader::PackageDataSize.
(*)Ожидается, что после вызова f() процесс попадет в цикл обработки событий и данные уйдут по сети.
Значит, если я буду делать
Код:
for ( int i = 0; i < 100; i++ )
   f();
Через шланг может политься инфа, разбитая на пакеты произвольно, но в результате я получаю просто 3-4 пакета, при повторном вызове новае пакеты дают пинок предыдущим, и те приходят еще одной кучкой из 3х-4х. Куда при этом деваются остальные - неясно.

Если предположить, что (*) верно, хотя уже предыдущий пример заставляет усомниться в этом, то положение могло бы исправить следующее действие:
Код:
for ( int i = 0; i < 100; i++ )
{
   f();
   qApp->processEvents();
}
Но, увы...
В чем тут фишка? Причем, начинает доходить около 60% пакетов, а со стороны отправителя фиксируется отправка всех пакетов в любом случае.


Название: Re: Как работает QTcpSocket::write()?
Отправлено: BRE от Ноябрь 20, 2009, 12:10
Попробуй после write делать flush.

Почитай про TCP и в частности про алгоритм Nagle, который старается оптимизировать число исходящих пакетов.


Название: Re: Как работает QTcpSocket::write()?
Отправлено: ilyagoo от Ноябрь 20, 2009, 12:15
Цитировать
Попробуй после write делать flush.
не помогает

читаю о nagle...
прочел:
Цитировать
Applications that expect real time responses can react poorly with Nagle's algorithm. Applications such as networked multiplayer video games expect that actions in the game are sent immediately, while the algorithm purposefully delays transmission, increasing bandwidth at the expense of latency. For this reason applications with low-bandwidth time-sensitive transmissions typically use TCP_NODELAY to bypass the Nagle delay

TCP_NODELAY это где-то в системе?


Название: Re: Как работает QTcpSocket::write()?
Отправлено: kuzulis от Ноябрь 20, 2009, 14:27
Цитировать
не помогает
должно помочь


Название: Re: Как работает QTcpSocket::write()?
Отправлено: BRE от Ноябрь 20, 2009, 14:30
TCP_NODELAY это где-то в системе?
Ты бы еще систему озвучил.


Название: Re: Как работает QTcpSocket::write()?
Отправлено: ilyagoo от Ноябрь 20, 2009, 14:59
WinXP, МСВС. flush() не помогает нигде.
пытаюсь через WinAPI setsockopt() - та же фигня...
Qt-4.4.2, версию сменить не могу, если что :)



Название: Re: Как работает QTcpSocket::write()?
Отправлено: maxxant от Ноябрь 20, 2009, 16:00

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


Лучше вообще не думать о низкоуровневой реализации протокола, TCP для программиста просто поток данных. Скорее всего ошибка в коде. Как читаете данные? Типичная ошибка бывает при чтении и неправильной обработке сигнала readyRead() - поскольку по приходу этого сигнала необходимо считывать все данные, а не свой один пакет, как иногда пытаются делать.

+ при такой структуре пакета неизвестна заранее его длина и для того чтобы пакет считать бывает нужно использовать QIODevice::peek(), чтобы без опустошения буфера проверить пришёл ли пакет целиком или нет (нужно считать PackageDataSize). Просто проще это делать когда первым полем идёт именно размер пакета.

+ socket->write( &header, sizeof( header ) );
не есть хорошо, sizeof - не кроссплатформенно, размер структуры может зависеть от выравнивания в опциях компилятора или процессора, также не учитывается порядок байт при такой записи в сокет - не критично если пишите для себя и только для одного процессора. В ином случае лучше писать по одному полю структуры в сокет и использовать явные размеры типов типа quint32, qint8, и т.д. Ещё хорошо бы осилить QByteArray и QDataStream и писать все пакеты как QByteArray (прим на всяк случай: с Qt 4.6. тип float будет в QDataStream всегда писаться с размером double).


Название: Re: Как работает QTcpSocket::write()?
Отправлено: ilyagoo от Ноябрь 20, 2009, 16:16
Цитировать
не есть хорошо, sizeof - не кроссплатформенно, размер структуры может зависеть от выравнивания в опциях компилятора или процессора, также не учитывается порядок байт при такой записи в сокет
абсолютно согласен :)

Цитировать
Ещё хорошо бы осилить QByteArray и QDataStream
я-то осилю, а вот осилит ли клиент, написанный не на Qt... видимо, нет.

а вот читаю данные я возможно левой задней... минут через 5 закину исходник


Название: Re: Как работает QTcpSocket::write()?
Отправлено: ilyagoo от Ноябрь 20, 2009, 16:25
там контроля никакого, сорри, нет времени на баловство :)
запускаешь два экземпляра ( можно и один, но отладка смешается ), на одном первом жмешь Start, на втором - Connect. С первого можно слать данные. IP, естесственно ставишь свой.


Название: Re: Как работает QTcpSocket::write()?
Отправлено: ilyagoo от Ноябрь 20, 2009, 16:40
мда... левой задней - мягко сказано, я просто не дочитываю оставшееся, что бы с этим делать?
может перед выходом из функции сгенерить сигнал readyRead() самому?


Название: Re: Как работает QTcpSocket::write()?
Отправлено: maxxant от Ноябрь 20, 2009, 16:54
мда... левой задней - мягко сказано, я просто не дочитываю оставшееся, что бы с этим делать?
может перед выходом из функции сгенерить сигнал readyRead() самому?

в void ClientServer::receivePackage()  может как-то while() {} прикрутить проще.

вообще у меня типично как то так, с учётом что первое 32 битное поле это размер пакета а потом любые данные:
Код:
//------------------------------------------------------------------------------
void net::connection::io_dev_ready_read()  // от сигнала readyRead()
{
    while(checkMessage())
    {
        // msg ready for read
        qint32 _size=0;
        QByteArray data;
        qint64 _readed = p_ioDevice->read((char*) &_size, sizeof(_size));
        if((sizeof(_size)==_readed) && (_size>0))
        {
            if(_size > const_message_size_max)
            {
                qCritical() << "big message received "
                        << _size << " bytes!";
                this->resetState();
                return;
            }
            data = p_ioDevice->read(_size);
            if(data.size() != _size)
            {
                qCritical() << "couldn't read full message";
                this->resetState();
                return;
            }
            emit evPacketReceived(data);
      }
   }
}

//------------------------------------------------------------------------------
bool net::connection::checkMessage()
{
    qint64 _bytes_available = p_ioDevice->bytesAvailable();
    if(_bytes_available<4) return false;

    qint32 _size=0;
    qint64 _peek_size = p_ioDevice->peek((char*)&_size, sizeof(_size));
    if(_peek_size != sizeof(_size)) return false;
    _bytes_available = p_ioDevice->bytesAvailable();
    if(_bytes_available < sizeof(_size) + _size) return false;

    return true;
}

сокет наследуется от QIODevice поэтому у меня  p_ioDevice  - по сути указатель на QTcpSocket.


Название: Re: Как работает QTcpSocket::write()?
Отправлено: ilyagoo от Ноябрь 24, 2009, 13:54
перед выходом из
Код:
void ClientServer::receivePackage()
проверяю, еостались ли еще данные:
Код:
   if (m_Client->bytesAvailable()) emit rereadData();
этот сигнал связан с сигналом
Код:
QTcpSocket::readyRead()

сравнил в профайлере с вариантом io_dev_ready_read() - мой производительнее.

теперь все данные приходят :)

но проблемы продолжаются: при попытке пересылать пакеты 50000 байтов (цифра случайная, а не граничная) на принимающем конце шланга данные еле-еле приходят, а отправитель толстеет в памяти на глазах. очевидно, отправитель ставит пакеты в очередь TCP\IP, вот только как это проверить, ведь QTcpSocket::write() возвращает правильное количество записанных байтов?