TCP/IPを使用したプログラムを作成する場合、以下のページで説明しているコンポーネント(TClientSocket, TServerSocket)を使うのが比較的簡単にプログラムを作成する事が出来ます。
ただ、残念ながらこれらのコンポーネントは非推奨となっており、C++Builder(Delphi)では、Indyが標準で使えるようになっています。ここでは、TIdTCPClientコンポーネントの使用方法について説明します。TClientSocketに比べると、若干癖があるように感じます(若干ではないかもしれませんが)。
尚、TIdTCPClient, TIdTCPServerコンポーネントは、VCLでもFMXでも使用出来るコンポーネントです。Android,iOS等で使用出来ますので、今後の事を考えるとTIdTCPClient, TIdTCPServerコンポーネントを使用しておいた方が良いと思います。
TIdTCPClientコンポーネントの使用法を、実際に動作確認用のテストプログラムを作成しながら説明します。実際にプログラムの動作確認を行うには、TIdTCPServer用のテストプログラムも必要となりますので、以下のページも参照ください。
Indy TIdTCPServerの使い方、テストプログラムにつきましては、こちらの記事をご覧ください。
TClientSocket、TServerSocketの使用方法につきましては、以下の記事をご覧ください。
プロジェクトの作成
- C++Builderで、初めてのプロジェクト作成で、説明した「SDIアプリケーション」をベースにテストプログラムを作成します。
- プロジェクト名を、”TIdTCPClient_test”に変更します。(プロジェクト名の変更を参照してください。)
- メッセージの表示用に、TMemoコンポーネントをDropし、Alignプロパティを”alClient”に設定します。
- TIdTCPClientコンポーネントをDropします。
- 以下のイベントの追加します。
最初のイベント処理を追加するを参考に、以下のイベントを追加しておきましょう。
イベント | 説明 |
---|---|
OnShow | フォームが表示されたとき(フォームの Visible プロパティが true に設定されている場合)に発生します。 |
OnCloseQuery | 問い合わせを終了しようとしたときに発生します。 |
OnClose | フォームを閉じたときに発生します。 |
TIdTCPClientコンポーネントのイベントの追加
以下の2つのイベント処理を追加します。
OnConnectは、関数のみ作成しその後外しておいてください。動的にイベントをセットします。
Name | Description |
---|---|
OnConnect | Event handler signalled for establishing new client connections. |
OnDisconnect | Event handler signalled when disconnecting the client connection. |
TIdTCPClientコンポーネントの接続処理
TIdTCPClientコンポーネント、クライアント接続を行うには、
- Hostに、接続左記のIPアドレスをセット
- Portに、ポート番号を設定
- OnConnectedに、イベント関数をセット
- 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)内で、
- IOHandler->InputBuffer->Sizeで、受信データのサイズ取得
- IOHandler->ReadBytes()関数で、データの受信
- 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も参考にしてください。