Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Передача данных в отдельном потоке. Сервер не успевает обработать данные.
Форум на CrossPlatform.RU > Библиотеки > Qt > Qt Ввод/Вывод, Сеть. Межпроцессное взаимодействие
kibsoft
Есть сервер и клиент (отсылает данные, например файл):
Клиент:
void FileSender::sendFile()
{
    QFile file(filePath);
    if (file.open(QIODevice::ReadOnly)) {//если файл открылся
        QByteArray dataForPeer;//массив для данных
        QDataStream out(&dataForPeer, QIODevice::WriteOnly);//выходной поток
        QByteArray byteArray;//поток байтов из файла
        while(!file.atEnd()) {//пока не конец файла
             byteArray=file.read(1024);//читаем по килобайту
             out << quint16(0) << FILEBYTES << byteArray;
             out.device()->seek(0);
             out << quint16(dataForPeer.size() - sizeof(quint16));
             tcpSocket->write(dataForPeer);

             dataForPeer.clear();//очищаем массив для отправки серверу
        }
        //отправка уведомления об окончании передачи файла
        out << quint16(0) << SENDING_FINISHED;
        out.device()->seek(0);
        out << quint16(dataForPeer.size() - sizeof(quint16));
        tcpSocket->write(dataForPeer);

        file.close();//закрываем файл
    }
}


На сервере есть слот, соединенный с сигналом readyRead сокета, но видимо данные приходят так быстро, что сервер не успевает сделать нужные действия (а именно записать на диск). Что можно придумать? Можно ли как то на клиенте узнать считаны ли данные сервером или нет(и ждать этого момента)? В голову приходит только одно: от сервера высылать уведомление о принятых данных, чтобы клиент мог дальше передавать их (но мне кажется, что что-то не то).
BRE
Покажи как читаются данные на другой стороне.
kibsoft
/*
  Слот для обработки принятых из сокета данных.
*/
void FileWriter::readBytes()
{
    QTcpSocket *tcpSocket=(QTcpSocket*)sender();//получаем сокет
    QDataStream in(tcpSocket);
    quint16 nextBlockSize=0;//размер принятого блока
    for(;;) {
        if(!nextBlockSize) {
            if(tcpSocket->bytesAvailable()<sizeof(quint16)) break;//если данных принято меньше 2 байт, то выходим

            in>>nextBlockSize;//получаем размер блока
        }

        if(tcpSocket->bytesAvailable()<nextBlockSize) break;//если все данные получены, то продолжаем работать

        quint8 request;//получаем запрос
        in>>request;
        TypeOfRequest type=(TypeOfRequest)request;
        if (type==FILEBYTES) {//если данные - файл, то записываем их на жесткий диск
            writeBytes(in);
        }
        else if (type==SENDING_FINISHED) {//если пир окончил отправку файла, то отправляем ему
            sendFinishMessage(tcpSocket);//уведомление об окончании его принятия
        }
        nextBlockSize=0;
    }  
}

/*
  Функция записи принятых данных в файл. Параметры: поток данных из сокета(reference).
*/
void FileWriter::writeBytes(QDataStream &in)
{
    QByteArray bytes;
    in>>bytes;//получаем нужные байты
    QFile file("D:/ref.dat");
    if (file.open(QIODevice::Append)) {
        file.write(bytes);//записываем байты
        file.close();
    }
}


