Прочитать все доступные байты из TCP Socket (неизвестное количество байтов)

У меня проблемы с использованием Indy TIdTCPClient, Я хочу вызывать функцию каждый раз, когда есть данные, доступные на сокете. Для этого у меня есть тема, вызывающая IdTCPClient->Socket->Readable(100), Сама функция выглядит так:

TMemoryStream *mStream = new TMemoryStream;
int len = 0;
try
{
    if(!Form1->IdTCPClient2->Connected())
        Form1->IdTCPClient2->Connect();
    mStream->Position = 0;
    do
    {
        Form1->IdTCPClient2->Socket->ReadStream(mStream, 1);
    }
    while(Form1->IdTCPClient2->Socket->Readable(100));

    len = mStream->Position;
    mStream->Position = 0;
    mStream->Read(Buffer, len);
}catch(Exception &Ex) {
    Form1->DisplaySSH->Lines->Add(Ex.Message);
    Form1->DisplaySSH->GoToTextEnd();
}
delete mStream;

Он не будет вызываться непосредственно в потоке, но поток вызывает событие, которое вызывает эту функцию. Что означает, что я использую Readable(100) дважды, без чтения данных в Betwee. Так как я не знаю, сколько байтов мне нужно прочитать, я подумал, что могу прочитать один байт, проверить, есть ли еще доступный, а затем прочитать другой байт. Проблема здесь в том, что цикл do while не зацикливается, он просто запускается один раз. Я предполагаю, что Readable не совсем так, как мне нужно. Есть ли другой способ получить все байты, доступные в сокете?

1 ответ

Решение

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

Использовать TIdIOHandler::CheckForDataOnSource() метод вместо TIdIOHandler::Readable():

TMemoryStream *mStream = new TMemoryStream;
try
{
    if (!Form1->IdTCPClient2->Connected())
        Form1->IdTCPClient2->Connect();

    mStream->Position = 0;

    do
    {
        if (Form1->IdTCPClient2->IOHander->InputBufferIsEmpty())
        {
            if (!Form1->IdTCPClient2->IOHander->CheckForDataOnSource(100))
                break;
        }

        Form1->IdTCPClient2->IOHandler->ReadStream(mStream, Form1->IdTCPClient2->IOHandler->InputBuffer->Size, false);

        /* alternatively:
        Form1->IdTCPClient2->IOHandler->InputBuffer->ExtractToStream(mStream);
        */
    }
    while (true);

    // use mStream as needed...
}
catch (const Exception &Ex) {
    Form1->DisplaySSH->Lines->Add(Ex.Message);
    Form1->DisplaySSH->GoToTextEnd();
}
delete mStream;

Или вы можете использовать в качестве альтернативы TIdIOHandler::ReadBytes() вместо TIdIOHandler::ReadStream(), Если вы установите его AByteCount параметр для -1, он вернет только те байты, которые доступны в данный момент (если InputBuffer пустой, ReadBytes() будет ждать до ReadTimeout интервал для сокета, чтобы получить любые новые байты) 1:

try
{
    if (!Form1->IdTCPClient2->Connected())
        Form1->IdTCPClient2->Connect();

    TIdBytes data;

    do
    {
        if (Form1->IdTCPClient2->IOHander->InputBufferIsEmpty())
        {
            if (!Form1->IdTCPClient2->IOHander->CheckForDataOnSource(100))
                break;
        }

        Form1->IdTCPClient2->IOHandler->ReadBytes(data, -1, true);

        /* alternatively:
        Form1->IdTCPClient2->IOHandler->InputBuffer->ExtractToBytes(data, -1, true);
        */
    }
    while (true);

    // use data as needed...
}
catch (const Exception &Ex) {
    Form1->DisplaySSH->Lines->Add(Ex.Message);
    Form1->DisplaySSH->GoToTextEnd();
}

1: убедитесь, что вы используете актуальный снимок Indy 10. До 6 октября 2016 года была логическая ошибка в ReadBytes() когда AByteCount=-1 что не взял InputBuffer перед проверкой сокета на наличие новых байтов.

Другие вопросы по тегам