ソケット通信サーバーアプリのフレームワークを作ってみました

# はじめに

新規プロジェクトで開発を行う場合は、開発のターゲットとなるプロトコルの知識が必要になります。

初心者向けにはWebscoketサーバーの開発環境をご用意していますので以下の記事をご覧ください。

https://qiita.com/glasses_seven/items/fa9226356815e69e4076/

また、以下のページをご覧になった上で進めていただいた方が用語の理解も含めてより効率的に進められます。

https://socket-manager.github.io/document/architecture.html

ここでは以下の記事のソースを例に挙げながら`php worker`コマンドを使って新規のクラスを作っていきます。このソースを参考にしつつ、ある程度処理の流れを掴んでから進めていくのがスムーズかと思います。

https://qiita.com/glasses_seven/items/7a490dc8694348505a53/

# 環境

<dl>
    <dt>プラットフォーム</dt>
    <dd>Windows10</dd>
    <dt>統合環境</dt>
    <dd>Xampp v3.3.0</dd>
    <dt>言語</dt>
    <dd>PHP v8.2.4（v8.1.0以降）</dd>
</dl>

:::note info
ここではXAMPP環境を使っている前提で説明していますが、デフォルトとは別の場所に格納している場合やLinux等の別の環境で利用されている場合は適宜読み替えてください。
:::


# インストール

以下のコマンドでインストールできます。

```terminal:新規プロジェクト開発環境のインストール
> composer create-project socket-manager/new-project <インストール先のディレクトリ名>
```
:::note info
GitHubから直接ダウンロードされる場合は以下のページからどうぞ。
:::

https://github.com/socket-manager/new-project/

<br />
インストールが完了すると以下のディレクトリ構成になります。

```text:プロジェクトの構成
/app
    /InitClass          初期化クラス
    /UnitParameter      UNITパラメータクラス
    /ProtocolUnits      プロトコルUNIT定義クラス
    /CommandUnits       コマンドUNIT定義クラス
    /MainClass          メイン処理クラス
/logs                   ログ出力用
```

`php worker`コマンドを実行すると以下のようなUsageが表示されます。

```terminal:Usage表示
> php worker

SOCKET-MANAGER Framework 1.0.0

Usage:
  command [arguments]

 main
  Empty...
 craft
  craft:init <初期化クラス名>                     初期化クラスの生成
  craft:parameter <UNITパラメータクラス名>        UNITパラメータクラスの生成
  craft:protocol <プロトコルUNIT定義のクラス名>   プロトコルUNIT定義のクラスとステータス名Enumの生成
  craft:command <コマンドUNIT定義のクラス名>      コマンドUNIT定義のクラスとキュー／ステータス名Enumの生成
  craft:main <メイン処理のクラス名>               メイン処理クラスの生成
  craft:setting <設定ファイル名>                  設定ファイルの生成
  craft:locale <メッセージファイル名>             メッセージファイルの生成
```
:::note info
`laravel:command`コマンドに関してはLaravel環境でしか表示されません。
:::

それでは動作確認のため、以下のコマンドを実行してメイン処理クラスを作成します。
今回は`MainForTest`という名前で作成します。

以下のように表示されれば成功です。

```terminal:MainForTestクラス作成
> php worker craft:main MainForTest

[success] メイン処理クラスの生成に成功しました (MainForTest)
```

再度`php worker`を実行してみます。

```terminal:Usage表示
> php worker

SOCKET-MANAGER Framework 1.0.0

Usage:
  command [arguments]

 main
  app:main-for-test                              Command description
 craft
  craft:init <初期化クラス名>                     初期化クラスの生成
  craft:parameter <UNITパラメータクラス名>        UNITパラメータクラスの生成
  craft:protocol <プロトコルUNIT定義のクラス名>   プロトコルUNIT定義のクラスとステータス名Enumの生成
  craft:command <コマンドUNIT定義のクラス名>      コマンドUNIT定義のクラスとキュー／ステータス名Enumの生成
  craft:main <メイン処理のクラス名>               メイン処理クラスの生成
  craft:setting <設定ファイル名>                  設定ファイルの生成
  craft:locale <メッセージファイル名>             メッセージファイルの生成
```

アプリ名`main-for-test`という名前で登録されている事が確認できます。
まずは以下のコマンドを実行して今回作成したサーバーを起動した状態にしてください。

