C++ Builder XE4 > TCP > Clientからの送信と受信処理 > v0.3: TTimerによる受信リトライ

```txt:動作環境
C++ Builder XE4
```

- v0.1, v0.2: [C++ Builder XE4 > TCP > Clientからの送信と受信処理 > v0.1:whileでの受信タイムアウト, v0.2:ReadLn()での受信タイムアウト | 余談: 記事消した...](https://qiita.com/7of9/items/18b9becaf67e4c7058d9)

### 処理概要

- TCPサーバーに接続する
- TCPサーバーに文字列を送信する
- TCPサーバーからの応答を受信する
    - タイムアウト付き
    - 受信リトライ付き

### v0.3の変更点

v0.1, v0.2においては、commTCP()の処理において受信完了（またはタイムアウト)まで処理を抜けることはなかった。
他の処理との並列性が失われる。

v0.3では以下のようにした。

- commTCP()では以下を行う
    - 接続
    - 送信
    - 受信用TTimer起動
- 受信用TTimerで以下を行う
    - 受信処理 (リトライ3回)

```Unit1.h
//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <IdBaseComponent.hpp>
#include <IdComponent.hpp>
#include <IdTCPClient.hpp>
#include <IdTCPConnection.hpp>
#include <Vcl.ComCtrls.hpp>
#include <Vcl.ExtCtrls.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TEdit *E_ipadr;
	TButton *Button1;
	TIdTCPClient *IdTCPClient1;
	TLabel *Label1;
	TLabel *Label2;
	TEdit *E_port;
	TLabel *Label3;
	TEdit *E_sendText;
	TStatusBar *StatusBar1;
	TTimer *ReceiveTimer;
	void __fastcall Button1Click(TObject *Sender);
	void __fastcall ReceiveTimerTimer(TObject *Sender);
private:	// ユーザー宣言
	void __fastcall commTCP(int timeout_msec);
	void __fastcall disconnectTcp(void);
public:		// ユーザー宣言
	__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
```

```Unit1.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------

/*
*** 留意事項 (2018/01/11) ***
	- [ReceiveTimer]のTagを受信リトライの回数として使用
*/

/*
v0.3 2018/01/11
	- TTimerによる受信処理 (リトライ付き)
		+ disconnectTcp()追加
		+ [ReceiveTimer]追加
v0.2 2018/01/11
	- ReadLn()の引数での受信タイムアウト処理
v0.1 2018/01/11
	- whileでの受信タイムアウト処理
*/

__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
	ReceiveTimer->Enabled = false;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::commTCP(int timeout_msec)
{
	// 1. Connect
	IdTCPClient1->Host = E_ipadr->Text;
	int portNum = StrToIntDef(E_port->Text, -1);
	if (portNum < 0) {
		String wrnmsg = L"Error: Invalid port number:" + E_port->Text;
		MessageDlg(wrnmsg, mtError, TMsgDlgButtons() << mbOK, 0);
		return; // fail to get portNum
	}
	IdTCPClient1->Port = portNum;
	try {
		IdTCPClient1->Connect();
	} catch (...) {
		String wrnmsg = L"Failed to connect to " + E_ipadr->Text;
		MessageDlg(wrnmsg, mtError, TMsgDlgButtons() << mbOK, 0);
		return;
	}

	// 2. send
	String sndTxt = E_sendText->Text;
	IdTCPClient1->IOHandler->WriteLn(sndTxt);

	// 3. receive用TTimerの起動
	ReceiveTimer->Tag = 3; // Retry
	ReceiveTimer->Interval = 100; // msec
	ReceiveTimer->Enabled = true;
}

void __fastcall TForm1::Button1Click(TObject *Sender)
{
	IdTCPClient1->ConnectTimeout = 2000; // msec
	IdTCPClient1->ReadTimeout = 2000; // msec

	commTCP(/*timeout_msec=*/1000);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ReceiveTimerTimer(TObject *Sender)
{
	// *** InputBufferIsEmpty()使用ではサーバー応答ありでもReadLn()まで到達することはなかった
//	if (IdTCPClient1->IOHandler->InputBufferIsEmpty()) {
//		ReceiveTimer->Tag -= 1;
//		if (ReceiveTimer->Tag == 0) {
//			ReceiveTimer->Enabled = false;
//			disconnectTcp();
//			//
//			String wrnmsg = L"Error: Receive timeout";
//			MessageDlg(wrnmsg, mtError, TMsgDlgButtons() << mbOK, 0);
//		}
//		return;
//	}

	String rcvd = IdTCPClient1->IOHandler->ReadLn(L"", 10); // msec
	//String rcvd = IdTCPClient1->IOHandler->ReadLn();
	if (rcvd == NULL || rcvd.Length() == 0) {
		ReceiveTimer->Tag -= 1;
		if (ReceiveTimer->Tag == 0) {
			ReceiveTimer->Enabled = false;
			disconnectTcp();
			//
			String wrnmsg = L"Error: Receive timeout";
			MessageDlg(wrnmsg, mtError, TMsgDlgButtons() << mbOK, 0);
		}
		return;
	}
	ReceiveTimer->Enabled = false;
	disconnectTcp();

	String infmsg = L"Received: " + rcvd;
	MessageDlg(infmsg, mtInformation, TMsgDlgButtons() << mbOK, 0);
}

void __fastcall TForm1::disconnectTcp(void)
{
	IdTCPClient1->IOHandler->InputBuffer->Clear();
	IdTCPClient1->Disconnect();
}
//---------------------------------------------------------------------------
```

## 備考

- InputBufferIsEmpty()を使ったリトライではサーバーが応答しても受信できなかった (失敗)
- ReceiveTimerのTagプロパティを受信リトライ回数として使ったが、ソースリーディングでは分かりにくい可能性がある
    - きちんとした名前のprivate変数にした方が良いだろう

## 実行例
(v0.1, v0.2と同じ)

### A. 相手先がListenしていない

![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/32870/d811d47a-bfc3-c1bc-2e48-3329c5c9ef0d.png)

### B. 相手先がListenしている, 応答しない

![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/32870/415353d4-6b44-119d-6b31-0046a6e56720.png)

### C. 相手先がListenしている, 応答する

![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/32870/c6ee129e-9eba-4349-a587-15ae7f6a72d7.png)

## 使用ツール

- [NonSoft - TCP/IPテストツール](http://nonsoft.la.coocan.jp/Download/SocketTool/index.html)
    - AとBの実行例について使用させていただきました
- [自作のTCP/IP echo server](https://qiita.com/7of9/items/d9367b0e2c8c844faa8e)
    - Cの実行例について使用しました

## 関連記事

- [C++ Builder XE4, 10.2 Tokyo >TIdTCPClientを使った送信例](https://qiita.com/7of9/items/0f09d499de5ec4fec428)
- [c++ builder XE4 > tcpサーバ / tcpクライアント](https://qiita.com/7of9/items/51bd31318c6e1332adbf)
- [C++ Builder XE4, 10.2 Tokyo > TCPサーバ > echo server > 1対1通信 | (1対N通信)](https://qiita.com/7of9/items/8925cce71cbf43448d55)