Всем добрый день! Столкнулся с довольно неприятной проблемой при работе с сокетами... Суть проблемы в следующем:
Имеется сервер и клиент, для общения используют сокеты. В некий момент времени клиент рушится (ну или связь падает, не суть важно), при этом сервер пытается что-то писать в сокет, что приводит к ошибке. Чтобы избежать этого, пробовал ввести проверку
if( tcpSocket->state() != 3 )
return;
tcpSocket->flush();
так в чем проблема? в том что сервер не знает записаны данные или нет?
Обработать ошибку нельзя - полностью прерывается дебаг и вылетаю, ошибка получается в том, что я пытаюсь записать информацию в сокет, который имеет состояние disconnected(), причем это состояние он получает в процессе выполнения команды записи, т.е. заранее это не отследить. Грубо говоря, получается такая ситуация:
1. Сокет подключен, начинается запись (запускается стандартная функция Qt)
2. Прямо во время записи рвется соединение
3. Половина функции прошла, а во второй оказывается, что соединения нет и вылетает ошибка...
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( 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;
}
Покажи как создается объект tcpSocket и какие сигналы обрабатываются.
Ты уверен, что этот объект не разрушается где-то еще (вижу там какой-то freeThread)?
Все эти проверки не нужны, ошибка где-то в другом месте.
У тебя течет память для буфера dataBlock, при любой ошибки - она не удаляется.
Зачем цикл у flush?
Значит по порядку...
Объект создается нормально, но в другом классе (раньше все работало, пока я не стал разбивать файлы на блоки, т.е. когда файл отсылал целиком, никаких ошибок не было). Суть в следующем: есть управляющий класс,в нем QTcpServer. Когда поступает запрос подключения, создается сокет, затем он добавляется в очередь. А далее из очереди сокеты передаются в заранее созданные потоки на обработку, после чего возвращаются обратно в очередь.
Насчет не освобождается память - я в курсе, это не страшно в данном случае (ошибки от этого не возникнет), потом устраню, когда разберусь, где все рушится.
Цикл сделан тоже неспроста... Раньше было без него, но столкнулся с некоторой проблемой - длинные файлы просто не передавались целиком, т.е. часть файла принималась с клиентской стороны, а потом сигналы readyRead() просто переставали приходить, т.е. каким-то образом остатки файла не проталкивались, хотя сервер и уходил дальше в eventLoop... Да и не в этом проблема, он рушится даже на первом вызове... Вот и не могу понять, почему при неизменном коде все работало при отправке файлов целиком и стало рушится при разбиении на блоки...
И кстати, проверки, все же, нужны, но несколько для других целей - чтобы остановить выполнение функции после разрыва соединения, иначе будет путаница с данными, если этот сокет разорвался, а потоку дали другой на обработку... И новый сокет "завершит" действия, которые предназначались не ему.
Класс TcpSocket данные кеширует сам и отправляет их блоками. Происходит это в цикле обработки событий.
Ты не показал код..., но мне кажется, что при разрыве соединения сам объект на который указывает tcpSocket разрушается (например в другом потоке), и ты пытаешься использовать уже разрушенный объект.
Метод write возвращает размер реально записанных данных, не всегда все данные могут поместиться в буфер отправки TCP-стека (хотя Qt и кеширует их сам). Эту ситуацию так же нужно обрабатывать.
Увы, но удаления объекта не происходит. Я уже думал об этом, поэтому тупо закомментировал ВСЕ места, где происходит удаление - результата нет. А насчет обработки записи, 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 );
Ну так если ты хочешь отправлять синхронно, то попробуй вместо flush крутить цикл обработки событий.
Боюсь, что так будет не лучше, ибо сложнее отследить, когда данные передались полностью... Понять бы, как тут избежать вылетов..
Как только ты записал последний кусок данных в сокет и метод write это подтвердил.
Дальше забота Qt.
Попробовал, ничего не вышло... Вот код:
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;
}
Набросал небольшой пример (клиент/сервер) для передачи больших файлов. Вроде все работает, даже если клиент посылает запрос и выходит, не дожидаясь чтения файла.
В файле сервера укажи имя существующего большого файла.
Спасибо, сейчас гляну
Что-то я не совсем понял...
int writed = write( buf ); - грубо говоря, записали и проверили, сколько именно записалось.. Но ведь нету проверки, что записалось ВСЕ, вдруг часть не записалась... Просто проблема в том, что размер блока может быть переменным и если сделать его, скажем, пару мегабайт, все может сломаться...
Попробовал, но, увы, этот вариант врядли прокатит( Получается, что сначала ВЕСЬ файл записывается в сокет, а только потом клиент начинает его принимать.. А в том и проблема, именно потому и было решено отправлять файлы блоками, что в случае, если на компьютере гигабайт оперативки, а файл больше гигабайта, то он просто не уместится в память и его нельзя будет даже считать... Как и тут передать...
ну не знаю, я запускал в дебаге твой пример, так там клиент висел "без данных", пока сервер не переслал все полностью..
Да, что-то я тоже не заметил, спасибо, так работает)) Другое дело, что полный перенос сего кода в мой проект с целью проверки ни к чему не привел... Запускаю, а клиенту ничего не приходит (пока не напишу flush()). Причем если после вызова write() написать waitForBytesWritten(), то клиент получает сигнал readyRead(), но ему приходит только маленький кусок информации,а не все сразу. Может это быть связано с тем, что сервер передает через QThread со своим циклом событий? Увы, прислать файл не могу, ибо ограничения инета, закрыто добавление файлов. Но поток сделан так:
В конструкторе
moveToThread( this );
А в функции run() одна функция exec() для создания собственного цикла. Далее работа потока идет чисто по сигналам...
Покажи подробно (закопипасть сюда) класс этого потока, скорее всего дело в нем.
Помимо переноса самого объекта потока в контекст этого потока, нужно переносить и все объекты, которые будут работать внутри него.
Ну все будет сложно сюда перенести, ибо много, я ключевые вещи вставлю...
#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();
}
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;
}
connect( this, SIGNAL( readF() ), this, SLOT( readFortune() ), Qt::QueuedConnection );
connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(setIsFree()), Qt::DirectConnection);
Сокет принадлежит главному потоку, объект NThread тоже создается в главном потоке, но без родителя. DirectConnection() выбрано, потому что так если соединение рвется, то я НЕМЕДЛЕННО переношусь в обработку разрыва, а если оставить в очереди, то буду пытаться работать с разорванным сокетом, что приведет к ошибке и потере времени...
Это не правильно. Соединение не должно быть прямым между двумя потоками.
//пытаемся читать или писать
if (socket->isValid() && socket()->state() == QAbstractSocket::ConnectingState) {
//пишем или читаем
}
//QWeakPointer<QTcpSocket> socket;
if (socket) {
QSharedPointer<QTcpSocket> mysock = socket->toStrongRef();
if (mysock) {
// чтение, запись
}
}
У меня и так эта проверка стоит, согласен, можно убрать... Но проблему это не решает((( Все равно ничего не меняется..
Сокет принадлежит главному потоку, а должен принадлежать потоку, который его обслуживает.
А вообще там столько всего наверчено. Для чего?
Нужно выполнить какое-то действие - создал поток, настроил его и запустил... Он отработал, забрал у него результат, убил.
Уверен, что если распишешь задачу, можно будет все упростить в несколько раз...
Смотри что получается. Сокет находится в гуишном потоке, его обслуживает отдельный поток, это означает, что когда ты вызываешь метод write() ты напрямую обращаешься к методу объекта. В результате ты пытаешься записать данные из одного потока в другой напрямую.
Если ты не хочешь переносить сокет в тот же поток, который его обслуживает, то убедись, что обслуживающий поток эмитит сигнал о начале записи каких-либо данных и продолжает своё выполнение в то время, когда основной поток что-то пишет в сокет. Если ты все-таки хочешь вызывать read/write в обслуживающем потоке, то сокет должен принадлежать ему. Просто запомни, что любое взаимодействие между двумя объектами находящимися в разных потоках должно происходить через сигналы или события. Не должно быть никаких прямых вызовов методов одного объекта у другого. Конечно ничего не случиться, если метод threadsafe, но таковыми являются не все.
Изначально так и планировалось, чтобы на 1 запрос 1 поток... Но суть в чем. Имеется система для передачи больших объемов информации (обновления данных по сети). Причем данные могут быть разнотипными (с разными приоритетами). В итоге имеем большое число возможных одновременных подключений. Если создавать отдельные потоки для каждого соединения, то их может стать слишком много и передача "умрет", будет разом обновляться у всех, но ОЧЕНЬ медленно (не из-за сети, так из-за скорости чтения с жесткого диска). Поэтому было принято решение создать заранее ограниченное количество потоков, а все запросы от клиентов распределять между ними, но не просто так, а на передачу 1 файла, т.е. клиент подключился и попал в очередь. Дошла очередь до него - сокет отправлен в поток, передается ОДИН файл и сокет попадает обратно в очередь, а потоку передают следующий. Так сделано из-за приоритетов... Если давать возможность загружать сразу все файлы, то просто приоритетные запросы файлов, необходимых срочно, будут ждать очень долго... А если создавать по одному потоку на передачу одного файла одному клиенту - это будет глупо, по-моему..
З.Ы. нагорожено там из-за некоторых дополнительных требований, они тут ни при чем)))
Задача потока выполнять долгую операцию на фоне позволяя работать пользователю без блокировки интерфейса. Если сокет будет находится в главном потоке да еще и write() использовать, то какой смысл в отдельном потоке? write() и read() неблокирующие методы и возвращаются моментально.
Предположим, что у нас иная ситуация. На каждого клиента запускается блокирующая функция, которая производит долгие вычисления, а потом результат шлет в сокет. В этом случае нам уже нужны отдельные потоки. Задача клиента сказать потоку "посчитай мне что-то в отдельном потоке, а потом верни результат в главный поток, чтобы я мог их передать".
Если в твоем варианте такая блокирующая функция - чтение файла с диска в буффер, то файл должен читаться в отдельном потоке, формировать буффер с данными, а потом сообщать (эмитить) в основной поток, что данные готовы, типа забирай.
Вот я и думал об этом как раз, чтобы обрабатывающие потоки просто главному пересылали массивы информации, считанной из файла, а он уже отсылал...
OrSOn, я скоро приеду домой и сброшу тебе пример асинхронной передачи. Т.е. в момент когда сокет сможет принять данные будет генерировать сигнал, в слоте которого будет отправляться небольшой блок данных с последующем выходом из слота. При следующем проходе, опять сигнал, отправка, выход... Пока все данные не будут переданы.
Возможно, все равно придется использовать пул потоков. В Qt уже есть их реализация, посмотри на QThreadPool и QRunnable.
Ок, спасибо, буду ждать)
Думаю вместо того, чтобы данные передавать с сигналом можно использовать кольцевой буффер (circular buffer, ring buffer). Можно посмотреть как он реализован в Wait Conditions Example.
Спасибо, сейчас буду пробовать все посоветованное..))
А такой вопрос еще... А можно ли рассматривать как вариант такой подход:
Сокеты создаются по-прежнему в главном потоке, но не имеют родителя. А когда нам надо отдать сокет на обработку, мы ему задаем moveToThread() в нужный поток, а затем также отправляем его в главный поток? По-идее, должно сработать, но что-то я не уверен в правильности такой работы. Подскажите плиз, есть шанс на успех или лучше так не делать?
А почему это я не смогу вернуть их в главный поток? А насчет родителя - всегда можно сделать setParent().
Попробовал только что, все работает вроде. Но вопрос остается открытым, могут ли быть какие-то проблемы в этой работе? Все же, я очень сомневаюсь, что разработчики Qt предусматривали использование moveToThread() для постоянной переброски объекта между потоками..
Пока я вижу один момент с перетягиванием объектов между потоками.
Qt на основании информации о расположении объекта принимает решение о том, какой тип подключения использовать при connect. Если при перемещении объекта разрывать все коннекты, а при назначении в новый поток подключать их вновь, то возможно все будет работать.
Попробуй поэкспериментировать с этим.
Увы, при перемещении я не могу рвать коннект, в том и суть, что надо передавать подключенным) Сейчас так и сделал, пока все работает, ошибок вызвать не получилось еще... Может и прокатит..
Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)