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