Прочитать все доступные байты из TCP Socket (неизвестное количество байтов) - PullRequest
0 голосов
/ 01 ноября 2018

У меня проблемы с использованием 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 Ответ

0 голосов
/ 01 ноября 2018

Вы не должны использовать 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 перед проверкой сокета на наличие новых байтов.

...