はじめに
TCP サーバーを PHP 上で安定運用するためには、ソケット生成、ノンブロッキングループ、ログ連携、UNIT との統合など、複数の要素を正しく設計する必要があります。
本記事では、SOCKET‑MANAGER Framework の SimpleSocket 機能を利用し、TCP サーバーを実装する際に押さえるべき構成要素と処理フローを体系的に整理します。
本稿で扱う主な内容は次の通りです。
本記事で理解できるポイント
- SimpleSocketGenerator を用いた TCP サーバーの初期化プロセス
- メイン処理クラス(エントリポイント)の役割と生成手順
- サーバー識別子・コマンド説明文など CLI 向けメタ情報の設定方法
- ジェネレータ引数(ホスト・ポート・バッファ設定など)の仕様とデフォルト値
- cycleDriven によるノンブロッキングループの動作
- ログライターの登録方法と運用上の注意点
- SocketManager の UNIT パラメータとの連携方法
- 常時実行コールバック(KeepRunning)の実装例
- コアインスタンス生成(generate)のタイミングと戻り値の扱い
本記事の目的
TCP サーバー構築に必要な設定項目と処理フローを、実装コードとともに段階的に理解できる状態を目指します。
SimpleSocket 単体での IPC 利用から、SocketManager と連携した高度な処理まで、実運用を想定した設計の基礎を習得できます。
メイン処理クラスの生成
メイン処理クラスを生成するコマンドは以下の通り。
> php worker simple:tcp-server MainForTest
[success] TCPサーバーのメイン処理クラス生成に成功しました (MainForTest)
生成されたクラスはapp/MainClassの場所にMainForTest.phpというファイル名で格納されます。
新しいメイン処理クラスが増えるとUsageのmainカテゴリに項目が追加されます。
> php worker
SOCKET-MANAGER Framework 1.X.X
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 <メッセージファイル名> メッセージファイルの生成
runtime
runtime:init <初期化クラス名> 初期化クラスの生成
runtime:parameter <UNITパラメータクラス名> UNITパラメータクラスの生成
runtime:units <ランタイムUNIT定義のクラス名> ランタイムUNIT定義のクラスとキュー/ステータス名Enumの生成
runtime:main <メイン処理のクラス名> メイン処理クラスの生成
simple
simple:tcp-server <メイン処理のクラス名> TCPサーバー用メイン処理クラスの生成
simple:tcp-client <メイン処理のクラス名> TCPクライアント用メイン処理クラスの生成
simple:udp <メイン処理のクラス名> UDP通信用メイン処理クラスの生成
上記Usageのようにmainカテゴリにapp:main-for-testという名前で追加されているのが確認できます。
サーバー識別子
メイン処理クラスが生成されると以下のプロパティが確認できます。
/**
* @var string $identifer アプリケーション識別子
*/
protected string $identifer = 'app:main-for-test {port_no?}';
ここではコマンドラインフォーマットを定義しています。
app:main-for-testの部分がサーバーアプリケーション(プロセス)名で、{port_no?}の部分がコマンドパラメータ名であり、デフォルトで定義される内容です。
そしてサーバーアプリケーション名や、コマンドパラメータは自由にカスタマイズできます。
例えばコマンドパラメータにホスト名を追加したい場合は以下のようにします。
/**
* @var string $identifer アプリケーション識別子
*/
protected string $identifer = 'app:main-for-test {host_name} {port_no?}';
port_noの後ろに付いている"はてな(?)マーク"は省略可能である事を表しています。
ちなみに"はてな(?)マーク"は最後尾の連続したパラメータにしか指定できません。
例えば{host_name?} {port_no?}と書く事はできますが{host_name?} {port_no}と書く事はできません。
なお、メイン処理クラス内では$this->getParameter('port_no')のように書く事でパラメータを取得できます。
省略可能なパラメータが省略されている時はnullが返却されます。
コマンド説明文
メイン処理クラスが生成されると以下のプロパティが確認できます。
/**
* @var string $description コマンド説明
*/
protected string $description = 'Command description';
ここではUsageを表示した時のサーバーコマンドの説明文を定義しています。
初期状態ではCommand descriptionとなっているので、これをプロパティで変更できます。
例えば以下のように変更すると
/**
* @var string $description コマンド説明
*/
protected string $description = 'テスト用のサーバー';
Usageでは以下のように表示されます。
> php worker
SOCKET-MANAGER Framework 1.X.X
Usage:
command [arguments]
main
app:main-for-test テスト用のサーバー
craft
craft:init <初期化クラス名> 初期化クラスの生成
craft:parameter <UNITパラメータクラス名> UNITパラメータクラスの生成
craft:protocol <プロトコルUNIT定義のクラス名> プロトコルUNIT定義のクラスとステータス名Enumの生成
craft:command <コマンドUNIT定義のクラス名> コマンドUNIT定義のクラスとキュー/ステータス名Enumの生成
craft:main <メイン処理のクラス名> メイン処理クラスの生成
craft:setting <設定ファイル名> 設定ファイルの生成
craft:locale <メッセージファイル名> メッセージファイルの生成
runtime
runtime:init <初期化クラス名> 初期化クラスの生成
runtime:parameter <UNITパラメータクラス名> UNITパラメータクラスの生成
runtime:units <ランタイムUNIT定義のクラス名> ランタイムUNIT定義のクラスとキュー/ステータス名Enumの生成
runtime:main <メイン処理のクラス名> メイン処理クラスの生成
simple
simple:tcp-server <メイン処理のクラス名> TCPサーバー用メイン処理クラスの生成
simple:tcp-client <メイン処理のクラス名> TCPクライアント用メイン処理クラスの生成
simple:udp <メイン処理のクラス名> UDP通信用メイン処理クラスの生成
設定可能項目
このメイン処理クラスであらかじめ設定可能な項目は、SimpleSocketGenerator クラスのコンストラクタ引数です。
各引数の設定方法には以下の3種類があります。
- ①メイン処理クラス内で直接指定
- この項目でご紹介する方法です。
- ②コマンドラインから取得
- 上記の>> サーバー識別子の項目でご紹介した方法です。
- ③設定ファイル
- 以下の「設定ファイルの管理」ページでご紹介している方法です。
SimpleSocketGeneratorクラスの引数
メイン処理クラスの実行(exec)メソッドの冒頭部分には SimpleSocketGenerator クラスのインスタンス生成部分である以下のコードが含まれています。
public function exec()
{
// 引数の取得
$port_no = $this->getParameter('port_no');
/***********************************************************************
* シンプルソケットジェネレータの初期設定
*
* ジェネレータインスタンスの生成や各種設定をここで実行します
**********************************************************************/
$generator = new SimpleSocketGenerator(SimpleSocketTypeEnum::TCP_SERVER, 'localhost', $port_no);
・
・
・
}
この SimpleSocketGenerator クラスの引数の内訳は次の通りです。
- シンプルソケットタイプ(今回の場合は SimpleSocketTypeEnum.TCP_SERVER)
- ホスト名(デフォルト:127.0.0.1)
- ポート番号(デフォルト:15000)
- ダウンタイム(デフォルト:100ms)
- 送受信バッファサイズ(デフォルト:255)
- バッファスタック件数(デフォルト:1)
- 言語コード(デフォルト:'ja')
- 接続制限数(デフォルト:10)
シンプルソケットタイプを除くこれら6つの項目をデフォルト値で指定すると以下のようになります。
$generator = new SimpleSocketGenerator( SimpleSocketTypeEnum::TCP_SERVER, '127.0.0.1', 15000, 100, 255, 1, 'ja', 10 );
$generator = new SimpleSocketGenerator( SimpleSocketTypeEnum::TCP_SERVER );
$generator = new SimpleSocketGenerator( SimpleSocketTypeEnum::TCP_SERVER, null, null, null, null, null, null, null );
御覧の通り、直接値を指定する事もできますが、引数を省略したり、nullを指定する事で自動的にデフォルト値が適用される仕組みになっています。
cycleDrivenメソッドの引数
メイン処理クラスの実行(exec)メソッド末尾のソースにはノンブロッキングループ内でcycleDrivenメソッドが呼ばれている箇所があります。
public function exec()
{
・
・
・
// ノンブロッキングループ
while(true)
{
// 周期ドリブン
$ret = $generator->cycleDriven();
if($ret === false)
{
goto finish;
}
}
・
・
・
}
このcycleDrivenメソッドの引数の内訳は次の通りです。
- 周期インターバルタイム(デフォルト:2000μs)
この項目をデフォルト値で指定すると以下のようになります。
$generator->cycleDriven( 2000 );
$generator->cycleDriven();
御覧の通り、直接値を指定する事もできますが、引数を省略しても自動的にデフォルト値が適用される仕組みになっています。
なお、ここでは SimpleSocketGenerator クラスの引数のようにnullを指定する事はできません。
ログライターの登録
メイン処理クラスの実行(exec)メソッドの中ほどに以下の定義があります。
public function exec()
{
・
・
・
/**
* ログライターの登録(任意)
*
* ログライターが使いたい場合に$generator->setLogWriter()メソッドで登録します
* SocketManager初期化クラスのログライターをそのままお使い頂けます
*/
$generator->setLogWriter
(
/**
* ログライター
*
* @param string $p_level ログレベル(debug、info、notice、warning、errorなど)
* @param array $p_param 連想配列形式のログ内容
* @return void
*/
function(string $p_level, array $p_param): void
{
}
);
・
・
・
}
function 内に処理を定義する事もできますが、SocketManager などであらかじめ作成しておいた初期化クラスを使って登録する事もできます。
public function exec()
{
・
・
・
// 初期化クラスのインスタンスを生成
$init = new InitForTest();
・
・
・
/**
* ログライターの登録(任意)
*
* ログライターが使いたい場合に$generator->setLogWriter()メソッドで登録します
* SocketManager初期化クラスのログライターをそのままお使い頂けます
*/
$generator->setLogWriter( $init->getLogWriter() );
・
・
・
}
SocketManager との連携
メイン処理クラスの実行(exec)メソッドの中ほどに以下のコメント部分があります。
public function exec()
{
・
・
・
/**
* SocketManagerとの連携(任意)
*
* UNITパラメータインスタンスの"simple_socket"プロパティにシンプルソケットインスタンスが設定され
* コマンドディスパッチャーやステータスUNIT内で使えるようになります
*
* $generator->setUnitParameter()メソッドでUNITパラメータクラスを設定します
*/
・
・
・
}
あらかじめ作成しておいたSocketManagerなどの UNIT パラメータクラスのインスタンスを、ここで以下のように引き渡して UNIT 処理と連携できます。
public function exec()
{
・
・
・
// UNITパラメータのインスタンスを生成
$param = new ParameterForTest();
・
・
・
/**
* SocketManagerとの連携(任意)
*
* UNITパラメータインスタンスの"simple_socket"プロパティにシンプルソケットインスタンスが設定され
* コマンドディスパッチャーやステータスUNIT内で使えるようになります
*/
$generator->setUnitParameter( $param );
・
・
・
}
ここで設定した UNIT パラメータインスタンスを使って、UNIT 処理やコマンドディスパッチャーで利用できるようになります。
常時実行コールバックの登録
メイン処理クラスの実行(exec)メソッドの中ほどに以下の定義があります。
public function exec()
{
・
・
・
/**
* 常時実行処理の登録(任意)
*
* 常時実行処理がある場合に$generator->setKeepRunning()メソッドで登録します
*/
$generator->setKeepRunning
(
/**
* 常時実行処理
*
* @param ISimpleSocketTcpServer $p_simple_socket シンプルソケットインスタンス
* @param mixed[] $p_argv 可変引数(setKeepRunningメソッドの第二引数以降のものが渡される)
* @return void
*/
function(ISimpleSocketTcpServer $p_simple_socket): void
{
}
);
・
・
・
}
以下のように function 内に処理を定義する事もできますが、ヘルパー関数などの関数名を指定する事もできます。
public function exec()
{
・
・
・
/**
* 常時実行処理の登録(任意)
*
* 常時実行処理がある場合に$generator->setKeepRunning()メソッドで登録します
*/
$generator->setKeepRunning
(
/**
* 常時実行処理
*
* @param ISimpleSocketTcpServer $p_simple_socket シンプルソケットインスタンス
* @param mixed[] $p_argv 可変引数(setKeepRunningメソッドの第二引数以降のものが渡される)
* @return void
*/
function(ISimpleSocketTcpServer $p_simple_socket): void
{
// データ受信
$cid = null;
$data = $p_simple_socket->recv($cid);
// データのログ出力
$p_simple_socket->logWriter('debug', ['受信データ' => $data]);
// データ送信
$p_simple_socket->send($cid, $data);
}
);
・
・
・
}
コアインスタンスの生成
メイン処理クラスの実行(exec)メソッドの中ほどに以下の定義があります。
public function exec()
{
・
・
・
/**
* シンプルソケットインスタンスの生成
*
* この手続きが行われた時点でインスタンスが生成され有効になります
*/
$w_ret = $generator->generate();
if($w_ret === null)
{
goto finish;
}
・
・
・
}
シンプルソケットの各インターフェース用コアインスタンスが、ここで初めて生成されると同時に有効になります。
generate メソッド実行時の戻り値(ここでは $w_ret 変数)には、setKeepRunning メソッドで設定するクロージャ、または関数の引数で渡される ISimpleSocketTcpServer インターフェースと同じものが設定されます(インスタンスの使い方は同じです)。
例えば初回起動時にログ出力する場合、以下のように書く事ができます。
$interface = $generator->generate();
if($interface === null)
{
goto finish;
}
$interface->logWriter('debug', ['TCPサーバー' => 'STARTUP']);
おわりに
これまで見てきたように、メイン処理クラスではコマンドライン/設定項目/ログライター/SocketManager連携/常時実行コールバックを自由にデザインすることができます。
シンプルソケット機能単体でも IPC(サーバー間通信)として機能しますが、SocketManager と連携させる事で IPC として利用する事もできます。