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


動作環境
C++ Builder XE4

処理概要

  • 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していない

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

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

使用ツール

関連記事