【プロトコルUNITクラスの実装】

はじめに

このクラスは、イベントハンドラをキューとUNITという単位に分けたモデルでプロトコル実装におけるイベントハンドラを定義するためのもので、専用のコマンドを使ってスキャルフォールディングできます。
そして、あらかじめ予約されている接続要求/データ送受信/アライブチェック/切断などのキューに任意のUNITを割り当ててプロトコルを実装していきます。

※キューとUNITの詳細については▶イベントハンドラについてのページを参照。

プロトコルUNITクラスを生成するコマンドは以下の通り。
> php worker craft:protocol ProtocolForTest

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

コマンドを実行する事でapp/ProtocolUnitsの場所に以下3つのファイルが生成されます。
・ProtocolForTest.php
IEntryUnitsインターフェイスがimplementsされたプロトコルUNITクラスです。
ここにプロトコル処理のイベントハンドラを実装します。
・ProtocolForTestQueueEnum.php
ここにプロトコル処理のキュー(イベント)名を定義します。
・ProtocolForTestStatusEnum.php
ここにプロトコル処理のステータス(UNIT)名を定義します。
このフレームワーク環境では、キュー名とステータス名をEnumで定義する事を推奨しているので、それぞれのEnumファイルに分けて出力されます。

以降では生成されたファイルの内容を見ていきます。

キュー名の定義

ProtocolForTestQueueEnum.phpのファイルは、イベントに対応するキュー名を定義するEnumファイルです。
プロトコルUNITのキュー名はあらかじめProtocolQueueEnumで予約されていますので、生成されたEnumファイルにはその内容が代入されています。
定義済みのキュー名は以下の通り。
ProtocolForTestQueueEnum::ACCEPT
クライアントからの接続要求に対するアクセプト時に呼ばれる
ProtocolForTestQueueEnum::CONNECT
当該サーバーから他のエンドポイントへ接続する時に呼ばれる
ProtocolForTestQueueEnum::RECV
通信データを受信する時に呼ばれる
ProtocolForTestQueueEnum::SEND
通信データを送信する時に呼ばれる
ProtocolForTestQueueEnum::CLOSE
切断シーケンスを走らせる時に呼ばれる
ProtocolForTestQueueEnum::ALIVE
アライブチェックを走らせる時に呼ばれる
これらのキュー名はプロトコルUNITクラスで定義されるイベントごとのUNITの集合を特定するためのものです。
今回のケースではProtocolForTestクラス内でキューとUNITの紐づけを行います。
紐づけの方法は以下の>> プロトコルUNITクラスの実装の項で説明しています。

※必ずしも全てのキューを使う必要はありませんが、オリジナルプロトコルを開発する場合は少なくともSENDキューとRECVキューが必要になります。

ステータス名の定義

ProtocolForTestStatusEnum.phpのファイルは、プロトコルUNITのステータス名を定義するEnumファイルです。
プロトコルUNITのステータス名であらかじめ予約されているものはStatusEnum::STARTだけで、生成されたEnumファイルにはその内容が代入されています。
それ以外のステータス名は自由に定義する事ができます。
定義済みのステータス名は以下の通り。
ProtocolForTestStatusEnum::START
同じキューに登録されているUNITの集合のうち一番最初に実行されるステータスです。
UNITは必ずSTARTステータスから始まるルールになっています。
これらのステータス名はプロトコルUNITクラスで定義したUNITに紐づくものなので、今回のケースでは先ほど生成したProtocolForTestクラス内でUNITとステータス名の紐づけを行います。
紐づけの方法は以下の>> プロトコルUNITクラスの実装の項で説明しています。

※ここで定義したステータス名は異なるキューで再利用が可能です。

プロトコルUNITクラスの実装

ProtocolForTest.phpのファイルにはUNIT定義を含めた各種メソッドが実装されています。
各メソッドの仕様と実装例は以下の通り。


➤キューリストの取得

フレームワークは以下のメソッドをコールして必要なキューのリストを登録します。
【メソッド】getQueueList(): array

【パラメータ】なし

【戻り値】array - キュー名のリスト
                    

例えば予約されている全てのキューを利用する場合、以下のように実装します。
protected const QUEUE_LIST = [
    ProtocolForTestQueueEnum::ACCEPT->value,   // アクセプトを処理するキュー
    ProtocolForTestQueueEnum::RECV->value,     // 受信処理のキュー
    ProtocolForTestQueueEnum::SEND->value,     // 送信処理のキュー
    ProtocolForTestQueueEnum::CLOSE->value,    // 切断処理のキュー
    ProtocolForTestQueueEnum::ALIVE->value     // アライブチェック処理のキュー
];
public function getQueueList(): array
{
    return (array)static::QUEUE_LIST;
}
                    


➤ステータスUNITリストの取得

以下のメソッドがフレームワーク内部でコールされる事によって、キューとUNITの紐づけが登録されます。
【メソッド】getUnitList(string $p_que): array

【パラメータ】
    $p_que - string - 必須 - キュー名

【戻り値】array - キューごとのUNIT集合のリスト(連想配列)
                    

