delphic++builderindy10c++builder-10.2-tokyoc++builder-tokyo

Reading stream of Data using TIdIOHandlerStream and TIdTCPClient


I have an app that needs to connect to a server using TCP/IP and then just wait for server to send data, and what ever server sends should be saved into a file.

Here is what I did:

The Header file

#ifndef MainH
#define MainH
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <FMX.Controls.hpp>
#include <FMX.Forms.hpp>
#include <FMX.Controls.Presentation.hpp>
#include <FMX.StdCtrls.hpp>
#include <FMX.Types.hpp>
#include <IdBaseComponent.hpp>
#include <IdComponent.hpp>
#include <IdIOHandler.hpp>
#include <IdIOHandlerStream.hpp>
#include <IdTCPClient.hpp>
#include <IdTCPConnection.hpp>
#include <boost/scoped_ptr.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:    // IDE-managed Components
    TIdTCPClient *pTCP;
    TIdIOHandlerStream *IdIOHandlerStream;
    TButton *Button1;
    void __fastcall Button1Click(TObject *Sender);
    void __fastcall IdIOHandlerStreamGetStreams(TIdIOHandlerStream *ASender, TStream *&VReceiveStream, TStream *&VSendStream);
private:    // User declarations
    boost::scoped_ptr<TFileStream> mFile;
    boost::scoped_ptr<TMemoryStream> mMem;

    void __fastcall StopTcpClick(TObject* Sender);
public:     // User declarations
    __fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

and the CPP file:

include <fmx.h>
#pragma hdrstop

#include "Main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.fmx"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner),
                            mFile(new TFileStream(L"C:\\IbsData.txt", fmCreate | fmOpenReadWrite | fmShareDenyWrite)),
                            mMem(new TMemoryStream())
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    pTCP->Connect();
    Button1->Text = L"Stop";
    Button1->OnClick = StopTcpClick;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::StopTcpClick(TObject* Sender)
{
    pTCP->Disconnect();
    Button1->Text = L"Start";
    Button1->OnClick = Button1Click;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::IdIOHandlerStreamGetStreams(TIdIOHandlerStream *ASender, TStream *&VReceiveStream, TStream *&VSendStream)
{
    VReceiveStream = mFile.get();
    VSendStream = mMem.get();
}

I have to note that IdIOHandlerStream has been set as the IOHandler of pTCP.

The problem is, I know server is sending lots of data, but nothing gets written into the file.

Does anyone know why?


Solution

  • You are using the wrong IOHandler class.

    TIdIOHandlerStream performs I/O using TStream objects. It is typically used for replaying previously captured sessions for debugging purposes, without needing a physical connection to a real server.

    You need to use TIdIOHandlerStack instead, which performs I/O using a TCP/IP socket connection. It is Indy's default IOHandler class, so you don't even need to create an instance of it 1, TIdTCPClient::Connect() will create one internally for you if you do not assign your own.

    1: unless you need more advanced usage, like connecting to a server through a proxy, etc, then you need your own instance so you can configure it as needed.

    For what you are attempting, let TIdTCPClient use TIdIOHandlerStack and then you can call the TIdIOHandler::ReadStream() method after connecting to the server. Pass in a TFileStream for it to read into, and set its AByteCount parameter to -1 and AReadUntilDisconnect parameter to True so it will read continuously until the socket connection is closed.

    Also, like most operations in Indy, ReadStream() blocks the calling thread until finished, so to avoid blocking your UI, you should call ReadStream() in a worker thread. But, if you don't want to use a thread, you can alternately put a TIdAntiFreeze component on your Form instead.