【UDP 通信の実装】

はじめに

本記事では、SOCKET‑MANAGER Framework の SimpleSocket 機能を使った UDP 通信(サーバー / クライアント兼用)の実装手順を分かりやすく解説します。
ジェネレータ(SimpleSocketGenerator)の初期設定、ノンブロッキングループの運用、ログ/UNIT連携、常時実行コールバックの実装例まで、実運用を見据えたコード例と運用ポイントを掲載します。

ここでは専用のコマンドを使ってスキャフォールディング可能な UDP 通信をメイン処理クラスとして実装する方法をご紹介します。
メイン処理クラスはアプリケーションのエントリポイントとして機能し、UDP 通信のインスタンスはジェネレータクラスで生成されます。
また、CLI上で表示されるアプリケーション識別子や説明文、さらにコマンドライン引数も含めたメイン処理クラス上でのカスタマイズできる内容を中心にご紹介します。

メイン処理クラスを生成するコマンドは以下の通り。
> php worker simple:udp MainForTest

[success] UDP通信のメイン処理クラス生成に成功しました (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 = 'テスト用のUDP通信';
                    

Usageでは以下のように表示されます。
> php worker
SOCKET-MANAGER Framework 1.X.X

Usage:
  command [arguments]

 main
  app:main-for-test                              テスト用のUDP通信
 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::UDP, 'localhost', $port_no);

    ・
    ・
    ・
}
                    

この SimpleSocketGenerator クラスの引数の内訳は次の通りです。
  1. シンプルソケットタイプ(今回の場合は SimpleSocketTypeEnum.UDP)
  2. ホスト名(必須)
  3. ポート番号(必須)
  4. ダウンタイム(デフォルト:100ms)
  5. 送受信バッファサイズ(デフォルト:255)
  6. バッファスタック件数(デフォルト:1)
  7. 言語コード(デフォルト:'ja')
シンプルソケットタイプ / ホスト名 / ポート番号を除くこれら4つの項目をデフォルト値で指定すると以下のようになります。
$generator = new SimpleSocketGenerator( SimpleSocketTypeEnum::UDP, 'localhost', 15000, 100, 255, 1, 'ja' );
                    

$generator = new SimpleSocketGenerator( SimpleSocketTypeEnum::UDP, 'localhost', 15000 );
                    

$generator = new SimpleSocketGenerator( SimpleSocketTypeEnum::UDP, null, null, null, null, null, null );
                    

御覧の通り、直接値を指定する事もできますが、引数を省略したり、nullを指定する事で自動的にデフォルト値が適用される仕組みになっています。
UDP通信の場合は サーバー / クライアント を兼ねているため、ホスト名とポート番号を指定している場合は、それがソケットにバインドされます。

cycleDrivenメソッドの引数

メイン処理クラスの実行(exec)メソッド末尾のソースにはノンブロッキングループ内でcycleDrivenメソッドが呼ばれている箇所があります。
public function exec()
{
    ・
    ・
    ・

    // ノンブロッキングループ
    while(true)
    {
        // 周期ドリブン
        $ret = $generator->cycleDriven();
        if($ret === false)
        {
            goto finish;
        }
    }

    ・
    ・
    ・
}
                    

このcycleDrivenメソッドの引数の内訳は次の通りです。
  1. 周期インターバルタイム(デフォルト: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 ISimpleSocketUdp $p_simple_socket シンプルソケットインスタンス
         * @param mixed[] $p_argv 可変引数(setKeepRunningメソッドの第二引数以降のものが渡される)
         * @return void
         */
        function(ISimpleSocketUdp $p_simple_socket): void
        {
        }
    );

    ・
    ・
    ・
}
                    

以下のように function 内に処理を定義する事もできますが、ヘルパー関数などの関数名を指定する事もできます。
public function exec()
{
    ・
    ・
    ・

    /**
     * 常時実行処理の登録(任意)
     * 
     * 常時実行処理がある場合に$generator->setKeepRunning()メソッドで登録します
     */
    $generator->setKeepRunning
    (
        /**
         * 常時実行処理
         * 
         * @param ISimpleSocketUdp $p_simple_socket シンプルソケットインスタンス
         * @param mixed[] $p_argv 可変引数(setKeepRunningメソッドの第二引数以降のものが渡される)
         * @return void
         */
        function(ISimpleSocketUdp $p_simple_socket): void
        {
            // データ受信
            $host = $port = null;
            $data = $p_simple_socket->recvfrom($host, $port);

            // データのログ出力
            $p_simple_socket->logWriter('debug', ['受信データ' => $data]);

            // データ送信
            $p_simple_socket->sendto($host, $port, $data);
        }
    );

    ・
    ・
    ・
}
                    


コアインスタンスの生成

メイン処理クラスの実行(exec)メソッドの中ほどに以下の定義があります。
public function exec()
{
    ・
    ・
    ・

    /**
     * シンプルソケットインスタンスの生成
     * 
     * この手続きが行われた時点でインスタンスが生成され有効になります
     */
    $w_ret = $generator->generate();
    if($w_ret === null)
    {
        goto finish;
    }

    ・
    ・
    ・
}
                    

シンプルソケットの各インターフェース用コアインスタンスが、ここで初めて生成されると同時に有効になります。
generate メソッド実行時の戻り値(ここでは $w_ret 変数)には、setKeepRunning メソッドで設定するクロージャ、または関数の引数で渡される ISimpleSocketUdp インターフェースと同じものが設定されます(インスタンスの使い方は同じです)。

例えば初回起動時にデータ送信する場合、以下のように書く事ができます。
$interface = $generator->generate();
if($interface === null)
{
    goto finish;
}
$interface->sendto('localhost', 15000, 'test data');
                    


おわりに

これまで見てきたように、メイン処理クラスではコマンドライン/設定項目/ログライター/SocketManager連携/常時実行コールバックを自由にデザインすることができます。
シンプルソケット機能単体でも IPC(サーバー間通信)として機能しますが、SocketManager と連携させる事で IPC として利用する事もできます。