Russian Qt Forum

Qt => Работа с сетью => Тема начата: Fregloin от Апрель 28, 2011, 14:48



Название: Чтение с сокета без QDataStream
Отправлено: Fregloin от Апрель 28, 2011, 14:48
Как читать с сокета данные без QDataStream (который используется во всех примерах).
Есть сервер (написан на нативных сокетах под QNX), пишу клиент под Win/Lin под Qt.
Нужно получать с сервера пакеты (пакеты могут иметь разную длину).
Пакет представляет из себя несколько массивов собранных в кучу друг за другом (типы quin32, структуры и т.п.).
при сигнале readyRead() получаю только часть данных (меньше чем нужно в bytesAvailable()).
при socket->read() я так понимаю внутренний буфер очищается на колчиество считанных байт.
Как грамотно реализовать чтение?

я пока делаю так:
Код:
void     QClient::readyRead()
{
    int readBytes = fsocket->bytesAvailable();
    if(readBytes<sizeof(scb_msg_t)) return; //если меньше данных чем заголовок игнорируем

    fsocket->read((char*)frawBuffer,sizeof(scb_msg_t));
    if(fframeBuffer.msg_head->size)
    {
        freadBytes = 0;
        while((readBytes = fsocket->bytesAvailable())!=0)
        {
            fsocket->read((char*)frawBuffer[freadBytes],readBytes);
            freadBytes+=readBytes;
        }
    }
...
}


Название: Re: Чтение с сокета без QDataStream
Отправлено: RedDog от Апрель 28, 2011, 15:19

Код:
QByteArray arr; // где нидь глобально (всмысле как закрытый член класса)
на readyDead:

Код:
arr.append(socket->readAll());


Название: Re: Чтение с сокета без QDataStream
Отправлено: Fregloin от Апрель 28, 2011, 15:22
а без QByteArray?

вот текущий пример моей реализации
Код:
if(fblockSize==0)
    {
        if(fsocket->bytesAvailable()<sizeof(scb_msg_t)) return;
        fblockSize = fsocket->read(frawBuffer,sizeof(scb_msg_t));
        if(fframeHeader.size==0)
        {
            fblockSize = 0;
            return;
        }
//        qDebug("block %d\n, header.size %d",fblockSize,fframeBuffer.msg_head->size);
    }

    fblockSize=fsocket->bytesAvailable();
    fleftBytes = fframeBuffer.msg_head->size-sizeof(scb_msg_t);

    if(fblockSize>=(fleftBytes))
    {
        fblockSize = fsocket->read(&frawBuffer[sizeof(scb_msg_t)],fleftBytes);
//        qDebug("fblockSize %d\n",fblockSize);
        fblockSize = 0;
    }


Название: Re: Чтение с сокета без QDataStream
Отправлено: ilyagoo от Май 01, 2011, 19:30
ну да, qint64 QIODevice::read ( char * data, qint64 maxSize )


Название: Re: Чтение с сокета без QDataStream
Отправлено: Fregloin от Июнь 29, 2011, 11:56
Вот что у меня получилось.
Код:
void     QGenericArmClient::readyRead()
{
    if(fblockSize==0) //читаем заголовок пакета (остальное остается в буфере)
    {
        if(fsocket->bytesAvailable()<sizeof(scb_msg_t)) return; //если данных меньше чем заголовок - игнорируем
        freceived = fsocket->read(frawBuffer,sizeof(scb_msg_t)); //получаем заголовок
        if(fframeHeader->size==0) //если в заголовке стоит длина 0, игнорируем этот пакет
        {
            freceived = 0;
            return;
        }
    }

    int bytesAvail = fsocket->bytesAvailable(); //сколько еще в буфере данных?
    if(bytesAvail) //если что то есть
    {
        fblockSize = fsocket->read(&frawBuffer[freceived],bytesAvail); //читаем очередной блок данных со смещением на уже принятые
        freceived+=fblockSize; //обновляем колво принятых данных
    }

    if(freceived>=fframeHeader->size) //если данных достаточно
    {
        fmutex->lock();
        qMemCopy(fdestBuffer,frawBuffer,freceived); //копируем в результирующий буфер
        fmutex->unlock();
        emit    frameReceived(freceived); //даем сигнал о том что данные приняты
        freceived = 0; //обнуляем счетчик принятых данных
    }
}
Все работает, но такой вопрос, может ли быть такая ситуация, когда в следующем
Код:
 int bytesAvail = fsocket->bytesAvailable(); //сколько еще в буфере данных?
    if(bytesAvail) //если что то есть
    {
        fblockSize = fsocket->read(&frawBuffer[freceived],bytesAvail); //читаем очередной блок данных со смещением на уже принятые
        freceived+=fblockSize; //обновляем колво принятых данных
    }