```terminal:サーバー起動
> php worker app:main-for-test 10000
```

その後PowerShellなどで以下のように`netstat`コマンドを実行して10000ポートがListenされている事が確認できれば正常にインストールされています。

```terminal:Listenポート確認
> netstat -ano | Select-String -Pattern "127.0.0.1:10000"

  TCP         127.0.0.1:10000        0.0.0.0:0              LISTENING       21536
```


# craftコマンドの種類

上記のUsageでご覧いただいた通り`craft`コマンドには現在７種類ありますが、このうち`setting`と`locale`は環境設定系のコマンドなので、それ以外の５種類が開発のメインとなるコマンドです。

新規で開発する場合はこれらのコマンドを使って新しいクラスや設定ファイルを作っていく事になります。
以降ではこの`craft`コマンドの使い方を簡単にみていきます。


### 初期化クラスの作成

`InitForTest`という名前で作成する場合、以下のように表示されれば成功です。

```terminal:初期化クラスの作成
> php worker craft:init InitForTest

[success] 初期化クラスの生成に成功しました (InitForTest)
```

```text:生成されるファイル
/app
    /InitClass
        InitForTest.php
```

初期化クラスではログ出力・シリアライザー・コマンドディスパッチャーなど、フレームワーク内で共通で利用するハンドラーを定義します。


### プロトコル処理の作成

ProtocolForTestという名前で作成する場合、以下のように表示されれば成功です。

```terminal:プロトコル処理の生成
> php worker craft:protocol ProtocolForTest

[success] プロトコルUNITクラスの生成に成功しました (ProtocolForTest)
[success] プロトコルUNITのキュー名Enumの生成に成功しました (ProtocolForTestQueueEnum)
[success] プロトコルUNITのステータス名Enumの生成に成功しました (ProtocolForTestStatusEnum)
```

```text:生成されるファイル
/app
    /ProtocolUnits
        ProtocolForTest.php
        ProtocolForTestQueueEnum.php
        ProtocolForTestStatusEnum.php
```

この部分の実装は主にソケットプロトコルに関係する送受信の処理を行うところで、実際の通信処理はこのプロトコル部で行います。


### コマンド処理の作成

CommandForTestという名前で作成する場合、以下のように表示されれば成功です。

```terminal:コマンド処理の生成
> php worker craft:command CommandForTest

[success] コマンドUNITクラスの生成に成功しました (CommandForTest)
[success] コマンドUNITのキュー名Enumの生成に成功しました (CommandForTestQueueEnum)     
[success] コマンドUNITのステータス名Enumの生成に成功しました (CommandForTestStatusEnum)
```

```text:生成されるファイル
/app
    /CommandUnits
        CommandForTest.php
        CommandForTestQueueEnum.php
        CommandForTestStatusEnum.php
```

コマンド処理はクライアント側とコマンド単位でのデータのやり取りを行って、コマンドの解釈とその内容に応じたレスポンスを返す部分になります。


### UNITパラメータクラス

ParameterForTestという名前で作成する場合、以下のように表示されれば成功です。

```terminal:UNITパラメータクラスの作成
> php worker craft:parameter ParameterForTest

[success] UNITパラメータクラスの生成に成功しました (ParameterForTest)
```

```text:生成されるファイル
/app
    /UnitParameter
        ParameterForTest.php
```

ここで言う`UNIT`というのは、プロトコル処理やコマンド処理で実行される最小単位の処理（メソッドや関数）の事を指しています。
その処理の引数として渡されるのがUNITパラメータクラスです。
そしてこのクラスは`SocketManagerParameter`クラスを継承しており、その継承メソッドを使って送受信を行ったり、クライアント接続子の操作を行ったりします。

