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

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

Форум на CrossPlatform.RU _ Qt Ввод/Вывод, Сеть. Межпроцессное взаимодействие _ Обрыв соединения QTcpSocket

Автор: OrSOn 16.2.2010, 14:47

Всем добрый день! Столкнулся с довольно неприятной проблемой при работе с сокетами... Суть проблемы в следующем:

Имеется сервер и клиент, для общения используют сокеты. В некий момент времени клиент рушится (ну или связь падает, не суть важно), при этом сервер пытается что-то писать в сокет, что приводит к ошибке. Чтобы избежать этого, пробовал ввести проверку

if( tcpSocket->state() != 3 )
    return;
tcpSocket->flush();


Так вот случаются ситуации, что проверка состояния проходит, а сразу после нее до начала проталкивания информации клиент падает и на tcpSocket->flush() снова вылетаю с ошибкой.

Если кто сталкивался с подобным, подскажите, пожалуйста, как это можно обойти...

Автор: sploid 16.2.2010, 17:29

так в чем проблема? в том что сервер не знает записаны данные или нет?

Автор: SABROG 16.2.2010, 20:28

Цитата(OrSOn @ 16.2.2010, 14:47) *
вылетаю с ошибкой.


С какой именно и куда? Обрабатывать ошибку никак нельзя что ли?

Автор: OrSOn 17.2.2010, 10:07

Обработать ошибку нельзя - полностью прерывается дебаг и вылетаю, ошибка получается в том, что я пытаюсь записать информацию в сокет, который имеет состояние disconnected(), причем это состояние он получает в процессе выполнения команды записи, т.е. заранее это не отследить. Грубо говоря, получается такая ситуация:

1. Сокет подключен, начинается запись (запускается стандартная функция Qt)
2. Прямо во время записи рвется соединение
3. Половина функции прошла, а во второй оказывается, что соединения нет и вылетает ошибка...

Автор: BRE 17.2.2010, 10:15

Цитата(OrSOn @ 17.2.2010, 10:07) *
Обработать ошибку нельзя - полностью прерывается дебаг и вылетаю, ошибка получается в том, что я пытаюсь записать информацию в сокет, который имеет состояние disconnected(), причем это состояние он получает в процессе выполнения команды записи, т.е. заранее это не отследить. Грубо говоря, получается такая ситуация:

1. Сокет подключен, начинается запись (запускается стандартная функция Qt)
2. Прямо во время записи рвется соединение
3. Половина функции прошла, а во второй оказывается, что соединения нет и вылетает ошибка...

Покажи код, как ты это делаешь. Никаких вылетов быть не должно.

Автор: OrSOn 17.2.2010, 10:19

while( tmpLength < file.size() )
    {
        if( file.size() - tmpLength > blockLength )
            tmpBlock = blockLength;
        else
            tmpBlock = file.size() - tmpLength;

        char * dataBlock = new char[ tmpBlock ];

        file.read( dataBlock, tmpBlock );

        if( !tcpSocket || freeThread )
            return;
        else
            if( tcpSocket->state() != 3 )
                return;
        tcpSocket->write( dataBlock, tmpBlock );

        if( !tcpSocket || freeThread )
            return;
        else
            if( tcpSocket->state() != 3 )
                return;
        while( tcpSocket->flush() )
        {
            if( !tcpSocket || freeThread )
                return;
            else
                if( tcpSocket->state() != 3 )
                    return;
            continue;
        }

        delete []dataBlock;

        tmpLength += tmpBlock;
    }


Вот, это "тот самый" кусок примера... Как видно, уже везде, где только можно, включена проверка на разрывы

if( !tcpSocket || freeThread )
    return;
else
    if( tcpSocket->state() != 3 )
        return;


Тем не менее, на строке while( tcpSocket->flush() ) вылетает ошибка, потому что разрыв умудряется произойти во время выполнения...

З.Ы. чтобы не так нагромождено все было, вот изначальный код без проверок