/*
  Функция отправки сообщения об окончании принятия файла. Параметры: принимающий сокет.
*/
void FileWriter::sendFinishMessage(QTcpSocket *tcpSocket)
{
    QByteArray dataForPeer;//массив для данных
    QDataStream out(&dataForPeer, QIODevice::WriteOnly);//выходной поток
    out << quint16(0) << quint8(RECEIVING_FINISHED);//соообщение об окончании
    out.device()->seek(0);
    out << quint16(dataForPeer.size() - sizeof(quint16));
    tcpSocket->write(dataForPeer);
}
BRE
Посмотри примеры еще раз и обрати внимание на то, как определена эта переменная:
void FileWriter::readBytes()
{
    ....
    quint16 nextBlockSize=0;//размер принятого блока
    ....

kibsoft
Цитата(BRE @ 13.4.2010, 21:49) *
Посмотри примеры еще раз и обрати внимание на то, как определена эта переменная:
void FileWriter::readBytes()
{
    ....
    quint16 nextBlockSize=0;//размер принятого блока
    ....

Спасибо, что подсказали. Объявил в хедере. Но все равно сервер не успевает записывать данные.
В консоль вывел количество раз запуска слота и записи данных. Передается файл размером 42Кб:
Цитата
1 //слот о принятии данных вызван
Writing: 1 //запись
Writing: 2
2
3
4
5
6

На самом деле, если я передаю по 1 Кб, то дожно быть около 42 вызовов.. Или я что-то не так думаю?
BRE
Может быть не так, алгоритм Нагла думаю включен, поэтому TCP-стек скорее всего пакеты объединяет.
kibsoft
Цитата(BRE @ 13.4.2010, 22:05) *
Может быть не так, алгоритм Нагла думаю включен, поэтому TCP-стек скорее всего пакеты объединяет.


Щас продебажил, на сервере (в слоте обработки) сокет действительно собирает пакеты не по 1024 байта, как отсылаются, а иногда по 30Кб даже, поэтому мой обработчик естественно не может определить по флагам(FILEBYTES) нужно ли записывать инфу. Как можно этого избежать? Если отключить алгоритм Нагла, то будет в разы медленней? И вообще можно ли его отключить? И вообще есть у кого-то какие-нибудь предложения?
BRE
А не нужно отключать алгоритм Нагла, нужно придерживаться при чтении своего же протокола.
Ты пишешь по 1K, а при чтении читаешь все, со всеми управляющими командами. А нужно читать так же как пишешь по 1K.
kibsoft
Цитата(BRE @ 14.4.2010, 7:49) *
А не нужно отключать алгоритм Нагла, нужно придерживаться при чтении своего же протокола.
Ты пишешь по 1K, а при чтении читаешь все, со всеми управляющими командами. А нужно читать так же как пишешь по 1K.

Т.е. не использовать QDataStream, а использовать read у объекта сокета? Не совсем понимаю как это реализовать.
BRE
Проверь несколько раз, сколько байт (какими порциями) реально читает эта функция из сокета:
void FileWriter::writeBytes(QDataStream &in)
{
    QByteArray bytes;
    in>>bytes;

    qDebug() << "Read data size from socket:" << bytes.size();

    QFile file("D:/ref.dat");
    if (file.open(QIODevice::Append)) {
        file.write(bytes);//записываем байты
        file.close();
    }
}

kibsoft
Цитата(BRE @ 14.4.2010, 15:08) *
Проверь несколько раз, сколько байт (какими порциями) реально читает эта функция из сокета:
void FileWriter::writeBytes(QDataStream &in)
{
    QByteArray bytes;
    in>>bytes;

    qDebug() << "Read data size from socket:" << bytes.size();

    QFile file("D:/ref.dat");
    if (file.open(QIODevice::Append)) {
        file.write(bytes);//записываем байты
        file.close();
    }
}

Read data size from socket: 1024
Read data size from socket: 0
Файл 59 Кб.
Все рушится после 2 раза вызова слота обработки (readBytes), т.е. первый раз приходит как положено 1031 байт(вместе с nextBlockSize), при следующем вызове слота в nextBlockSize появляется 30 тысяч с лишним байт (размер следующего блока), потом if(tcpSocket->bytesAvailable()<nextBlockSize) break; тут пару раз происходит выход из слота, т.к.30 тысяч байт еще не пришли, а потом выходит косяк с этим:
quint8 request;//получаем запрос
        in>>request;
        TypeOfRequest type=(TypeOfRequest)request;
        if (type==FILEBYTES) {//если данные - файл, то записываем их на жесткий диск
            writeBytes(in);
        }
в reguest вместо 0 или 1 попадает 97 вроде.
BRE
А покажи как определены: FILEBYTES, SENDING_FINISHED
kibsoft
Цитата(BRE @ 14.4.2010, 15:34) *
А покажи как определены: FILEBYTES, SENDING_FINISHED

class FileWriter : public QObject
{
    Q_OBJECT
public:
    FileWriter(QObject *parent=0):QObject(parent),nextBlockSize(0) {};
public slots:
    void readBytes();//слот для чтения принятых данных

private:
    quint16 nextBlockSize;
    //тип запроса от клиента
    enum TypeOfRequest {FILEBYTES/*отправка частей файла*/,SENDING_FINISHED/*отправка файла завершена*/};
    //тип ответа от сервера
    enum TypeOfReply {RECEIVING_FINISHED/*окончание принятия файла*/};

    void writeBytes(QDataStream &in);//запись принятых байтов в файл
    void sendFinishMessage(QTcpSocket *tcpSocket);//отправка уведомления об окончании принятия файла
};
BRE
Попробуй сиправить отсылку:
void FileSender::sendFile()
{
    QFile file(filePath);
    if (file.open(QIODevice::ReadOnly)) {//если файл открылся
        QByteArray dataForPeer;//массив для данных
        QDataStream out(&dataForPeer, QIODevice::WriteOnly);//выходной поток
        QByteArray byteArray;//поток байтов из файла
        while(!file.atEnd()) {//пока не конец файла
             byteArray=file.read(1024);//читаем по килобайту
             out << quint16(0) << quint8( FILEBYTES ) << byteArray;        // <<<
             out.device()->seek(0);
             out << quint16(dataForPeer.size() - sizeof(quint16));
             tcpSocket->write(dataForPeer);

             dataForPeer.clear();//очищаем массив для отправки серверу
        }
        //отправка уведомления об окончании передачи файла
        out << quint16(0) << quint8( SENDING_FINISHED );        // <<<
        out.device()->seek(0);
        out << quint16(dataForPeer.size() - sizeof(quint16));
        tcpSocket->write(dataForPeer);

        file.close();//закрываем файл
    }
}
kibsoft
Да я вечером уже этот глюк нашел и исправил, но все равно неправильно работает :(


Вот исходники 3 классов: сервер, поток-приемщик и отправитель. Может поможет Вам разобраться точней..
BRE
Попробуй изменить так:
void FileSender::sendFile()
{
    QFile file(filePath);
    if (file.open(QIODevice::ReadOnly)) {//если файл открылся
        while(!file.atEnd()) {//пока не конец файла
             QByteArray byteArray=file.read(1024);//читаем по килобайту

             QByteArray dataForPeer;//массив для данных
             QDataStream out(&dataForPeer, QIODevice::WriteOnly);//выходной поток
             out << quint16(0) << FILEBYTES << byteArray;
             out.device()->seek(0);
             out << quint16(dataForPeer.size() - sizeof(quint16));
             tcpSocket->write(dataForPeer);
        }
        //отправка уведомления об окончании передачи файла
        QByteArray dataForPeer;//массив для данных
        QDataStream out(&dataForPeer, QIODevice::WriteOnly);//выходной поток
        out << quint16(0) << SENDING_FINISHED;
        out.device()->seek(0);
        out << quint16(dataForPeer.size() - sizeof(quint16));
        tcpSocket->write(dataForPeer);

        file.close();//закрываем файл
    }
}

kibsoft
Цитата
Read data size from socket: 0
Read data size from socket: 0

Щас попробую сделать эту же задачу, но в GUI-потоке, посмотрю будет ли работать..

P.S. У нас День Рождения одинаковый :)

http://rghost.ru/1381290 исходники
BRE
Блин, quint8 забыл добавить:
void FileSender::sendFile()
{
    QFile file(filePath);
    if (file.open(QIODevice::ReadOnly)) {//если файл открылся
        while(!file.atEnd()) {//пока не конец файла
             QByteArray byteArray=file.read(1024);//читаем по килобайту

             QByteArray dataForPeer;//массив для данных
             QDataStream out(&dataForPeer, QIODevice::WriteOnly);//выходной поток
             out << quint16(0) << quint8( FILEBYTES ) << byteArray;
             out.device()->seek(0);
             out << quint16(dataForPeer.size() - sizeof(quint16));
             tcpSocket->write(dataForPeer);
        }
        //отправка уведомления об окончании передачи файла
        QByteArray dataForPeer;//массив для данных
        QDataStream out(&dataForPeer, QIODevice::WriteOnly);//выходной поток
        out << quint16(0) << quint8( SENDING_FINISHED );
        out.device()->seek(0);
        out << quint16(dataForPeer.size() - sizeof(quint16));
        tcpSocket->write(dataForPeer);

        file.close();//закрываем файл
    }
}


P.S. Ты бы проект сбросил, что бы его собрать можно было. А то гадать уже надоело. :)
kibsoft
Вы уже это писали :) Я же сказал, что еще вчера это исправил, но все равно не работает. У меня с quint8 сейчас.
kibsoft
BRE, спасибо за разные подсказки! Сейчас посидел, подумал - неправильно вообще я алгоритм организовал, понял в чем дело, буду перерабатывать. Загвоздка как раз в типе запроса(request).
kibsoft
QFile file(filePath);
    if (file.open(QIODevice::ReadOnly)) {//если файл открылся
        char b[1025];
        char *bytes=b;

        while(!file.atEnd()) {//пока не конец файла
           file.read(bytes,1024);//читаем по килобайту
            tcpSocket->write(bytes);
        }

        file.close();//закрываем файл
    }


