Delphi 素朴なソケット通信をしたい

<h1>はじめに</h1>
測定装置など、ハードウェアとの通信にTCP/IPを用いる場合は、ソケットの挙動について、細かな制御が必要がある場合が未だあるように思います。

Delphi XE のTTcpServerは、文字列受信後に直ぐにコネクションを閉じてしまう為、今回はIndy の TIdTCPServer を使用してみました。

（備忘録です）

<h1>サンプル</h1>

<h2>フォーム</h2>
![フォームデザイン.png](https://qiita-image-store.s3.amazonaws.com/0/33684/24a57384-4f3c-5fcf-047e-07dc26479966.png)


<h2>ソースコード</h2>
```delphi
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdContext, Vcl.StdCtrls,
  IdBaseComponent, IdComponent, IdCustomTCPServer, IdTCPServer, Vcl.ExtCtrls, IdGlobal;

type
  TForm1 = class(TForm)
    LabeledEdit1: TLabeledEdit;
    IdTCPServer1: TIdTCPServer;
    CheckBox1: TCheckBox;
    Memo1: TMemo;
    procedure IdTCPServer1Execute(AContext: TIdContext);
    procedure IdTCPServer1Exception(AContext: TIdContext;
      AException: Exception);
    procedure IdTCPServer1Disconnect(AContext: TIdContext);
    procedure IdTCPServer1Connect(AContext: TIdContext);
    procedure CheckBox1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    FEncoding:TEncoding;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FEncoding := TEncoding.GetEncoding(932);
  Memo1.Clear;
  LabeledEdit1.Text := IntToStr(IdTCPServer1.DefaultPort);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if IdTCPServer1.Active then
    IdTCPServer1.Active := False;

  FEncoding.Free;
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  if CheckBox1.Checked then begin
    CheckBox1.Caption := 'Start';
    IdTCPServer1.DefaultPort := StrToInt(LabeledEdit1.Text);
    IdTCPServer1.Active := True;
  end else begin
    CheckBox1.Caption := 'Stop';
    IdTCPServer1.Active := False;
  end;
end;

procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
begin
  Memo1.Lines.Add('Connect:'+AContext.Binding.PeerIP);
end;

procedure TForm1.IdTCPServer1Disconnect(AContext: TIdContext);
begin
  Memo1.Lines.Add('Disconnect:'+AContext.Binding.PeerIP);
end;

procedure TForm1.IdTCPServer1Exception(AContext: TIdContext;
  AException: Exception);
begin
  Memo1.Lines.Add(AException.Message);
end;

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
  Text: String;
  Bytes: TIdBytes;
begin
  AContext.Connection.IOHandler.ReadBytes(Bytes,-1);

  try
    Text := FEncoding.GetString(Bytes);
    Memo1.Lines.Add(Text);

    AContext.Connection.IOHandler.Write('OK');
  except
    Memo1.Lines.Add('文字列変換に失敗しました');
  end;

end;

end.
```

<h2>結果</h2>
汎用のソケット通信ツールを使用して、接続したり、文字を送ってみてください。
![こんな感じ.png](https://qiita-image-store.s3.amazonaws.com/0/33684/020e8a1a-3f8f-b51e-9ec2-6e05a47a9352.png)

上記例は、<b>汎用ツール側</b>で、
　１．接続→「あいうえお」送信→切断
　２．接続→「かきくけこ」送信→アプリ終了
を行いました。
２．の後に、もう再度接続する事も可能です。

<h1>ポイント</h1>
<h2>Usesの追加</h2>
「IdGlobal」は手動Usesに追加する必要があるようです。

<h2>受信時の処理</h2>
こちらの記事を参考に、TIdTCPServerのExcecuteイベントにて、受信バッファをバイト列で取出し、Stringに変換します。
http://www.watercolor-city.net/ct_delphi/delphi_tiburon/doc_unicode/bintostring.htm

```delphi
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
  Text: String;
  Bytes: TIdBytes;
begin
  AContext.Connection.IOHandler.ReadBytes(Bytes,-1);

  try
    Text := FEncoding.GetString(Bytes);
    Memo1.Lines.Add(Text);

    AContext.Connection.IOHandler.Write('OK');
  except
    Memo1.Lines.Add('文字列変換に失敗しました');
  end;

end;
```

ReadBytesの２番目の引数は、取得バイト数で、-1に設定すると「前回の取得から全て」となるようです。便利！

今回は、TEncoding のインスタンスをFormCreate内で作成しています。
コードページには、932（Microsoft コードページ　Shift-JIS）を指定します。

<h2>クライアントの区別</h2>
AContext.Binding.PeerIP で IP を取得できるようです。

<h1>（自分への）宿題</h1>
「接続中の任意のクライアントに、自発的にデータ送信する」場合の処理が、まだ調べられていません。どこからアクセスするのか・・英語・・ﾆｶﾞﾃ・・。