:::note info
`SocketManagerParameter`クラスの部品については[>>`Reference`](https://socket-manager.github.io/document/reference/classes/SocketManager-Library-SocketManagerParameter.html "SocketManagerParameter Reference")をご覧ください。
:::

### メイン処理クラス

MainForTestという名前で作成する場合、以下のように表示されれば成功です。

```terminal:メイン処理クラスの作成
> php worker craft:main MainForTest

[success] メイン処理クラスの生成に成功しました (MainForTest)
```

```text:生成されるファイル
/app
    /MainClass
        MainForTest.php
```

メイン処理クラスでは上記で作成した各種クラスを`SocketManager`クラスへ初期設定するためのクラスと言えます。
例えば一般的な組み方をすれば以下のようになります。

```php:メイン処理クラスの実装
$manager = new SocketManager('localhost', 10000);

// 初期化クラスの設定
$manager->setInitSocketManager(new InitForTest());

// プロトコルUNITの設定
$manager->setProtocolUnits(new ProtocolForTest());

// コマンドUNITの設定
$manager->setCommandUnits(new CommandForTest())
```

サーバー間通信を使ってサーバー同士で連携するマルチサーバーを作成するような場合はメイン処理クラスの内容が変わってきます。

詳しく知りたい方は以下のページをご覧ください。

https://socket-manager.github.io/document/multi-server.html

マルチサーバーは以下の記事でご紹介させて頂いたデモ環境でも実装していますので、そちらのソースも併せてご覧いただければ分かり易いかと思います。

https://qiita.com/glasses_seven/items/7a490dc8694348505a53/

### 設定ファイルの作成

設定ファイルは以下のコマンドで作成できます。

```terminal:設定ファイルの作成
> php worker craft:setting test

[success] 設定ファイルの生成に成功しました (test)
```

```text:生成されるファイル
/setting
    test.php
```

作成されたソースは次の通り、空の配列のリターン値になります。

```php:setting/test.php
return [
];
```

#### 使い方

設定ファイル内の設定値を取得するヘルパー関数はLaravelと同様に使えるようにしています。
ここでは`test_key`という名前の設定名を使って値を取得する場合を例に挙げます。

設定ファイルで以下のように連想配列を定義していたとします。

```php:setting/test.php
return [
    'test_key' => 100
];
```

あとはプログラムの方で`config`ヘルパー関数を使って以下のように取得するだけです。

```php:configヘルパー関数で値を取得
$value = config('test.test_key', null);
```

変数`$value`には100を返します。
関数の第一引数にはファイル名を含めたキー名をピリオド区切りで指定します。第二引数には値が取得できなかった場合のデフォルト値を指定します。

### メッセージファイルの作成

メッセージ管理のコマンドは`setting/app.php`内の`locale`設定項目と連動します。
例えば`locale`の項目が`ja`の場合の挙動は以下の通りです。

```terminal:メッセージファイルの作成
> php worker craft:locale test

[success] メッセージファイルの生成に成功しました (test)
```

```text:生成されるファイル
/locale
    /ja
        test.php
```

`test.php`のファイルは`ja`のサブディレクトリに格納されます。
作成されたソースは次の通り、空の配列のリターン値になります。

```php:locale/ja/test.php
return [
];
```

#### 使い方

メッセージファイル内のメッセージを取得するヘルパー関数はLaravelと同様に使えるようにしています。
ここでは`test_key`という名前の設定名を使って値を取得する場合を例に挙げます。

メッセージファイルで以下のように連想配列を定義していたとします。

```php:locale/ja/test.php
return [
    'test_key' => 'テストメッセージ'
];
```

あとはプログラムの方で`__`ヘルパー関数を使って以下のように取得するだけです。

```php:__ヘルパー関数でメッセージを取得
$value = __('test.test_key');
```

変数`$value`には「テストメッセージ」という文字列を返します。

また、以下のようにプレースホルダも使えます。

```php:locale/ja/test.php
return [
    'test_key' => '私の名前は:nameです。年齢は:age歳です。'
];
```

```php:プレースホルダを使ってメッセージを取得
$value = __('test.test_key', ['name' => '山田太郎', 'age' => 20]);
```

変数`$value`には「私の名前は山田太郎です。年齢は20歳です。」という文字列を返します。


# おわりに

基本的な作り方はデモ環境を参考にして頂ければ進めやすいかと思いますが、ここではコマンドの使い方を中心にさらっと流しているだけですので、さらに詳細な内容をお知りになりたい場合は以下のページをご覧ください。

https://socket-manager.github.io/document/new-project.html

ここでご紹介したプロジェクトはLaravelと連携できます。
ご興味のある方は以下の記事をご覧ください。

https://qiita.com/glasses_seven/items/39ee921d754ed83585b3/