2024/06/03 電子書籍「電子書籍出版・技術文書作成を劇的に加速!秀丸エディタ + Markdown + Pandocの驚異」を出版

Indy TIdTCPClientの使い方

C++Builder

TCP/IPを使用したプログラムを作成する場合、以下のページで説明しているコンポーネント(TClientSocket, TServerSocket)を使うのが比較的簡単にプログラムを作成する事が出来ます。

ただ、残念ながらこれらのコンポーネントは非推奨となっており、C++Builder(Delphi)では、Indyが標準で使えるようになっています。ここでは、TIdTCPClientコンポーネントの使用方法について説明します。TClientSocketに比べると、若干癖があるように感じます(若干ではないかもしれませんが)。

尚、TIdTCPClient, TIdTCPServerコンポーネントは、VCLでもFMXでも使用出来るコンポーネントです。Android,iOS等で使用出来ますので、今後の事を考えるとTIdTCPClient, TIdTCPServerコンポーネントを使用しておいた方が良いと思います。

TIdTCPClientコンポーネントの使用法を、実際に動作確認用のテストプログラムを作成しながら説明します。実際にプログラムの動作確認を行うには、TIdTCPServer用のテストプログラムも必要となりますので、以下のページも参照ください。

TClientSocket、TServerSocketの使用方法につきましては、以下の記事をご覧ください。

プロジェクトの作成

  1. C++Builderで、初めてのプロジェクト作成で、説明した「SDIアプリケーション」をベースにテストプログラムを作成します。
  2. プロジェクト名を、”TIdTCPClient_test”に変更します。(プロジェクト名の変更を参照してください。)
  3. メッセージの表示用に、TMemoコンポーネントをDropし、Alignプロパティを”alClient”に設定します。
  4. TIdTCPClientコンポーネントをDropします。
  5. 以下のイベントの追加します。

最初のイベント処理を追加するを参考に、以下のイベントを追加しておきましょう。

イベント説明
OnShowフォームが表示されたとき(フォームの Visible プロパティが true に設定されている場合)に発生します。
OnCloseQuery問い合わせを終了しようとしたときに発生します。
OnCloseフォームを閉じたときに発生します。

プロジェクトファイルのダウンロードはこちらから

TIdTCPClientコンポーネントのイベントの追加

以下の2つのイベント処理を追加します。

OnConnectは、関数のみ作成しその後外しておいてください。動的にイベントをセットします。

NameDescription
OnConnectEvent handler signalled for establishing new client connections.
OnDisconnectEvent handler signalled when disconnecting the client connection.

TIdTCPClientコンポーネントの接続処理

TIdTCPClientコンポーネント、クライアント接続を行うには、

  1. Hostに、接続左記のIPアドレスをセット
  2. Portに、ポート番号を設定
  3. OnConnectedに、イベント関数をセット
  4. Connect()関数を実行

と、します。アプリの起動時に接続を有効にするには、コンストラクタかOnShowイベントに記述します。

void __fastcall TSDIAppForm::FormShow(TObject *Sender)
{
    // ここは、実行する環境に合わせて変更してください
    IdTCPClient1->Host      = "10.16.77.22";
    IdTCPClient1->Port      = 2000;

    IdTCPClient1->OnConnected   = IdTCPClient1Connected; // 接続した時
    IdTCPClient1->Connect();
}

TIdTCPClientコンポーネントのイベント内での処理

以下に、各イベント処理について説明します。

OnConnectイベント

サーバーとのコネクションが成功した時に呼ばれるイベントです。

このイベント関数の中で、受信用のスレッドを用意し、そのスレッドの中で受信処理を行う必要があります。通常、スレッド処理はコンポーネント側で行ってくれるコンポーネントが多いのですが、TIdTCPClientでは、自分でスレッド処理を実装する必要があります。Android, iOSのプロフラムでも、時間のかかり処理は、別スレッドで処理を行う必要がありあますが、それと同じような処理が必要となります。

void __fastcall TSDIAppForm::IdTCPClient1Connected(TObject *Sender)
{
    // ------------------------------
    // 以下の処理を先に処理する必要がある
    // ------------------------------
    Memo1->Lines->Add("IdTCPClient1Connected");

    // ------------------------------
    // 受信用のスレッドを起動
    // 以下の処理は、この関数内の最後に実行する必要がある
    // ------------------------------
    TcpListenerThread   = new TTcpListenerThread(true);
    TcpListenerThread->FreeOnTerminate      = true;
    TcpListenerThread->Resume();    //
}

OnDisconnectイベント

コネクションが切断された時に呼ばれるイベントです。

void __fastcall TSDIAppForm::IdTCPClient1Disconnected(TObject *Sender)
{
    Memo1->Lines->Add("IdTCPClient1Disconnected");    //
}

スレッド内での受信

TTcpListenerThread1.cpp

スレッド処理の参考例です。コネクションが切れたら、スレッド終了します。