может прийти часть следующего пакета?


Название: Re: Чтение с сокета без QDataStream
Отправлено: Fregloin от Июнь 29, 2011, 12:25
интересно, что некоторые пакеты не приходят, по карайней мере я не получаю сигнал readyRead, когда например в другой программе(которая работает по этому же протоколу но написанной под QNX на socket API) все приходит. В чем может быть проблема?
В данном случае, я ожидаю пакет длиной 48 байт. Ощущение ,что пакеты меньше или равные 48 байтам мне не доходят.


Название: Re: Чтение с сокета без QDataStream
Отправлено: kuzulis от Июнь 29, 2011, 12:38
Цитировать
может прийти часть следующего пакета?
Да, может.
Анализируй заголовок и т.п. через метод peek() к примеру, а потом,
если все проверки прошли, то читай через read().

И вообще, неизвестен твой протокол обмена. Что он из себя представляет?
т.к. алгоритм чтения/парсинга входящего пакета напрямую зависит от протокола.

Раз у тебя в пакете есть заголовок, то ИМХО, разбей архитектуру своего приложения по модели ISO/OSI например на два уровня:
1. Канальный
2. Уровень приложений.

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

Цитировать
В данном случае, я ожидаю пакет длиной 48 байт. Ощущение ,что пакеты меньше или равные 48 байтам мне не доходят.
Ну и что что 48, может придти в один раз 15 байт, а в другой - 80 байт.
Поэтому, нельзя, читая первые байты пакета, думать, что эти данные являются заголовком.. А может заголовок - где-то в середине пакета, а первые байты - это мусор, оставшийся от предыдущего пакета и т.п.

Вот тебе, для ознакомления ссылочка на паттерн при разработки сетевых (да и нетолько) приложений (как один из многих вариантов паттернов):
http://www.eventhelix.com/realtimemantra/patterncatalog/protocol_layer.htm


Название: Re: Чтение с сокета без QDataStream
Отправлено: Fregloin от Июнь 29, 2011, 13:59
Я так и не понял к чему то что по ссылке?
А наш протокол довольно протостой.
Есть заголовок в виде структуры, у которой несколько полей.
Код:
typedef struct
{
        uint32_t family; //Семейство
        uint32_t action; //Действие
        uint32_t size; //размер пакета
        time_t time;
        int32_t p1;
        int32_t p2;
        int32_t p3;
        int32_t p4;
        int32_t p5;
        int32_t p6;
        int32_t p7;
        int32_t p8;
}   scb_msg_t;// __attribute__ ((packed));
Как видно, в size заклдаывается длина пакета (заголовок + данные если есть за ним).
На клиенте мне нужно получить даголовок и данные если есть за ним.


Название: Re: Чтение с сокета без QDataStream
Отправлено: RedDog от Июнь 29, 2011, 14:12
Прям так структурой и передается?
А если разное выравнивание будет на сервер и клиенте?


Название: Re: Чтение с сокета без QDataStream
Отправлено: Fregloin от Июнь 29, 2011, 14:13
выравнивание мы привели к одинаковому значению.
например проблем на qnx клиенте не возникает, и все данные приходят как надо. но механизм приема там немного другой в силу специфики ОС.


Название: Re: Чтение с сокета без QDataStream
Отправлено: kuzulis от Июнь 29, 2011, 14:19
Цитировать
Я так и не понял к чему то что по ссылке?
Очень плохо.

Цитировать
Есть заголовок в виде структуры, у которой несколько полей.
...
Опять 25.
Ты сначала должен определить где же всё-таки находится заголовок в массиве тех байт, которые принял сокет.
Не факт, что первые sizeof(scb_msg_t) байт в буфере будут заголовком.
Иначе можешь оч сильно нарваться на неприятности...

Цитировать
На клиенте мне нужно получить даголовок и данные если есть за ним.
Получай.

Цитировать
выравнивание мы привели к одинаковому значению.
Выравнивание тут ни при чем.
 


Название: Re: Чтение с сокета без QDataStream
Отправлено: Fregloin от Июнь 29, 2011, 14:23
предложите ваш вариант?
сервер уже написан (писал не я) и работает.
т.е. в начало и конец заголовка положить сигнатуру?
потом в очередном peek её искать? если нашёл, тогда уже читать как выше изложено?


Название: Re: Чтение с сокета без QDataStream
Отправлено: Fregloin от Июнь 29, 2011, 14:27
дело в том, что многие данные из p1-p8 используются, поэтому вырезать заголовок не имеет смысла