while( tmpLength < file.size() )
    {
        if( file.size() - tmpLength > blockLength )
            tmpBlock = blockLength;
        else
            tmpBlock = file.size() - tmpLength;

        char * dataBlock = new char[ tmpBlock ];

        file.read( dataBlock, tmpBlock );

        tcpSocket->write( dataBlock, tmpBlock );

        while( tcpSocket->flush() )
            continue;

        delete []dataBlock;

        tmpLength += tmpBlock;
    }


Автор: BRE 17.2.2010, 10:25

Покажи как создается объект tcpSocket и какие сигналы обрабатываются.

Ты уверен, что этот объект не разрушается где-то еще (вижу там какой-то freeThread)?

Все эти проверки не нужны, ошибка где-то в другом месте.
У тебя течет память для буфера dataBlock, при любой ошибки - она не удаляется.
Зачем цикл у flush?

Автор: OrSOn 17.2.2010, 10:34

Значит по порядку...
Объект создается нормально, но в другом классе (раньше все работало, пока я не стал разбивать файлы на блоки, т.е. когда файл отсылал целиком, никаких ошибок не было). Суть в следующем: есть управляющий класс,в нем QTcpServer. Когда поступает запрос подключения, создается сокет, затем он добавляется в очередь. А далее из очереди сокеты передаются в заранее созданные потоки на обработку, после чего возвращаются обратно в очередь.

Насчет не освобождается память - я в курсе, это не страшно в данном случае (ошибки от этого не возникнет), потом устраню, когда разберусь, где все рушится.

Цикл сделан тоже неспроста... Раньше было без него, но столкнулся с некоторой проблемой - длинные файлы просто не передавались целиком, т.е. часть файла принималась с клиентской стороны, а потом сигналы readyRead() просто переставали приходить, т.е. каким-то образом остатки файла не проталкивались, хотя сервер и уходил дальше в eventLoop... Да и не в этом проблема, он рушится даже на первом вызове... Вот и не могу понять, почему при неизменном коде все работало при отправке файлов целиком и стало рушится при разбиении на блоки...

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

Автор: BRE 17.2.2010, 10:50

Класс TcpSocket данные кеширует сам и отправляет их блоками. Происходит это в цикле обработки событий.

Ты не показал код..., но мне кажется, что при разрыве соединения сам объект на который указывает tcpSocket разрушается (например в другом потоке), и ты пытаешься использовать уже разрушенный объект.

Метод write возвращает размер реально записанных данных, не всегда все данные могут поместиться в буфер отправки TCP-стека (хотя Qt и кеширует их сам). Эту ситуацию так же нужно обрабатывать.

Автор: OrSOn 17.2.2010, 11:02

Увы, но удаления объекта не происходит. Я уже думал об этом, поэтому тупо закомментировал ВСЕ места, где происходит удаление - результата нет. А насчет обработки записи, flush() записывает максимально возможное количество информации (не дожидаясь возвращения в цикл обработки событий), поэтому помещение его в цикл (пока записывается) как раз и осуществляет эту обработку (я уже проверял на файлах по 1.36 гигабайта). И если соединение не рвать, тов се работает и передается отлично, ломается только при закрытии клиента.

З.Ы. как говорит ассистант, мы не можем использовать flush() для сокетов, у которых состояние не connected(), так что рушится оно неспроста... А вот как обойти - хз.

З.З.Ы. код показать не могу, ибо там слишком много и все в разных классах... Но если конкретно создание сокета интересует, то вот оно:

 
if (tcpServer->hasPendingConnections()==FALSE)
        return;
    
tcpSocket = tcpServer->nextPendingConnection();
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(readFortune()), Qt::QueuedConnection);
tcpSocket->reset();

tcpSocket->write((QString::number((int)((void *)tcpSocket))).toLocal8Bit());

tcpSocket->flush();

tmpSocketList.append( tcpSocket );


Это содержимое слота, который обрабатывает сигнал newConnection()

