C++ Builder XE4 > TCP > Client에서 전송 및 수신 처리 > v0.3: TTimer를 통한 수신 재시도

운영 환경
C++ Builder XE4
  • v0.1, v0.2: C++ Builder XE4 > TCP > 클라이언트로부터의 송신 및 수신 처리 > v0.1:while에서의 수신 타임아웃, v0.2:ReadLn()에서의 수신 타임아웃 | 여담: 기사 지웠다...

  • 처리 개요


  • 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. 상대방이 청취하지 않음





    B. 상대방이 청취 중, 응답하지 않음





    C. 상대방이 청취하고 있다, 응답





    사용 도구



  • NonSoft - TCP/IP 테스트 도구
  • A와 B의 실행 예를 사용했습니다.


  • 자작 TCP/IP echo server
  • C 실행 예제에 사용했습니다.


  • 관련 기사


  • C++ Builder XE4, 10.2 Tokyo >TIdTCPClient를 사용한 전송 예
  • c++ 빌더 XE4 > tcp 서버 / tcp 클라이언트
  • C++ Builder XE4, 10.2 Tokyo > TCP 서버 > echo server > 일대일 통신 | (일대 N 통신)
  • 좋은 웹페이지 즐겨찾기