Сделал, чтобы просто байты отсылались без всяких структур типа QByteArray, но в сокет(tcpSocket->write(bytes);) пишется меньше данных чем было прочитано в bytes.. ПРобовал flush() - не помогает, из-за чего такое может быть?
BRE
Посмотри эту тему, там я выкладывал архивы с тестовым проектом, передающим файл:
http://www.forum.crossplatform.ru/index.ph...c=4296&st=0
kibsoft
Цитата(BRE @ 16.4.2010, 8:07) *
Посмотри эту тему, там я выкладывал архивы с тестовым проектом, передающим файл:
http://www.forum.crossplatform.ru/index.ph...c=4296&st=0

Спасибо! Под себя немного переделал, все работает! И последний вопрос, но не о программировании: какими частями лучше передавать файл (сколько байт), если ориентироваться на LAN-сеть? Или 64Кб нормально как в вашем коде?
BRE
Цитата(kibsoft @ 16.4.2010, 20:14) *
Спасибо! Под себя немного переделал, все работает! И последний вопрос, но не о программировании: какими частями лучше передавать файл (сколько байт), если ориентироваться на LAN-сеть? Или 64Кб нормально как в вашем коде?

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

Понятно.
Цитата
Смысл чтения по частям больше в том, что бы не загружать большие файлы в память сразу.

Да, это я знал :)

Нарушу правила, не буду создавать новую тему:
connect(fileSender,SIGNAL(finished()),fileSender,SLOT(deleteLater())); //fileSender это поток

В run() я запускаю цикл событий exec(), но явно сам его не завершаю, но все-таки после передачи файла объект(fileSender) сам уничтожается (вывел на консоль из деструктора). Это мне не мешает, но я просто не пойму почему он завершается?
Litkevich Yuriy
Цитата(kibsoft @ 16.4.2010, 23:28) *
после передачи файла
может это событие где-то останавливает поток.
(коль скоро ты используешь чужой код, может ты его не полностью изучил)
BRE
Без кода я затрудняюсь ответить.
kibsoft
Цитата(Litkevich Yuriy @ 16.4.2010, 20:37) *
Цитата(kibsoft @ 16.4.2010, 23:28) *
после передачи файла
может это событие где-то останавливает поток.
(коль скоро ты используешь чужой код, может ты его не полностью изучил)


:lol: сам несколько минут назад соединил обрыв сокета со слотом quit() и забыл..извиняюсь
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Форум IP.Board © 2001-2022 IPS, Inc.