スレッド(TTcpListenerThread)内で、

  1. IOHandler->InputBuffer->Sizeで、受信データのサイズ取得
  2. IOHandler->ReadBytes()関数で、データの受信
  3. SyncPrint()関数の呼び出し

という処理を行っています。

受信したデータの具体的な処理は、SyncPrint()関数を修正すれば良いということになります。

これで、データの受信の雛形は完成した事になります。

#include <System.hpp>
#pragma hdrstop

#include "TTcpListenerThread1.h"
#pragma package(smart_init)

TTcpListenerThread *TcpListenerThread = NULL;

//---------------------------------------------------------------------------
#include "SDIMain.h"

__fastcall TTcpListenerThread::TTcpListenerThread(bool CreateSuspended)
	: TThread(CreateSuspended)
{
}

//---------------------------------------------------------------------------
void __fastcall TTcpListenerThread::Execute()
{
	//---- ここにスレッド コードを記述します ----
	while(Terminated != true)
	{
		if(SDIAppForm->IdTCPClient1->Connected() == false) // 切断されたら終わる
		{
			Terminate();
			continue;
		}

	    // 受信処理
		int size = SDIAppForm->IdTCPClient1->IOHandler->InputBuffer->Size;
		if(size != 0) 
		{
			RecBytes.Length = 0;
			SDIAppForm->IdTCPClient1->IOHandler->ReadBytes(RecBytes, size, true);
		    Synchronize(SyncPrint);
			RecBytes.Length = 0;
		}
	}
}

//---------------------------------------------------------------------------
void __fastcall TTcpListenerThread::SyncPrint()
{
	String wstr = "";

	// 受信データが、ASCIIコードの文字の場合
	for(int i=0;i<RecBytes.Length;i++)
	{
		wstr += (char)RecBytes[i];
	}
	SDIAppForm->Memo1->Lines->Add(wstr);
}

TTcpListenerThread1.h

TTcpListenerThread1.cppのヘッダファイルです。

#ifndef TTcpListenerThread1H
#define TTcpListenerThread1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <System.SysUtils.hpp>
//---------------------------------------------------------------------------
class TTcpListenerThread : public TThread
{
private:
	void __fastcall SyncPrint();
protected:
	void __fastcall Execute();

public:
	__fastcall TTcpListenerThread(bool CreateSuspended);

    TBytes RecBytes;
};
//---------------------------------------------------------------------------
extern TTcpListenerThread *TcpListenerThread;
#endif

データの送信

簡単に実装するために、ファイルの作成イベント(FileNew1)に、データ送信の処理を追加しました。

データの送信は、IOHandler->Write()関数を使用します。この関数の引数も、TBytes型をとります。

void __fastcall TSDIAppForm::FileNew1Execute(TObject *Sender)
{
    if( (IdTCPClient1 != NULL) && (IdTCPClient1->Connected()) )
    {
        // データ送信
        char str[]  = "Client Test2";

        TBytes txdata;
        txdata.Length   = sizeof(str);
        for(int i=0;i<sizeof(str);i++)
        {
            txdata[i]   = (BYTE)str[i];
        }
        IdTCPClient1->IOHandler->Write(txdata, txdata.Length);
    }
}

再接続処理

再接続処理ですが、データの送信時に、サーバーが存在しない時に、再接続する処理を検討してみました。IOHandler->Write()では、例外が発生しなかった為、ダミーで1バイト0を送信する関数を作成し、その関数を利用して再接続するようにしてみました。

void __fastcall TSDIAppForm::FileNew1Execute(TObject *Sender)
{
  try
  {
    if( (IdTCPClient1 != NULL) && (IdTCPClient1->Connected()) )
    {
        // データ送信
        char str[]  = "Client Test2";

        TBytes txdata;
        txdata.Length   = sizeof(str);
        for(int i=0;i<sizeof(str);i++)
        {
            txdata[i]   = (BYTE)str[i];
        }
        // ここでの、接続待ちでは例外は発生しない
        IdTCPClient1->IOHandler->Write(txdata, txdata.Length);

        if(IdTCPClient1->Connected() == false)
        {
            // ここでチェックする事で例外が発生する
            IdTCPClient1->Disconnect();
            IdTCPClient1->Connect();
            IdTCPClientDummyWrite();
        }
    }  else {
        IdTCPClient1->Disconnect();
        IdTCPClient1->Connect();
        IdTCPClientDummyWrite();
    }
  } catch(const Exception &e) {
    IdTCPClient1->Disconnect();
    IdTCPClient1->Connect();
    IdTCPClientDummyWrite();
  }
}

void __fastcall TSDIAppForm::IdTCPClientDummyWrite(void)
{
    // 2023/07/21 ダミーライトすると、コネクションされる
    TBytes txdata;
    txdata.Length   = (int)1;
    txdata[0]       = 0x00;
    IdTCPClient1->IOHandler->Write(txdata, (int)1);
}

再接続処理を追加したプロジェクトファイルのダウンロードはこちらから

参考URL

以下のHPも参考にしてください。

タイトルとURLをコピーしました