Автор: BRE 17.2.2010, 11:39

Ну так если ты хочешь отправлять синхронно, то попробуй вместо flush крутить цикл обработки событий.

Автор: OrSOn 17.2.2010, 11:45

Боюсь, что так будет не лучше, ибо сложнее отследить, когда данные передались полностью... Понять бы, как тут избежать вылетов..

Автор: BRE 17.2.2010, 12:11

Как только ты записал последний кусок данных в сокет и метод write это подтвердил.
Дальше забота Qt.

Автор: OrSOn 17.2.2010, 14:50

Попробовал, ничего не вышло... Вот код:

while( tmpLength < file.size() )
    {
        if( file.size() - tmpLength > blockLength )
            tmpBlock = blockLength;
        else
            tmpBlock = file.size() - tmpLength;

        char * dataBlock = new char[ tmpBlock ];

        file.read( dataBlock, tmpBlock );
        
        char * tmpPointer;
        int tmpSize = tcpSocket->write( dataBlock, tmpBlock );
        QCoreApplication::processEvents();
        while( tmpSize < tmpBlock )
        {
            tmpPointer = dataBlock + tmpSize;
            tmpSize += tcpSocket->write( tmpPointer, tmpBlock - tmpSize );
            QCoreApplication::processEvents();
        }

        delete []dataBlock;

        tmpLength += tmpBlock;
    }


Результат, как я и говорил, плачевный - в сокет НИЧЕГО не передается, вообще ничего... Без flush() упорно отказывается отсылать

Автор: BRE 17.2.2010, 16:29

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


 network.tar.gz ( 1.58 килобайт ) : 348
 

Автор: OrSOn 17.2.2010, 17:12

Спасибо, сейчас гляну

Что-то я не совсем понял...

int writed = write( buf ); - грубо говоря, записали и проверили, сколько именно записалось.. Но ведь нету проверки, что записалось ВСЕ, вдруг часть не записалась... Просто проблема в том, что размер блока может быть переменным и если сделать его, скажем, пару мегабайт, все может сломаться...

Автор: BRE 17.2.2010, 17:23

Цитата(OrSOn @ 17.2.2010, 17:12) *
int writed = write( buf ); - грубо говоря, записали и проверили, сколько именно записалось.. Но ведь нету проверки, что записалось ВСЕ, вдруг часть не записалась... Просто проблема в том, что размер блока может быть переменным и если сделать его, скажем, пару мегабайт, все может сломаться...

Данные попали в буфер отправки tcp-стека, дальше это проблема стека. Если какой-то пакет потеряется, то сам протокол пере-пошлет его еще раз.
Для подтверждения же доставки всех данных, другая сторона (твой клиент) может отправить пакет твоему серверу, что бы он "успокоился".

Насчет моего кода.... Я там упростился, хотя так делать не совсем правильно. Я отправляю размер файла (переменную int) как массив char. Если архитектура на принимающей стороне будет отличаться типом хранения (byte order), то размер будет неверным. Поэтому для всех подобных передач, желательно использовать QDataStream.

Автор: OrSOn 18.2.2010, 13:15

Попробовал, но, увы, этот вариант врядли прокатит( Получается, что сначала ВЕСЬ файл записывается в сокет, а только потом клиент начинает его принимать.. А в том и проблема, именно потому и было решено отправлять файлы блоками, что в случае, если на компьютере гигабайт оперативки, а файл больше гигабайта, то он просто не уместится в память и его нельзя будет даже считать... Как и тут передать...

Автор: BRE 18.2.2010, 14:33

Цитата(OrSOn @ 18.2.2010, 13:15) *
Получается, что сначала ВЕСЬ файл записывается в сокет, а только потом клиент начинает его принимать.

Это почему так получается??? ;)
Клиент параллельно забирает данные, по мере их получения.

Автор: OrSOn 18.2.2010, 14:35

ну не знаю, я запускал в дебаге твой пример, так там клиент висел "без данных", пока сервер не переслал все полностью..

