从 TCP 套接字读取所有可用字节(未知字节数)

Read all available bytes from TCP Socket (unknown byte count)

我在使用 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) 两次,中间没有读取数据。 因此,由于我不知道我必须读取多少字节,所以我认为我可以读取一个字节,检查是否有更多可用字节,然后读取另一个字节。 这里的问题是 do while 循环不循环,它只运行一次。 我猜 Readable 并没有完全按照我需要的方式工作。 有没有其他方法可以接收套接字中所有可用的字节?

在这种情况下,您不应该直接使用 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,它会return只有当前可用的字节(如果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 的最新快照。在 2016 年 10 月 6 日之前,当 [=25= 时 ReadBytes() 中存在逻辑错误] 在检查新字节的套接字之前没有考虑 InputBuffer