フレームワーク内部ではgetQueueListメソッドで取得したキュー名を元に、getUnitListメソッドがコールされるため、引数にはキュー名が渡されます。
例えばSENDキューとRECVキューにそれぞれ2つずつUNITの集合を登録する場合、以下のように引数で与えられたキュー名に対応するリストを返す必要があります。
public function getUnitList(string $p_que): array
{
    $ret = [];

    // SENDキューのUNIT集合を登録
    if($p_que === ProtocolForTestQueueEnum::SEND->value)
    {
        // STARTステータスとUNIT(getSendStart)の紐づけ
        $ret[] = [
            'status' => ProtocolForTestStatusEnum::START->value,
            'unit' => $this->getSendStart()
        ];
        // SEND_COMPLETEステータスとUNIT(getSendComplete)の紐づけ
        $ret[] = [
            'status' => ProtocolForTestStatusEnum::SEND_COMPLETE->value,
            'unit' => $this->getSendComplete()
        ];
    }

    // RECVキューのUNIT集合を登録
    if($p_que === ProtocolForTestQueueEnum::RECV->value)
    {
        // STARTステータスとUNIT(getRecvStart)の紐づけ
        $ret[] = [
            'status' => ProtocolForTestStatusEnum::START->value,
            'unit' => $this->getRecvStart()
        ];
        // RECV_COMPLETEステータスとUNIT(getRecvComplete)の紐づけ
        $ret[] = [
            'status' => ProtocolForTestStatusEnum::RECV_COMPLETE->value,
            'unit' => $this->getRecvComplete()
        ];
    }
    .
    .
    .
    return $ret;
}
                    


➤ステータスUNITの実装

ここではgetUnitListメソッド内で紐づけたUNITを定義します。
【メソッド】<任意のメソッド名>(): Closure|string|null

【パラメータ】なし

【戻り値】
    Closure|string
    - ステータスUNITの定義: Closure
        パラメータ:
            $p_param - SocketManagerParameter - 必須 - UNITパラメータクラスのインスタンス
                各イベントハンドラで共通の引数として使用されるインスタンス。
                SocketManagerParameterクラスを継承した拡張クラスを指定する事も可能。
        戻り値: string|null
            - 遷移先がある場合: string
                遷移先のキュー名
            - 遷移先がない(終了する)場合: null
    - ステータスUNITの定義: string
        ヘルパー関数などの関数名
                    

以下ではSENDキューのUNIT集合とRECVキューのUNIT集合をポーリングUNITを使って実装した例をご紹介します。
ポーリングUNITの詳細については▶イベントハンドラについての説明をご覧ください。

【SENDキューのUNIT集合】
ここでは、スタックエリアの送信データをポーリングUNIT(getSendComplete)を使って送信する例をご紹介しています。
protected function getSendStart()
{
    return function(ParameterForTest $p_param): ?string
    {
        // スタックエリアから送信データを取得する
        $send_data = $p_param->protocol()->getSendData();

        // 送信データの設定
        $p_param->protocol()->setSendingData($send_data);

        // SEND_COMPLETEステータス(getSendComplete)のUNITへ遷移
        return ProtocolForTestStatusEnum::SEND_COMPLETE->value;
    };
}
protected function getSendComplete()
{
    return function(ParameterForTest $p_param): ?string
    {
        // データ送信
        $w_ret = $p_param->protocol()->sending();
    
        // nullの場合は送信中のためポーリングを続ける
        if($w_ret === null)
        {
            // 自身のステータス名を返してポーリングする
            $sta = $p_param->getStatusName();
            return $sta;
        }

        // nullを返して終了する
        return null;
    };
}
                            


【RECVキューのUNIT集合】
ここでは、仮に10バイトの固定データをポーリングUNIT(getRecvComplete)を使って受信する例をご紹介しています。
※実際には、実装する各プロトコルフォーマットに合わせてデータ長を取得する必要があります。
protected function getRecvStart()
{
    return function(ParameterForTest $p_param): ?string
    {
        // 受信データサイズの設定
        $p_param->protocol()->setReceivingSize(10);

        // RECV_COMPLETEステータス(getRecvComplete)のUNITへ遷移
        return ProtocolForTestStatusEnum::RECV_COMPLETE->value;
    };
}
protected function getRecvComplete()
{
    return function(ParameterForTest $p_param): ?string
    {
        // データ受信
        $w_ret = $p_param->protocol()->receiving();
    
        // nullの場合は受信中のためポーリングを続ける
        if($w_ret === null)
        {
            // 自身のステータス名を返してポーリングする
            $sta = $p_param->getStatusName();
            return $sta;
        }

        // 受信したデータを受信スタックへ設定
        $p_param->setRecvStack($w_ret);

        // nullを返して終了する
        return null;
    };
}
                            

getRecvCompleteメソッド(UNIT)内で$p_param->setRecvStack()メソッドを使って受信データを設定する事で、コマンドディスパッチャーやコマンドUNITに受信データを引き渡す事ができます。

※送受信メソッドには▶UNITパラメータクラスのページでご紹介したprotocol()のメソッドチェーンを使用しています。
※UNITパラメータクラスには▶UNITパラメータクラスのページで使用したParameterForTestを指定しています。

おわりに

プロトコルUNITクラスはプロトコルの実装を担う部分なので、通信データとして扱うデータは基本的にバイナリデータである事に注意してください。
そのため、メソッドチェーンの仲介役であるprotocol()を介して送受信を行う必要があります。

生成されたクラスのインスタンスは、メイン処理クラス内で$manager->setProtocolUnits()メソッドに引き渡す事で適用されます。
複数のプロトコルUNITクラスやメイン処理クラスを用意している場合は、サーバーの実装内容によって最適なインスタンスを動的、あるいは静的に適用する事で柔軟なサーバー構築が可能になります。