DXライブラリを使用したSocketクライアントの作成

<!----------------------------------------------------------------------------------------------------------------------------------->
## 概要
<!----------------------------------------------------------------------------------------------------------------------------------->

### 作業目的

DXライブラリを利用してSocketクライアントを作成します。

### 環境・前提条件

本件手順では、以下Windows環境にMicrosoft Visual Studioがインストールされている前提とします。

- エディション：Microsoft Windows 10 Pro
- システムの種類：64 ビット オペレーティング システム、x64 ベース プロセッサ
- バージョン：22H2
- OSビルド 19045.4651
- エクスペリエンス：Windows Feature Experience Pack 1000.19060.1000.0
- Microsoft Visual Studio Community 2022 (64 ビット) ：Version 17.9.7
- ＤＸライブラリ Windows版 VisualStudio( C++ )用：Ver 3.24d
- (2024年7月21日現在)

### 参考ページ

その他の参考となるページは以下に記載しています。

- [参考ページ](https://qiita.com/techmilestone/items/007e28dc416af3d8c778#1-3%E5%8F%82%E8%80%83%E3%83%9A%E3%83%BC%E3%82%B8)

<!----------------------------------------------------------------------------------------------------------------------------------->
## DXライブラリ準備
<!----------------------------------------------------------------------------------------------------------------------------------->

### DXライブラリの事前設定

下記ページから、DXライブラリの設定準備を進めてください。
- [Visual Studio Community 2022 を使用した場合のＤＸライブラリの使い方](https://dxlib.xsrv.jp/use/dxuse_vscom2022.html)
- 以後はプロジェクト名を「dxtest」、ソース名を「dxtest.cpp」として説明します。

<!----------------------------------------------------------------------------------------------------------------------------------->
## DXライブラリでSocket処理を利用する場合の注意点
<!----------------------------------------------------------------------------------------------------------------------------------->

### Socket処理のサンプル

下記ページにサンプルがあります。
- [DXライブラリ置き場　リファレンスページ/ConnectNetWork](https://dxlib.xsrv.jp/function/dxfunc_other.html#R13N1)

### 送信データサイズの解除

事前に"SetUseDXNetWorkProtocol"関数を実行して、無効化する必要があります。

```cpp

SetUseDXNetWorkProtocol( FALSE );

```

- [DXライブラリのネットワーク通信](https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=past&no=2159)

<!----------------------------------------------------------------------------------------------------------------------------------->
## Socketクライアントの作成
<!----------------------------------------------------------------------------------------------------------------------------------->

### 処理概要
Socketクライアントとしてメッセージを送信するアプリケーションを作成します。

### プログラムソース

[手順1.]ソースの作成

以下のソースについてコピー＆ペーストします。
保存する場合にはテキストファイルを以下の形式としています。

(テキストファイルの仕様)
- 改行コード：SJIS
- 改行コード：CR+LF

<details><summary>dxtest.cpp</summary><div>

```cpp

/* ---------------------------------------------------------------------------------------------- 
 * 
 * プログラム概要   ： DXライブラリ用Socket接続
 * バージョン       ： 1.0.0.0
 * プログラム名     ： dxtest.exe
 * ファイル名       ： dxtest.cpp
 * 処理概要         ： メイン処理
 * Ver1.0.0.0作成日 ： 2024/07/21 10:32:37
 * 最終更新日       ： 2024/07/21 10:32:37
 * 
 * Copyright (c) 2023-2024 Techmilestone, All rights reserved.
 * 
 * ---------------------------------------------------------------------------------------------- */

#include "DxLib.h"

// ----------------------------------------
// グローバル変数定義
// ----------------------------------------

	// ----------------------------------------
	// 変数初期化(フレーム処理)
	// ----------------------------------------

	// フレーム処理用定数
	static const int SAMPLE_FRAME_COUNT = 60;   // フレームのサンプル数
	static const int FPS                = 60;   // 想定のFPS値 (待ち時間の算出用)

	// FPS算出用変数(フレーム定義)
	static int frame_count;                     // サンプルフレームのカウンタ数
	static int total_frame_count;               // 処理したフレームの合計数

	// FPS算出用変数(時間定義)
	static int checkpoint_time;                 // 定点時刻(チェックポイントごとの時刻)
	static int wait_time;                       // 待ち時間(Sleep関数を利用するために単位はミリ秒)

	// FPS算出用変数(FPS数)
	static float ave_fps;                       // 実測のFPS値

	// ----------------------------------------
	// 変数初期化(マウス処理)
	// ----------------------------------------

	// マウス処理情報
	static int msx;                     // X座標
	static int msy;                     // Y座標
	static int get_mouse_input;         // マウスの押した状態取得
	static unsigned int get_button[8];  // ボタンの押した状態
	static int wheel_rot_vol;           // ホイールの回転量

	// ----------------------------------------
	// 変数初期化(画像処理)
	// ----------------------------------------

	// 画像ハンドル
	static int pic_handle;              // 背景用画像

	// ----------------------------------------
	// 変数初期化(Socket処理)
	// ----------------------------------------
	static int  net_handle;      // ネットワークハンドル
	static char rcv_buf[1024];   // 受信用バッファ
	static char snd_buf[1024];   // 送信用バッファ

// ----------------------------------------
// フレーム初期化
// ----------------------------------------
int WindowFrameInit(){

	// Log.txtの出力抑止
	SetOutApplicationLogValidFlag(FALSE);

	// ウインドウモードにする
	// DxLib_Initより前に行う
    if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK ) {

		// 異常終了
		return -1;

	}

	// ウィンドウサイズ変更
	SetGraphMode( 1024 , 768 , 32 );
	SetWindowSize( 1024 , 768 );

	// ウインドウがアクティブではない状態でも処理を続行する
 	SetAlwaysRunFlag(TRUE);

	// 2つ以上同時に起動できるかどうかを設定する。
 	SetDoubleStartValidFlag(TRUE);

	// マウス表示オン
    SetMouseDispFlag(TRUE);

	// 正常終了
	return 0;

}

// ----------------------------------------
// DX表示ライブラリ初期化
// ----------------------------------------
int CustomDxlibInit(){

	// DXライブラリ初期化
	if (DxLib_Init() != 0){

		// 異常終了
		return -1;

	}

	// 描画先を裏画面に設定
	SetDrawScreen(DX_SCREEN_BACK); 

	// 画像ハンドル
	pic_handle = LoadGraph("bmp/back.bmp"); // 画像のロード

	// 正常終了
	return 0;

}

// ----------------------------------------
// DX表示ライブラリ終了処理
// ----------------------------------------
void CustomDxlibEnd(){

	DxLib_End();   // DXライブラリ使用の終了処理

}

// ----------------------------------------
// Fps算出処理の初期化
// ----------------------------------------
void FpsParamInit(){

	// 変数初期化
	ave_fps           = 0;    // ave_fps初期化
	frame_count       = 0;    // frame_count初期化
	total_frame_count = 0;    // total_frame_count初期化

	// 最初フレームの「定点時刻(checkpoint_time)」定義
	checkpoint_time = GetNowCount();

}

// ----------------------------------------
// 定点時刻(checkpoint_time)の更新、平均Fps算出
// ----------------------------------------
void FpsUpdate(){

	// 変数定義
	int tmp_now_time;    // 現在時刻

	// サンプルフレーム数に達した場合の内部処理
	// 「定点時刻(checkpoint_time)」の更新、「平均ave_fps」算出
	if( frame_count == SAMPLE_FRAME_COUNT ){ 

		// 現在の時間の取得
		tmp_now_time = GetNowCount();

		// 「平均ave_fps」算出処理
		ave_fps = (float)SAMPLE_FRAME_COUNT*1000.f / (tmp_now_time - checkpoint_time);

		// 「フレームのカウンタ数」の初期化
		frame_count = 0;

		// SAMPLE_FRAME_COUNTごとに「定点時刻」を更新
		checkpoint_time = tmp_now_time;

	}

	// 「フレームのカウンタ数」の加算
	frame_count++;

	// 「合計フレームのカウンタ数」の加算
	total_frame_count++;

}

// ----------------------------------------
// FPSウエイト処理
// ----------------------------------------
void FpsWait(){

	// 現在時刻と「定点時刻(checkpoint_time)」との差分時間を算出
	// それから待ち時間(wait_time)を算出
	wait_time = ( frame_count*1000 / FPS ) - (GetNowCount() - checkpoint_time); // frame_countからの理論時刻と実機からの現在時間との差分を算出する

	// ウエイト処理
	if( wait_time > 0 ){
		Sleep(wait_time); // wait_timeの時間(ミリ秒)だけ待機を行う
	}

}

// ----------------------------------------
// 表示処理
// ----------------------------------------
void CustomDxlibDraw(){

	// 画面を消す
	ClearDrawScreen();

	// 背景表示
	DrawGraph( 0, 0, pic_handle, TRUE );

	// 文字列表示
	DrawFormatString(  5,   0, GetColor(255,255,255), "受信メッセージ：%s", rcv_buf);
	DrawFormatString(  5,  17, GetColor(255,255,255), "実測FPS値：%.1f", ave_fps);

	// 裏画面を表画面に反映
	ScreenFlip();

}

// ----------------------------------------
// socket初期化
// ----------------------------------------
int CustomSocketInit(){

	// 変数定義
	IPDATA ip;                   // 接続用IPアドレスデータ

	// DXライブ独自のsocket仕様の機能を使用しない
	SetUseDXNetWorkProtocol( FALSE );

	// IPアドレスを設定
	ip.d1 = 127 ;
	ip.d2 = 0 ;
	ip.d3 = 0  ;
	ip.d4 = 1 ;

	// 通信を確立
	net_handle = ConnectNetWork( ip, 11600 ) ;

	// ハンドルの正常性確認
	if( net_handle == -1 ){
		// 異常終了
		return -1;
	}else{
		// データ送信
		sprintf_s(snd_buf,"接続要求\n");
		NetWorkSend(net_handle, snd_buf, lstrlen(snd_buf)) ;
		// 正常終了
		return 0;
	}

}

// ----------------------------------------
// socket終了処理
// ----------------------------------------
void CustomSocketEnd(){

	// ネットワークハンドルの解放
	CloseNetWork(net_handle);

}

// ----------------------------------------
// データ送受信
// ----------------------------------------
void CustomSocketRecSnd(){

	// 変数定義
	int data_size;               // 受信データ時のデータサイズ

	// 取得していない受信データ量を得る
	data_size = GetNetWorkDataLength(net_handle);

	// 受信データ量が0より大きい場合
	if( data_size > 0 ){

		// バッファクリア
		memset(rcv_buf, 0, sizeof(rcv_buf));

		// 受信したデータをバッファに取得
		NetWorkRecv(net_handle, rcv_buf, sizeof(rcv_buf));

		// サーバ側にデータ送信
		sprintf_s(snd_buf,"受信完了\n");
		NetWorkSend(net_handle, snd_buf, lstrlen(snd_buf));

	}

}


// ----------------------------------------
// メイン処理
// ----------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){

	// ----------------------------------------
	// 事前処理
	// ----------------------------------------

	// Windows画面の事前処理
	if (WindowFrameInit() != 0){

		// ダイアログの出力
		MessageBox( NULL, "異常終了 ChangeWindowMode", "警告", MB_OK );
		return -1;

	}

	// DXライブラリ初期化
	if (CustomDxlibInit() != 0){    

		// ダイアログの出力
		MessageBox( NULL, "異常終了 DxLib_Init", "警告", MB_OK );
		return -1;

	}

	// FPS処理初期化
	if (CustomSocketInit() != 0){    

		// ダイアログの出力
		MessageBox( NULL, "異常終了 Socket_Init", "警告", MB_OK );
		return -1;

	}

	// FPS処理初期化
	FpsParamInit();

	// ----------------------------------------
	// メイン処理(ループ処理)
	// ----------------------------------------

	// 終了処理がされるまで、ループ処理
	while( ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0 ){

		// 関数処理
		CustomSocketRecSnd();  // socketデータの送受信
		FpsUpdate();           // checkpoint_time更新、平均Fps算出
		CustomDxlibDraw();     // 表示処理
		FpsWait();             // ウエイト処理

	}

	// ----------------------------------------
	// 終了処理
	// ----------------------------------------

	// socket終了処理
	CustomSocketEnd();

	// DXライブラリ終了処理
	CustomDxlibEnd();

	// 正常終了
	return 0;

}

/* ---------------------------------------------------------------------------------------------- 
 * ソース終了
 * ---------------------------------------------------------------------------------------------- */

```

</div></details>

[手順2.]ソースのビルドを行う。

Visual Studio Communityを利用してビルドを行ってください。

<!----------------------------------------------------------------------------------------------------------------------------------->
## Socketクライアントの機能説明
<!----------------------------------------------------------------------------------------------------------------------------------->

### はじめに
Socket利用のために、実装が必要となるDXライブラリの関数を記載します。

### 1.変数定義
以下の変数を定義します

```cpp

static int  net_handle;      // ネットワークハンドル
static char rcv_buf[1024];   // 受信用バッファ
static char snd_buf[1024];   // 送信用バッファ

```

### 2.サーバに接続
connect関数でリモートソケット(サーバ側ソケット)に接続します

```

// DXライブ独自のsocket仕様の機能を使用しない
SetUseDXNetWorkProtocol( FALSE );

// IPアドレスを設定
ip.d1 = 127 ;
ip.d2 = 0 ;
ip.d3 = 0  ;
ip.d4 = 1 ;

// 通信を確立
net_handle = ConnectNetWork( ip, 11600 ) ;

```

### 3.サーバにデータを送信
NetWorkSend関数で接続済みサーバのソケットにデータを送信します。

```

// データ送信
sprintf_s(snd_buf,"接続要求\n");
NetWorkSend(net_handle, snd_buf, lstrlen(snd_buf)) ;

```

### 4.サーバからデータを受信
サーバからのデータ受信を検知します。

```

// 取得していない受信データ量を得る
data_size = GetNetWorkDataLength(net_handle);

// 受信データ量が0より大きい場合
if( data_size > 0 ){

	// バッファクリア
	memset(rcv_buf, 0, sizeof(rcv_buf));

	// 受信したデータをバッファに取得
	NetWorkRecv(net_handle, rcv_buf, sizeof(rcv_buf));

}

```

### 参考情報

- [PythonでTCP通信(クライアント編)](https://qiita.com/keiusukematsuda/items/362450fda4beca76c030)

<!----------------------------------------------------------------------------------------------------------------------------------->
## 動作確認
<!----------------------------------------------------------------------------------------------------------------------------------->

### 事前処理：「TCP/IPテストツール」のインストール
以下サイトからSocketクライアント用フリーツールを取得します。

- [TCP/IPテストツール](http://nonsoft.la.coocan.jp/Download/SocketTool/)

上記のアプリケーションをインストールします。

### 動作確認：接続確認
以下のとおりにクライアントがサーバからのメッセージを受信しています。

[手順1.]「TCP/IPテストツール」を起動します。

[手順2.]ビルドしたアプリケーションを起動します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77425/47c8698c-9ca3-d74e-3ae9-d195df52395b.png" width=50%>

[手順3.]「TCP/IPテストツール」でデータを受信したことを確認します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77425/79efc088-84b7-f5d8-0012-d4f818cba5a5.png" width=50%>

### 動作確認：データ送信

[手順1.]「TCP/IPテストツール」でデータを送信します。

[手順2.]ビルドしたアプリケーションがデータを受信したことを確認します。

<!----------------------------------------------------------------------------------------------------------------------------------->

以上

<!----------------------------------------------------------------------------------------------------------------------------------->