Автор: BRE 18.2.2010, 18:53

Цитата(OrSOn @ 18.2.2010, 14:35) *
ну не знаю, я запускал в дебаге твой пример, так там клиент висел "без данных", пока сервер не переслал все полностью..

Ну да, ну да.
Я там выше писал про цикл обработки событий... А у себя в примере добавить забыл. ;)
Добавь в цикл отправки сервера:
QCoreApplication::processEvents()
и все заработает.

Или уж делать полноценную асинхронную отправку, тоже сложного ничего нет.

Автор: OrSOn 19.2.2010, 11:50

Да, что-то я тоже не заметил, спасибо, так работает)) Другое дело, что полный перенос сего кода в мой проект с целью проверки ни к чему не привел... Запускаю, а клиенту ничего не приходит (пока не напишу flush()). Причем если после вызова write() написать waitForBytesWritten(), то клиент получает сигнал readyRead(), но ему приходит только маленький кусок информации,а не все сразу. Может это быть связано с тем, что сервер передает через QThread со своим циклом событий? Увы, прислать файл не могу, ибо ограничения инета, закрыто добавление файлов. Но поток сделан так:

В конструкторе

moveToThread( this );

А в функции run() одна функция exec() для создания собственного цикла. Далее работа потока идет чисто по сигналам...

Автор: BRE 19.2.2010, 11:59

Покажи подробно (закопипасть сюда) класс этого потока, скорее всего дело в нем.
Помимо переноса самого объекта потока в контекст этого потока, нужно переносить и все объекты, которые будут работать внутри него.

Автор: OrSOn 19.2.2010, 12:15

Ну все будет сложно сюда перенести, ибо много, я ключевые вещи вставлю...

Раскрывающийся текст

Объявление класса

#ifndef NTHREAD_H
#define NTHREAD_H

#include <QThread>
#include <QTcpSocket>
#include <QTimer>


struct PrioritySocket {
    QTcpSocket * tSocket;
    int prior;
};

struct HashStruct {
    QByteArray hashArr;
    QString filePath;
};

class NThread : public QThread
{
    Q_OBJECT

public:
    NThread( int num );

    void run();
    void readBD(QString & DBName,QString & DBHost, QString & DBUser,QString &  DBPassword,QString & DBDriver);
    bool isFree();
    void unlockDB( QString bName );
    void setIsFree( bool isFree );
    void stopCurrentWork();
    void updateHashMap();
    int myNum;

signals:
    void error(QTcpSocket::SocketError socketError);
    void needDB( QString bName, int threadName );
    void endThreadSocket( PrioritySocket *pSocket );
    void unlockBase( QString bName );
    void readF();

private:
    QMap<QString, HashStruct> hashMap;
    QString neededDB;
    bool dbLocked;
    QTcpSocket *tcpSocket;
    int socketDescriptor;
    QString text;
    bool HasBD;
    int CountFile;
    QString KARTADRIVER;
    QString KARTAHOST;
    QString KARTAGISDATA;
    QString KARTALISTS;
    quint32 blockSize;
    bool threadEnd;
    bool freeThread;
    PrioritySocket *priorSocket;
    bool setSocketFlag;

    int blockLength;

    void setThreadSocketFn();

public slots:
    void readFortune();
    void endThread();
    void setThreadSocket( PrioritySocket *pSocket );
    void setIsFree();
};

#endif


конструктор
NThread::NThread( int num )
: QThread(0)
{
                  myNum = num;
    moveToThread( this );
    setBaseValues();

}


Run
void NThread::run()
{
    connect( this, SIGNAL( readF() ), this, SLOT( readFortune() ), Qt::QueuedConnection );
    exec();
}