Название: Re: Чтение с сокета без QDataStream
Отправлено: kuzulis от Июнь 29, 2011, 14:27
Цитировать
т.е. в начало и конец заголовка положить сигнатуру?
потом в очередном peek её искать? если нашёл, тогда уже читать как выше изложено?
аха.
ищем заголовок, смотрим сколько там указано байт данных после него, прыгаем туда, и ищем после данных конечную сигнатуру.
и если всё ок, то пакет вынимаем.

если же пакет не полный пришел, то ждем пока он наполнится, а потом уже анализируем

Цитировать
дело в том, что многие данные из p1-p8 используются, поэтому вырезать заголовок не имеет смысла
значит не вырезай

В общем, думай сам. Тебе виднее как у вас там что сделано.


Название: Re: Чтение с сокета без QDataStream
Отправлено: kuzulis от Июнь 29, 2011, 14:35
Цитировать
т.е. в начало и конец заголовка положить сигнатуру?
бррр...
и в заголовок сигнатуру и в конец пакета.

например: <0x68><len><len><0x68><data><0x16>

В общем, что-то типа кадров формата FT xxx сделай.
ИМХО, так будет надежнее чем то что есть сейчас.

Нечего придумывать новое, если все уже придумано за вас.

PS: можно еще и CRC впихнуть


Название: Re: Чтение с сокета без QDataStream
Отправлено: Fregloin от Июнь 29, 2011, 14:50
но ведь может возникнуть ситуация когда сигнатура начала/конца может оборваться и прийти (или не прийти хотя маловероятно в случае TCP) в другом пакете. как тогда быть?


Название: Re: Чтение с сокета без QDataStream
Отправлено: kuzulis от Июнь 29, 2011, 14:58
ждать пока придет


Название: Re: Чтение с сокета без QDataStream
Отправлено: Fregloin от Июнь 29, 2011, 15:20
вот кусок кода чтения клиента под QNX где все работает: (хотя без каких либо проверок пока)
Код:
void	*	thread_func(void * params)
{
net_client_t * client = (net_client_t *)params;

ssize_t      received;
ssize_t header_size = sizeof(scb_msg_t);
ssize_t waitsize;
uint32_t flag = MSG_PEEK;


while(1)    //endless loop
{
while(1)
{
flag = MSG_PEEK | MSG_WAITALL;
waitsize = header_size;
received = recv(client->socket_descriptor,client->read_buffer,waitsize,flag);
if(received > 0)
{
if(((scb_msg_t*)client->read_buffer)->size>client->read_buffer_size)
{
printf("%s:%d receive error: header->size[%d]>read_buffer_size[%d]!\n",
client->remote_host.c_str(),client->remote_port,
((scb_msg_t*)client->read_buffer)->size,client->read_buffer_size);
break;
}

flag = MSG_WAITALL;

if(((scb_msg_t*)client->read_buffer)->size==0)
{
printf("%s:%d receive error: header->size==0, ignoring\n",client->remote_host.c_str(),client->remote_port);
received = recv(client->socket_descriptor,client->read_buffer,header_size,flag);
continue;
}

waitsize = (ssize_t)((scb_msg_t*)client->read_buffer)->size;

received = recv(client->socket_descriptor,client->read_buffer,waitsize,flag);
if(received == waitsize)
{
if(client->on_data_received) client->on_data_received(client,client->read_buffer,received);
}
else break;
}
else break;
}

client_disconnect(client);
sleep(2);
client_connect(client);
}


    #if defined(__WINDOWS__)
    return  0;
    #else
    return NULL;
    #endif
}
В этом коде все пакеты приходят правильно и целиком...


Название: Re: Чтение с сокета без QDataStream
Отправлено: Fregloin от Июнь 29, 2011, 16:04
вот как лучше сделать?
я думаю, сначала пакета ставить сигнатуру, например 'PACKBGN',/0, потом uint16_t lenght, потом данные если есть, в конце 'PACKEND',/0.
может в будущем и CRC.
теперь я так понимаю, сначала делаю peek.
далее надо найти в памяти сигнатуру начала. потом получить размер оставшийся, и копировать в буфер данные с сокета пока не получу сигнатуру конца или размер принятых данных не будет равен размеру, который шел после сигнатуры начала.
так делать? а если сигнатура обрывается где то по середине?
тогда какого размера брать буфер чтения?


Название: Re: Чтение с сокета без QDataStream
Отправлено: Fregloin от Июнь 29, 2011, 16:30
QAbstractSocket::LowDelayOption - частично решил проблему, теперь все приходит нормально (хотя конечно проверку я введу, но когда приедет разработчик сервера).
теперь непонятная проблема - когда я шлю на сервер запрос, открыть файл, то в ответ должен прийти пакет данных (структура scb_msg_t без данных за ней). И вот клиент QNX ее получает, а в Qt сигнал readyRead() не получаю. В чем может быть загвостка?