【新規開発環境】
はじめに
新規プロジェクトで開発を行う場合は、開発のターゲットとなるプロトコルの知識が必要になります。
また▶アーキテクチャのページをご覧になった上で進めていただいた方が用語の理解も含めてより効率的に進められます。
ここではデモ版のソースを例に挙げながら
デモ版のソースを参考にしつつ、ある程度処理の流れを掴んでから進めていくのがスムーズかと思います。
また▶アーキテクチャのページをご覧になった上で進めていただいた方が用語の理解も含めてより効率的に進められます。
ここではデモ版のソースを例に挙げながら
php workerコマンドを使って新規のクラスを作っていきます。デモ版のソースを参考にしつつ、ある程度処理の流れを掴んでから進めていくのがスムーズかと思います。
インストール
以下のコマンドでインストールできます。
※GitHubから直接ダウンロードする場合は>> こちらからどうぞ。
インストールが完了すると以下のディレクトリ構成になります。
それでは動作確認のため、以下のコマンドを実行してメイン処理クラスを作成します。
今回は
以下のように表示されれば成功です。
再度
アプリ名
まずは以下のコマンドを実行して今回作成したサーバーを起動した状態にしてください。
その後PowerShellなどで以下のように
以降の説明ではデモの中で一番オーソドックスなWebsocket版チャットサーバーを例に取り上げて話を進めます。
※GitHubから直接ダウンロードする場合は>> こちらからどうぞ。
> composer create-project socket-manager/new-project <インストール先のディレクトリ名>
インストールが完了すると以下のディレクトリ構成になります。
/app
/InitClass 初期化クラス
/UnitParameter UNITパラメータクラス
/ProtocolUnits プロトコルUNIT定義クラス
/CommandUnits コマンドUNIT定義クラス
/MainClass メイン処理クラス
/logs ログ出力用
/setting 設定ファイル用
php workerコマンドを実行すると以下のような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 <メッセージファイル名> メッセージファイルの生成
※laravel:commandコマンドに関してはLaravel環境でしか表示されません。それでは動作確認のため、以下のコマンドを実行してメイン処理クラスを作成します。
今回は
MainForTestという名前で作成します。以下のように表示されれば成功です。
> php worker craft:main MainForTest
[success] メイン処理クラスの生成に成功しました (MainForTest)
再度
php workerを実行してみます。
> 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という名前で登録されている事が確認できます。まずは以下のコマンドを実行して今回作成したサーバーを起動した状態にしてください。
> php worker app:main-for-test 10000
その後PowerShellなどで以下のように
netstatコマンドを実行して10000ポートがListenされている事が確認できれば正常にインストールされています。
> netstat -ano | Select-String -Pattern "127.0.0.1:10000"
TCP 127.0.0.1:10000 0.0.0.0:0 LISTENING 21536
以降の説明ではデモの中で一番オーソドックスなWebsocket版チャットサーバーを例に取り上げて話を進めます。
初期化クラスの実装
初期化クラスのソースは以下のコマンドで作成できます。
それでは以下のWebsocket版チャットサーバーのソースをご覧ください。
ここで必須になる実装は大きく分けると以下の7点になります。
各々のメソッドはnullを返す事で無効化できます。
InitForTestという名前で作成する場合、以下のように表示されれば成功です。
> php worker craft:init InitForTest
[success] 初期化クラスの生成に成功しました (InitForTest)
/app
/InitClass
InitForTest.php
それでは以下のWebsocket版チャットサーバーのソースをご覧ください。
class InitForWebsocket implements IInitSocketManager
{
.
.
.
/**
* ログライターの取得
*
* nullを返す場合は無効化(但し、ライブラリ内部で出力されているエラーメッセージも出力されない)
*
* @return mixed "function(string $p_level, array $p_param): void" or null(ログ出力なし)
*/
public function getLogWriter()
{
return function(string $p_level, array $p_param)
{
.
.
.
};
}
/**
* シリアライザーの取得
*
* nullを返す場合は無効化となる。
* エラー発生時はUnitExceptionクラスで例外をスローして切断する。
*
* @return mixed "function(mixed $p_data): mixed" or null(変更なし)
*/
public function getSerializer()
{
return function($p_data)
{
.
.
.
};
}
/**
* アンシリアライザーの取得
*
* nullを返す場合は無効化となる。
* エラー発生時はUnitExceptionクラスで例外をスローして切断する。
*
* @return mixed "function(mixed $p_data): mixed" or null(変更なし)
*/
public function getUnserializer()
{
return function($p_data)
{
.
.
.
};
}
/**
* コマンドディスパッチャーの取得
*
* 受信データからコマンドを解析して返す
*
* コマンドUNIT実行中に受信データが溜まっていた場合でもコマンドUNITの処理が完了するまで
* 待ってから起動されるため処理競合の調停役を兼ねる
*
* nullを返す場合は無効化となる。エラー発生時はUnitExceptionクラスで例外をスローして切断する。
*
* @return mixed "function(SocketManagerParameter $p_param, mixed $p_dat): ?string" or null(変更なし)
*/
public function getCommandDispatcher()
{
return function(ParameterForWebsocket $p_param, $p_dat): ?string
{
.
.
.
};
}
/**
* 緊急停止時のコールバックの取得
*
* 例外等の緊急切断時に実行される。nullを返す場合は無効化となる。
*
* @return mixed "function(SocketManagerParameter $p_param)"
*/
public function getEmergencyCallback()
{
return function(ParameterForWebsocket $p_param)
{
.
.
.
};
}
/**
* UNITパラメータインスタンスの取得
*
* nullの場合はSocketManagerParameterのインスタンスが適用される
*
* @return ?SocketManagerParameter SocketManagerParameterクラスのインスタンス(※1)
* @see:RETURN (※1)当該クラス、あるいは当該クラスを継承したクラスも指定可
*/
public function getUnitParameter(): ?SocketManagerParameter
{
.
.
.
}
}
ここで必須になる実装は大きく分けると以下の7点になります。
各々のメソッドはnullを返す事で無効化できます。
- ■IInitSocketManagerのインプリメント
- ライブラリが以下に記載のハンドラーの取り込み時に利用する。
- ■getLogWriterメソッドの実装
-
ライブラリ内でのログ出力に利用。
ハンドラーにする事でerror_log関数やフレームワークのLogger等の利用が可能。 - ■getSerializerメソッドの実装
-
送受信スタック内ペイロードデータのシリアライズ時に利用する。
今回のデモではJSONエンコード時に利用。 - ■getUnserializerメソッドの実装
-
送受信スタック内ペイロードデータのアンシリアライズ時に利用する。
今回のデモではJSONデコード時に利用。 - ■getCommandDispatcherメソッドの実装
- クライアントから受信したペイロード部を解析して対応するコマンドキュー名を返す。
- ■getEmergencyCallbackメソッドの実装
- 相手先による切断・コマンドディスパッチャーでの例外発生・アライブチェックタイムアウトなどの緊急停止時にコールされる。
- ■getUnitParameterメソッドの実装
- UNITパラメータとして利用される。グローバルエリアの管理が必要ないのであれば
SocketManagerParameterクラスをそのまま使っても構わない。
プロトコル部の実装
プロトコル部のソースは以下のコマンドで作成できます。
この部分の実装は主にソケットプロトコルに関係する送受信の処理を行うところで、実際の通信処理はこのプロトコル部で行います。
それでは以下のWebsocket版チャットサーバーのソースをご覧ください。
ここで必須になる実装は大きく分けると以下の4点になります。
※プロトコル部で利用するキュー名は全てライブラリ側の
必ずしも全てのキュー名を使用する必要はありませんがサーバーサイドのWebsocketの規約では
ProtocolForTestという名前で作成する場合、以下のように表示されれば成功です。
> php worker craft:protocol ProtocolForTest
[success] プロトコルUNITクラスの生成に成功しました (ProtocolForTest)
[success] プロトコルUNITのキュー名Enumの生成に成功しました (ProtocolForTestQueueEnum)
[success] プロトコルUNITのステータス名Enumの生成に成功しました (ProtocolForTestStatusEnum)
/app
/ProtocolUnits
ProtocolForTest.php
ProtocolForTestQueueEnum.php
ProtocolForTestStatusEnum.php
この部分の実装は主にソケットプロトコルに関係する送受信の処理を行うところで、実際の通信処理はこのプロトコル部で行います。
それでは以下のWebsocket版チャットサーバーのソースをご覧ください。
class ProtocolForWebsocket implements IEntryUnits
{
// キューリスト
protected const QUEUE_LIST = [
ProtocolQueueEnum::ACCEPT->value, // アクセプトを処理するキュー
ProtocolQueueEnum::RECV->value, // 受信処理のキュー
ProtocolQueueEnum::SEND->value, // 送信処理のキュー
ProtocolQueueEnum::CLOSE->value, // 切断処理のキュー
ProtocolQueueEnum::ALIVE->value // アライブチェック処理のキュー
];
/**
* キューリストの取得
*
* @return array キュー名のリスト
*/
public function getQueueList(): array
{
return self::QUEUE_LIST;
}
/**
* ステータスUNITリストの取得
*
* @param string $p_que キュー名
* @return array キュー名に対応するUNITリスト
*/
public function getUnitList(string $p_que): array
{
$ret = [];
if($p_que === ProtocolQueueEnum::ACCEPT->value)
{
$ret[] = [
'status' => ProtocolStatusEnumForWebsocket::START->value,
'unit' => $this->getAcceptStart()
];
$ret[] = [
'status' => ProtocolStatusEnumForWebsocket::CREATE->value,
'unit' => $this->getAcceptCreate()
];
$ret[] = [
'status' => ProtocolStatusEnumForWebsocket::SEND->value,
'unit' => $this->getAcceptSend()
];
}
.
.
.
return $ret;
}
.
.
.
/**
* ステータス名: START
*
* 処理名:受信
*
* @param ParameterForWebsocket $p_param UNITパラメータ
* @return ?string 遷移先のステータス名
*/
protected function getAcceptStart()
{
return function(ParameterForWebsocket $p_param): ?string
{
.
.
.
};
}
.
.
.
}
ここで必須になる実装は大きく分けると以下の4点になります。
- ■IEntryUnitsのインプリメント
- ライブラリがプロトコル部の取り込み時に利用する
- ■getQueueListメソッドの実装
- プロトコル部で利用するキューのリストを返す
- ■getUnitListメソッドの実装
- 引数のキュー名に対応するUNITのリストを返す
- ■各UNITの実装
getUnitListメソッドで返している定義済みのUNIT処理を実装
getAcceptStartメソッドでクロージャとして返していますが、グローバル関数名を指定する事も可能です。※プロトコル部で利用するキュー名は全てライブラリ側の
ProtocolQueueEnumクラスで予約されています。
enum ProtocolQueueEnum: string
{
/**
* @var アクセプト時のキュー名
*/
case ACCEPT = 'accept';
/**
* @var コネクション時のキュー名
*/
case CONNECT = 'connect';
/**
* @var 受信時のキュー名
*/
case RECV = 'recv';
/**
* @var 送信時のキュー名
*/
case SEND = 'send';
/**
* @var 切断時のキュー名
*/
case CLOSE = 'close';
/**
* @var アライブチェック時のキュー名
*/
case ALIVE = 'alive';
}
必ずしも全てのキュー名を使用する必要はありませんがサーバーサイドのWebsocketの規約では
CONNECTを除いて全てルール化されています。
コマンド部の実装
コマンド部のソースは以下のコマンドで作成できます。
プロトコル部が通信を担う部分であるのに対して、コマンド部ではクライアント側とコマンド単位でのデータのやり取りを行って、コマンドの解釈とその内容に応じたレスポンスを返す部分になります。
まずは以下のWebsocket版チャットサーバーのソースをご覧ください。
構成内容はプロトコル部とほぼ同じで、必須になる実装も大きく分けると以下の4点になります。
※コマンド部で利用するキュー名は自由定義です。特に予約されているものもありません。
CommandForTestという名前で作成する場合、以下のように表示されれば成功です。
> php worker craft:command CommandForTest
[success] コマンドUNITクラスの生成に成功しました (CommandForTest)
[success] コマンドUNITのキュー名Enumの生成に成功しました (CommandForTestQueueEnum)
[success] コマンドUNITのステータス名Enumの生成に成功しました (CommandForTestStatusEnum)
/app
/CommandUnits
CommandForTest.php
CommandForTestQueueEnum.php
CommandForTestStatusEnum.php
プロトコル部が通信を担う部分であるのに対して、コマンド部ではクライアント側とコマンド単位でのデータのやり取りを行って、コマンドの解釈とその内容に応じたレスポンスを返す部分になります。
まずは以下のWebsocket版チャットサーバーのソースをご覧ください。
/**
* コマンドUNIT登録クラス
*
* IEntryUnitsインターフェースをインプリメントする
*/
class CommandForWebsocket implements IEntryUnits
{
// キューリスト
protected const QUEUE_LIST = [
CommandQueueEnumForWebsocket::ENTRANCE->value, // entranceコマンドを処理するキュー
CommandQueueEnumForWebsocket::MESSAGE->value, // messageコマンドを処理するキュー
CommandQueueEnumForWebsocket::EXIT->value, // exitコマンドを処理するキュー
CommandQueueEnumForWebsocket::CLOSE->value, // closeコマンドを処理するキュー
CommandQueueEnumForWebsocket::PRIVATE->value, // privateコマンドを処理するキュー
CommandQueueEnumForWebsocket::PRIVATE_RESULT->value, // private-resultコマンドを処理するキュー
CommandQueueEnumForWebsocket::USERSEARCH_RESULT->value // usersearch-resultコマンドを処理するキュー
];
/**
* キューリストの取得
*
* @return array キュー名のリスト
*/
public function getQueueList(): array
{
return static::QUEUE_LIST;
}
/**
* ステータスUNITリストの取得
*
* @param string $p_que キュー名
* @return array キュー名に対応するUNITリスト
*/
public function getUnitList(string $p_que): array
{
$ret = [];
if($p_que === CommandQueueEnumForWebsocket::ENTRANCE->value)
{
$ret[] = [
'status' => CommandStatusEnumForWebsocket::START->value,
'unit' => $this->getEntranceStart()
];
}
.
.
.
}
.
.
.
/**
* ステータス名: START
*
* 処理名:入室処理開始
*
* @param ParameterForWebsocket $p_param UNITパラメータ
* @return ?string 遷移先のステータス名
*/
protected function getEntranceStart()
{
return function(ParameterForWebsocket $p_param): ?string
{
.
.
.
}
}
.
.
.
}
構成内容はプロトコル部とほぼ同じで、必須になる実装も大きく分けると以下の4点になります。
- ■IEntryUnitsのインプリメント
- ライブラリがコマンド部の取り込み時に利用する
- ■getQueueListメソッドの実装
- コマンド部で利用するキューのリストを返す
- ■getUnitListメソッドの実装
- 引数のキュー名に対応するUNITのリストを返す
- ■各UNITの実装
getUnitListメソッドで返している定義済みのUNIT処理を実装
getEntranceStartメソッドでクロージャとして返していますが、グローバル関数名を指定する事も可能です。※コマンド部で利用するキュー名は自由定義です。特に予約されているものもありません。
予約済ステータス
予約されているものは以下ライブラリ側のEnum定義のみです。
プロトコル/コマンド部共通で利用するSTART(処理開始)のみになります。
enum StatusEnum: string
{
/**
* @var UNITの処理開始時のステータス名
*/
case START = 'start';
}
プロトコル/コマンド部共通で利用するSTART(処理開始)のみになります。
UNITパラメータの実装
UNITパラメータクラスのソースは以下のコマンドで作成できます。
ここで述べているUNITパラメータというのはプロトコル部やコマンド部で定義されているUNITの引数の部分です。
上記のプロトコル部やコマンド部の例でいえば
この引数には
グローバルエリアを使う必要がなければ特に実装する事は何もありませんが、新規で作成した場合は以下のようにUNIT処理の引数の部分を必要に応じて置き替えてください。
ParameterForTestという名前で作成する場合、以下のように表示されれば成功です。
> php worker craft:parameter ParameterForTest
[success] UNITパラメータクラスの生成に成功しました (ParameterForTest)
/app
/UnitParameter
ParameterForTest.php
ここで述べているUNITパラメータというのはプロトコル部やコマンド部で定義されているUNITの引数の部分です。
上記のプロトコル部やコマンド部の例でいえば
ParameterForWebsocket $p_paramの部分になります。この引数には
SocketManagerParameterクラスを継承しているものであれば何を指定しても構いません。グローバルエリアを使う必要がなければ特に実装する事は何もありませんが、新規で作成した場合は以下のようにUNIT処理の引数の部分を必要に応じて置き替えてください。
protected function getAcceptStart()
{
return function(SocketManagerParameter $p_param): ?string
{
$p_param->logWriter('debug', ['ACCEPT' => 'START']);
return null;
};
}
protected function getAcceptStart()
{
return function(ParameterForTest $p_param): ?string
{
$p_param->logWriter('debug', ['ACCEPT' => 'START']);
return null;
};
}
メイン処理クラスの実装
メイン処理クラスのソースは以下のコマンドで作成できます。
生成されたソースは次の通り。
ここで必須になる実装は初期設定ブロックである以下の3点になります。
MainForTestという名前で作成する場合、以下のように表示されれば成功です。
> php worker craft:main MainForTest
[success] メイン処理クラスの生成に成功しました (MainForTest)
/app
/MainClass
MainForTest.php
生成されたソースは次の通り。
class MainForTest extends Console
{
/**
* @var string $identifer サーバー識別子
*/
protected string $identifer = 'app:main-for-test {port_no?}';
/**
* @var string $description コマンド説明
*/
protected string $description = 'Command description';
/**
* サーバー起動
*
*/
public function exec()
{
// 引数の取得
$port_no = $this->getParameter('port_no');
// ソケットマネージャーのインスタンス設定
$manager = new SocketManager('localhost', $port_no);
/***********************************************************************
* ソケットマネージャーの初期設定
*
* プロトコル/コマンド部等で実装したクラスのインスタンスをここで設定します
**********************************************************************/
/**
* 初期化クラスの設定
*
* $manager->setInitSocketManager()メソッドで初期化クラスを設定します
*/
/**
* プロトコルUNITの設定
*
* $manager->setProtocolUnits()メソッドでプロトコルUNITクラスを設定します
*/
/**
* コマンドUNITの設定
*
* $manager->setCommandUnits()メソッドでコマンドUNITクラスを設定します
*/
/***********************************************************************
* ソケットマネージャーの実行
*
* ポートの待ち受け処理や周期ドリブン処理を実行します
**********************************************************************/
// リッスンポートで待ち受ける
$ret = $manager->listen();
if($ret === false)
{
goto finish; // リッスン失敗
}
// ノンブロッキングループ
while(true)
{
// 周期ドリブン
$ret = $manager->cycleDriven();
if($ret === false)
{
goto finish;
}
}
finish:
// 全接続クローズ
$manager->shutdownAll();
}
}
ここで必須になる実装は初期設定ブロックである以下の3点になります。
- ■初期化クラスの設定
- 初期化クラスのインスタンスを生成し、
SocketManagerクラスのsetInitSocketManagerメソッドにインスタンスを引き渡す - ■プロトコルUNITの設定
- プロトコルUNITクラスのインスタンスを生成し、
SocketManagerクラスのsetProtocolUnitsメソッドにインスタンスを引き渡す - ■コマンドUNITの設定
- コマンドUNITクラスのインスタンスを生成し、
SocketManagerクラスのsetCommandUnitsメソッドにインスタンスを引き渡す
設定ファイルの作成
設定ファイルは以下のコマンドで作成できます。
作成されたソースは次の通り、空の配列のリターン値になります。
ここでは
設定ファイルで以下のように連想配列を定義していたとします。
あとはプログラムの方で
変数
関数の第一引数にはファイル名を含めたキー名をピリオド区切りで指定します。第二引数には値が取得できなかった場合のデフォルト値を指定します。
testという名前で作成する場合、以下のように表示されれば成功です。
> php worker craft:setting test
[success] 設定ファイルの生成に成功しました (test)
/setting
test.php
作成されたソースは次の通り、空の配列のリターン値になります。
return [
];
使い方
設定ファイル内の設定値を取得するヘルパー関数はLaravelと同様に使えるようにしています。ここでは
test_keyという名前の設定名を使って値を取得する場合を例に挙げます。設定ファイルで以下のように連想配列を定義していたとします。
return [
'test_key' => 100
];
あとはプログラムの方で
configヘルパー関数を使って以下のように取得するだけです。
$value = config('test.test_key', null);
変数
$valueには100を返します。関数の第一引数にはファイル名を含めたキー名をピリオド区切りで指定します。第二引数には値が取得できなかった場合のデフォルト値を指定します。
メッセージファイルの作成
メッセージ管理のコマンドは
例えば
作成されたソースは次の通り、空の配列のリターン値になります。
ここでは
メッセージファイルで以下のように連想配列を定義していたとします。
あとはプログラムの方で
変数
また、以下のようにプレースホルダも使えます。
変数
setting/app.php内のlocale設定項目と連動します。例えば
localeの項目がjaの場合の挙動は以下の通りです。
> php worker craft:locale test
[success] メッセージファイルの生成に成功しました (test)
/locale
/ja
test.php
test.phpのファイルはjaのサブディレクトリに格納されます。作成されたソースは次の通り、空の配列のリターン値になります。
return [
];
使い方
メッセージファイル内のメッセージを取得するヘルパー関数はLaravelと同様に使えるようにしています。ここでは
test_keyという名前の設定名を使って値を取得する場合を例に挙げます。メッセージファイルで以下のように連想配列を定義していたとします。
return [
'test_key' => 'テストメッセージ'
];
あとはプログラムの方で
__ヘルパー関数を使って以下のように取得するだけです。
$value = __('test.test_key');
変数
$valueには「テストメッセージ」という文字列を返します。また、以下のようにプレースホルダも使えます。
return [
'test_key' => '私の名前は:nameです。年齢は:age歳です。'
];
$value = __('test.test_key', ['name' => '山田太郎', 'age' => 20]);
変数
$valueには「私の名前は山田太郎です。年齢は20歳です。」という文字列を返します。
おわりに
動作確認をしながら進めるのであれば以下の順に進めるのが効率的かと思います。
そしてプロトコル部とコマンド部のクラスは初期状態のままでいいのでメイン処理クラスで設定だけ済ませておいた上でプロトコル部、あるいはコマンド部のUNITを作成するたびに動作確認していくのが順当かと思います。
UNITパラメータクラスの方はUNIT処理の作成中にグローバルエリアの管理が必要になった時に実装を進める形でよいかと思います。
- 1.初期化クラスの実装
- 2.メイン処理クラスの実装
- 3.プロトコル部の実装
- 4.コマンド部の実装
そしてプロトコル部とコマンド部のクラスは初期状態のままでいいのでメイン処理クラスで設定だけ済ませておいた上でプロトコル部、あるいはコマンド部のUNITを作成するたびに動作確認していくのが順当かと思います。
UNITパラメータクラスの方はUNIT処理の作成中にグローバルエリアの管理が必要になった時に実装を進める形でよいかと思います。