Функция установки текущего рабочего сокета на поток (сокет создается в основном потоке и передается на обратоку в один из потоков)
void NThread::setThreadSocket( PrioritySocket *pSocket )
{
    priorSocket = pSocket;    
    tcpSocket = priorSocket->tSocket;

    connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(setIsFree()), Qt::DirectConnection);

    emit readF();
    connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(readFortune()), Qt::QueuedConnection);
}


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

void NThread::setIsFree()
{
    disconnect(tcpSocket, SIGNAL(disconnected()), this, SLOT(setIsFree()));
    priorSocket->prior = -1;
    emit endThreadSocket( priorSocket ); //Сигнал основному потоку, что данный сокет завершил текущую работу, удалить, если связь прервалась или поставить в очередь, если есть еще запросы с него.
    freeThread = true;
    
}



Вот примерно такая структура...

Автор: SABROG 19.2.2010, 12:25

connect( this, SIGNAL( readF() ), this, SLOT( readFortune() ), Qt::QueuedConnection );


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

connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(setIsFree()), Qt::DirectConnection);


Почему выбран именно Qt::DirectConnection? Сокет принадлежит тому же потоку, что и объект QThread?

Автор: OrSOn 19.2.2010, 12:31

Сокет принадлежит главному потоку, объект NThread тоже создается в главном потоке, но без родителя. DirectConnection() выбрано, потому что так если соединение рвется, то я НЕМЕДЛЕННО переношусь в обработку разрыва, а если оставить в очереди, то буду пытаться работать с разорванным сокетом, что приведет к ошибке и потере времени...

Автор: SABROG 19.2.2010, 13:01

Это не правильно. Соединение не должно быть прямым между двумя потоками.

Цитата
а если оставить в очереди, то буду пытаться работать с разорванным сокетом, что приведет к ошибке и потере времени...


//пытаемся читать или писать

if (socket->isValid() && socket()->state() == QAbstractSocket::ConnectingState) {
    //пишем или читаем
}


Не спасет? Потеря времени будет только если размер твоего блока 1 байт.

Если есть сомнения насчет валидности указателя socket, то его можно обернуть в QWeakPointer:

//QWeakPointer<QTcpSocket> socket;
if (socket) {
    QSharedPointer<QTcpSocket> mysock = socket->toStrongRef();
    if (mysock) {
        // чтение, запись
    }
}

Автор: OrSOn 19.2.2010, 13:02

У меня и так эта проверка стоит, согласен, можно убрать... Но проблему это не решает((( Все равно ничего не меняется..

Автор: BRE 19.2.2010, 13:12

Сокет принадлежит главному потоку, а должен принадлежать потоку, который его обслуживает.
А вообще там столько всего наверчено. Для чего?
Нужно выполнить какое-то действие - создал поток, настроил его и запустил... Он отработал, забрал у него результат, убил.
Уверен, что если распишешь задачу, можно будет все упростить в несколько раз...

Автор: SABROG 19.2.2010, 13:19

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

Если ты не хочешь переносить сокет в тот же поток, который его обслуживает, то убедись, что обслуживающий поток эмитит сигнал о начале записи каких-либо данных и продолжает своё выполнение в то время, когда основной поток что-то пишет в сокет. Если ты все-таки хочешь вызывать read/write в обслуживающем потоке, то сокет должен принадлежать ему. Просто запомни, что любое взаимодействие между двумя объектами находящимися в разных потоках должно происходить через сигналы или события. Не должно быть никаких прямых вызовов методов одного объекта у другого. Конечно ничего не случиться, если метод threadsafe, но таковыми являются не все.

Автор: OrSOn 19.2.2010, 13:26

Изначально так и планировалось, чтобы на 1 запрос 1 поток... Но суть в чем. Имеется система для передачи больших объемов информации (обновления данных по сети). Причем данные могут быть разнотипными (с разными приоритетами). В итоге имеем большое число возможных одновременных подключений. Если создавать отдельные потоки для каждого соединения, то их может стать слишком много и передача "умрет", будет разом обновляться у всех, но ОЧЕНЬ медленно (не из-за сети, так из-за скорости чтения с жесткого диска). Поэтому было принято решение создать заранее ограниченное количество потоков, а все запросы от клиентов распределять между ними, но не просто так, а на передачу 1 файла, т.е. клиент подключился и попал в очередь. Дошла очередь до него - сокет отправлен в поток, передается ОДИН файл и сокет попадает обратно в очередь, а потоку передают следующий. Так сделано из-за приоритетов... Если давать возможность загружать сразу все файлы, то просто приоритетные запросы файлов, необходимых срочно, будут ждать очень долго... А если создавать по одному потоку на передачу одного файла одному клиенту - это будет глупо, по-моему..

З.Ы. нагорожено там из-за некоторых дополнительных требований, они тут ни при чем)))

Цитата(SABROG @ 19.2.2010, 13:19) *
Смотри что получается. Сокет находится в гуишном потоке, его обслуживает отдельный поток, это означает, что когда ты вызываешь метод write() ты напрямую обращаешься к методу объекта. В результате ты пытаешься записать данные из одного потока в другой напрямую.

Если ты не хочешь переносить сокет в тот же поток, который его обслуживает, то убедись, что обслуживающий поток эмитит сигнал о начале записи каких-либо данных и продолжает своё выполнение в то время, когда основной поток что-то пишет в сокет. Если ты все-таки хочешь вызывать read/write в обслуживающем потоке, то сокет должен принадлежать ему. Просто запомни, что любое взаимодействие между двумя объектами находящимися в разных потоках должно происходить через сигналы или события. Не должно быть никаких прямых вызовов методов одного объекта у другого. Конечно ничего не случиться, если метод threadsafe, но таковыми являются не все.


Я не могу отдавать сокет обрабатывающему потоку, ибо потом он возвращается в главный и может быть передан затем другим... А вот насчет передачи сигналов - если так получится, то будет круто, надо попробовать)))

Автор: SABROG 19.2.2010, 13:45

Задача потока выполнять долгую операцию на фоне позволяя работать пользователю без блокировки интерфейса. Если сокет будет находится в главном потоке да еще и write() использовать, то какой смысл в отдельном потоке? write() и read() неблокирующие методы и возвращаются моментально.

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

Если в твоем варианте такая блокирующая функция - чтение файла с диска в буффер, то файл должен читаться в отдельном потоке, формировать буффер с данными, а потом сообщать (эмитить) в основной поток, что данные готовы, типа забирай.

Автор: OrSOn 19.2.2010, 13:48

Вот я и думал об этом как раз, чтобы обрабатывающие потоки просто главному пересылали массивы информации, считанной из файла, а он уже отсылал...

Автор: BRE 19.2.2010, 14:03

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

Возможно, все равно придется использовать пул потоков. В Qt уже есть их реализация, посмотри на QThreadPool и QRunnable.

Автор: OrSOn 19.2.2010, 14:05

Ок, спасибо, буду ждать)

Автор: SABROG 19.2.2010, 14:37

Думаю вместо того, чтобы данные передавать с сигналом можно использовать кольцевой буффер (circular buffer, ring buffer). Можно посмотреть как он реализован в Wait Conditions Example.

Автор: BRE 19.2.2010, 15:06

Цитата(SABROG @ 19.2.2010, 14:37) *
Думаю вместо того, чтобы данные передавать с сигналом можно использовать кольцевой буффер (circular buffer, ring buffer). Можно посмотреть как он реализован в Wait Conditions Example.

А если принимающая сторона (клиент) сидит на узком канале и не успевает выбирать данные, которые ему пихают.

OrSOn
Вот новый архив, асинхронный сервер лежит в server-async. Для работы используется QSocketNotifier.

 network.tar.gz ( 2.43 килобайт ) : 294
 

Автор: SABROG 19.2.2010, 16:19

Цитата(BRE @ 19.2.2010, 15:06) *
А если принимающая сторона (клиент) сидит на узком канале и не успевает выбирать данные, которые ему пихают.


В общем QIODevice и так использует QRingBuffer (internal class), поэтому можно просто завести буфер на базе QByteArray для каждого сокета, ловить сигнал bytesWritten() и просить (emit signal) поток подгрузить новую порцию данных, если клиенту были отправлены все данные. Поток сам очистит буффер и поместит следующий блок, затем оповестит (emit signal) об этом сокет. Сокет уже их будет писать, а поток продолжит читать данные из файлов для других сокетов и эмитить им сигналы как прочитает.

Автор: BRE 19.2.2010, 16:53

Цитата(SABROG @ 19.2.2010, 16:19) *
В общем QIODevice и так использует QRingBuffer (internal class), поэтому можно просто завести буфер на базе QByteArray для каждого сокета, ловить сигнал bytesWritten() и просить (emit signal) поток подгрузить новую порцию данных, если клиенту были отправлены все данные. Поток сам очистит буффер и поместит следующий блок, затем оповестит (emit signal) об этом сокет. Сокет уже их будет писать, а поток продолжит читать данные из файлов для других сокетов и эмитить им сигналы как прочитает.

Я немного про другое... Qt-сокеты кешируют отправляемые данные. Если же из-за каких-то проблем с сетью или низкой скорости работы принимающей стороны, реально данные по сети не уходят (буфер отправки tcp-стека переполнен), то они будут скапливаться в памяти. А сервер вместо того, что бы заниматься теми клиентами, которые готовы принимать данные, будет периодически загружать все новые куски для "тормозящего" клиента и складывать их в памяти.

Хотя... Если bytesWritten отправляется только после реальной отправки данных, то все будет нормально. Полез смотреть. :)

Да, вместо QSocketNotifier можно использовать сигнал bytesWritten().

Автор: OrSOn 24.2.2010, 10:30

Спасибо, сейчас буду пробовать все посоветованное..))

Автор: OrSOn 24.2.2010, 16:10

А такой вопрос еще... А можно ли рассматривать как вариант такой подход:

Сокеты создаются по-прежнему в главном потоке, но не имеют родителя. А когда нам надо отдать сокет на обработку, мы ему задаем moveToThread() в нужный поток, а затем также отправляем его в главный поток? По-идее, должно сработать, но что-то я не уверен в правильности такой работы. Подскажите плиз, есть шанс на успех или лучше так не делать?

Автор: SABROG 25.2.2010, 2:43

Цитата(OrSOn @ 24.2.2010, 16:10) *
а затем также отправляем его в главный поток?


Поместив однажды объект в другой поток ты его уже обратно не сможешь вернуть в первоначальный.

Цитата(OrSOn @ 24.2.2010, 16:10) *
Сокеты создаются по-прежнему в главном потоке, но не имеют родителя.


Сокеты возвращенные из метода QTcpServer::nextPendingConnection() являются детьми QTcpServer'a, стало быть и родитель у них есть.

Автор: OrSOn 25.2.2010, 9:47

А почему это я не смогу вернуть их в главный поток? А насчет родителя - всегда можно сделать setParent().

Попробовал только что, все работает вроде. Но вопрос остается открытым, могут ли быть какие-то проблемы в этой работе? Все же, я очень сомневаюсь, что разработчики Qt предусматривали использование moveToThread() для постоянной переброски объекта между потоками..

Автор: BRE 25.2.2010, 12:14

Пока я вижу один момент с перетягиванием объектов между потоками.
Qt на основании информации о расположении объекта принимает решение о том, какой тип подключения использовать при connect. Если при перемещении объекта разрывать все коннекты, а при назначении в новый поток подключать их вновь, то возможно все будет работать.
Попробуй поэкспериментировать с этим.

Автор: OrSOn 25.2.2010, 12:44

Увы, при перемещении я не могу рвать коннект, в том и суть, что надо передавать подключенным) Сейчас так и сделал, пока все работает, ошибок вызвать не получилось еще... Может и прокатит..

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