【アーキテクチャ】

はじめに

SOCKET-MANAGER Framework は、CUEIアーキテクチャ(キューイ、通称キュー) をビルトイン採用した、軽量で拡張性のある通信フレームワークです。
CUEIアーキテクチャは、以下の4つの要素を統合した設計思想を指します。
  • Communication(通信抽象化):多様なプロトコルを統一的に扱う
  • Union(共有基盤):処理全体を束ねる共有基盤
  • Event(非同期処理):イベント駆動型の非同期処理
  • IPC(サーバー間通信):マルチサーバー構成・分散処理
これら4つの柱をビルトインで備えることにより、外部ライブラリや追加サーバーに依存せず、効率的でスケーラブルな通信アプリケーションを構築できます。

CUEI アーキテクチャは、ITIL v4 のプラクティス体系を技術的に再構成したもの として設計されています。
Communication / Union / Event / IPC の 4 要素は、ITIL v4 が定義する サービスマネジメント構造(価値、プラクティス、役割、改善モデル)と対応 しており、リアルタイム通信システムに求められる複雑性・高負荷・分散性を統制するためのガバナンスを提供します。

この構造により、CUEI は 設計段階から運用段階まで一貫した品質保証 を可能にし、通信方式の抽象化、共通基盤の統制、非同期イベント処理、分散スケールといった要素を統合的に扱うことができます。
ITIL v4 が掲げる「価値・リスク・コスト」の最適化を、ソケット通信領域に特化した技術アーキテクチャ として実現することで、将来の拡張性と運用の再現性を高いレベルで維持します。

これにより CUEI は、単なる通信アーキテクチャではなく、サービスマネジメントの観点からも一貫性を持つ「技術版 ITIL」として機能します。

CUEIは以下の4つの柱で構成されます。
四象限モデル(CUEIの4大要素)
CUEIアーキテクチャを構成する4大要素


四象限モデル(CUEIの4要素)

CUEIアーキテクチャは、Communication / Union / Event / IPC の4要素を四象限モデルとして整理できます。
それぞれが独立しつつも相互に連携し、フレームワーク全体を支える基盤を形成しています。

当フレームワークで置き換えると…

・Communication(通信抽象化)
TCP・UDP・WebSocket・独自プロトコルなど多様な通信方式を抽象化する事でビジネスロジックとのシームレスな分離が可能。
詳細は ▶通信抽象化 を参照。

・Union(共有基盤)
UNITパラメータによる統合コンテキスト。イベント処理全体のハブとして機能し、接続情報や送受信データを一元管理。
送受信データに関してはFIFO方式バッファを採用する事でイベント多重起動を防止。
詳細は ▶共有基盤 を参照。

・Event(非同期処理)
独自のイベントループとコルーチンによる軽量なイベント駆動処理を実装する事で、動的なプロセスやスレッドに頼らない設計が可能。
FIFOバッファの送受信データを順次取り出し、ビルトインの状態遷移制御により処理順序を保障。
詳細は ▶イベント駆動アーキテクチャ を参照。
UNIT / Queue を用いたイベント処理の具体的な実装方法については、▶イベントハンドラについて にて詳しく紹介しています。

・IPC(サーバー間通信)
サーバー間通信を前提としたマルチサーバー構成とスケーラビリティの確保。
INETソケットを使う事でスケールアップ/スケールアウト時も統一的なインターフェースを提供する事が可能。
詳細は ▶IPC(プロセス間通信) を参照。

これらの4大要素を体系化する事で、自作によらないCLIコマンドによるスキャフォールディングのビルトイン実装を可能にしています。

CUEI/O(開発から運用まで)

SOCKET-MANAGER には SOCKET-MANAGER Launcher というサーバーリソース監視付きのサービス管理ランチャーを用意しています。
これを組み合わせることで、CUEI/O(キューアイオー) として「開発から運用まで」を一貫してカバーできます。

  • CUEI = 開発思想(通信・共有基盤・非同期処理・サーバー間通信)
  • CUEI/O = 開発+運用(Operation)を含む完全版
「/O」を付けることで、運用まで含めた包括的な基盤であることを明示できます。

SOCKET-MANAGER Launcherを組み合わせることでCUEIはCUEI/Oへ拡張されます。
CUEI/O──開発と運用を統合する拡張モデル
CUEI/O──開発と運用を統合する拡張モデル

REST-API 環境における CUEI の実装レベルでの関係については、▶CUEIとの実装レベルでの関係を参照してください。
CUEI/O の運用面での位置づけについては、▶技術版 ITIL としての CUEI/Oにて補足しています。


Operation(運用モデル)

CUEI/O の「O(Operation)」は、SOCKET-MANAGER Framework における運用モデルを表します。
開発時に構築した CUEI アーキテクチャを、そのまま運用フェーズへ拡張し、サービスの起動・停止・監視・設定管理・コミュニケーション を統合的に扱うための基盤です。

Operation レイヤーでは、以下の要素が中心となります。
  • サービス管理ランチャー:GUI/CLI ランチャーによるサービスの起動・停止・スケーリング
  • リソース監視:CPU/メモリ/ディスクなどのリソース状況の可視化
  • 運用ガバナンス:操作ログ・チャットログによる透明性と追跡性の確保
これらにより、CUEI で構築した通信アプリケーションを、実運用スケールで安全に動かすための運用モデル として CUEI/O が完成します。

DevOps環境:開発と運用を統合する基盤

DevOps は「開発(Dev)」と「運用(Ops)」を統合する文化・仕組みです。
SOCKET-MANAGER Framework の DevOps環境は、CUEI/Oモデルに準拠して設計されており、同一のランチャー環境で開発と運用をシームレスに接続 できる点が特徴です。

SOCKET-MANAGER Launcher は、単なるサービス起動ツールではなく、リソース管理・監視機能・設定管理・新規開発環境 を兼ね備えた統合基盤です。
これにより、以下のような DevOps 実践が可能になります。
  • 開発環境で運用を見据えたテストが可能
    → ローカル環境でも本番に近い条件で検証でき、リリース後のトラブルを未然に防止。
  • 本番環境へそのまま移行可能
    → 開発・検証環境と本番環境の差異を最小化し、デプロイ時のリスクを軽減。
  • CLI/GUI ランチャーによるサービス管理
    → app:cli / app:gui コマンドを通じて、サービスの起動・停止・監視を一元化。

DevOps環境の詳細な導入手順やコマンド一覧については、▶フレームワークのDevOps環境をご覧ください。

ChatOps:運用の会話主導化とリアルタイム連携

ChatOps は、チャットを中心に運用情報・操作・通知を集約し、 チームの状況把握とインシデント対応を高速化する運用モデルです。
SOCKET-MANAGER Launcher には、管理者同士がリアルタイムにやり取りできる 管理者間チャット機能 がビルトインされており、 この ChatOps の考え方を取り入れた内蔵型の運用連携機能として機能します。

特筆すべき点として、本チャット機能は Slack や Microsoft Teams のような外部サービスとの連携を必要としません。
ランチャー内部にチャット基盤が組み込まれているため、 外部 API や Bot の設定を行うことなく、 導入直後から即座に ChatOps を実践できる軽量な運用環境 を提供します。

管理者チャット機能では、以下のような運用上のメリットを提供します。
  • 接続/切断イベントの共有:誰がいつ参加・離脱したかをチャットログとして記録
  • 操作ログとチャットログの分離管理:サービス操作履歴と会話履歴を別ファイルで管理し、監査性を向上
  • プライベートメッセージ/全体メッセージ:宛先指定による個別連携と、全管理者への一斉通知
  • GUI上でのリアルタイム連携:リソース監視・サービス一覧・チャットを同一画面で確認可能
  • 外部連携不要の内蔵型 ChatOps:外部チャットサービスの障害や認証トラブルの影響を受けない安定した運用基盤

これにより、SOCKET-MANAGER Launcher は、 DevOps環境における運用ハブ兼 ChatOpsプラットフォーム として機能し、 運用ガバナンスと即時性を両立します。

管理者間チャット機能の詳細な画面構成やログ管理の仕組みについては、 ▶管理者間チャット機能(ChatOps)をご覧ください。

アーキテクチャ思想

SOCKET-MANAGER Framework のアーキテクチャは、 CUEI(Communication / Union / Event / IPC) を中心に構築されています。
これは単なるイベントループや非同期処理の実装ではなく、 異なる通信方式を統一的に扱うための 基盤レイヤー を提供するものです。

特に重要なのは、プロトコル部とコマンド部が UNIT / Queue モデル によって接続されている点です。
この構造により、WebSocket・TCP・UDP・REST-API といった 通信方式の違いを越えて、すべての処理が同じレイヤー構造で動作します。

また、CycleDrivenManager による状態遷移制御により、 非同期処理中でも 状態破壊が起きない堅牢な実行環境 を実現しています。
これは一般的な非同期ランタイムでは難しい特徴であり、 本フレームワークが「実運用スケール」を重視する理由でもあります。

CUEIアーキテクチャは、通信方式やサーバー構成の違いを吸収し、 統一されたリアルタイム基盤(Unified Realtime Layer) として機能します。

レイヤー概念図

CUEIアーキテクチャの思想を具体的に実装した構造が、以下の「レイヤー概念図」です。
ここではプロトコル部とコマンド部の分離、UNITパラメータの役割、CycleDrivenManagerによる制御など、ノンブロッキングモードによる実装を前提とした仕組みを詳しく解説していきます。
SOCKET-MANAGERフレームワークのレイヤー構造図
サーバーのアプリケーションの部分はプロトコル部とコマンド部に大きく分かれています。
最初にそれらをライブラリ内の CycleDrivenManager のプロトコル部とコマンド部へ取り込ませてから使う事になります。

プロトコル部とコマンド部に分けているのは、それぞれを自由に入れ替える事ができるようにするためです。
作成したクライアントのプロトコルに合わせてプロトコル部を入れ替えたり、サーバーサイドのコンテンツを切り替えるためにコマンド部を入れ替えたりする事で様々な組み合わせのサーバーを構築する事が可能です。

キューとUNITの関係

まずは CycleDrivenManager の動きを理解するためにキューとステータスUNITの関係を知る必要があります。
図にすると以下のようなイメージになります。
SOCKET-MANAGERフレームワークのイベント処理部分であるキューとUNITの関係図
通信データの送受信の特徴として送りたいデータ、あるいは欲しいデータが一度に全て送信/受信できるとは限らないためこのような構成になっています。
また、WAIT が発生するような処理(送信/受信をセットにしている動作など)を UNIT で分ける事で、キューの処理を待たせる事なく次の UNIT 処理へと進む事ができるようになります。

例えば Websocket の opening ハンドシェイクの場合はクライアントからヘッダ情報を受信した後は同じようにヘッダ情報をサーバーから送り返さないといけませんが、これをブロッキングモードで送信が完了するまで待っていると他の接続も巻き込んで処理が待たされる事になってしまいます。

この回避策として当ライブラリでは以下の UNIT 処理のようにノンブロッキングモードで送信が行えるようにしています。
// CREATEステータスのUNIT
{
    // 送信データの設定
    $p_param->protocol()->setSendingData(<送信データ>);

    return ProtocolStatusEnumForWebsocket::SEND->value;
}

// SENDステータスのUNIT
{
    // データ送信
    $w_ret = $p_param->protocol()->sending();

    // 送信中の場合は再実行
    if($w_ret === null)
    {
        $sta = $p_param->getStatusName();
        return $sta;
    }

    return null;
}
                    

実際にデータを送信する SEND ステータスのひとつ前のUNIT処理( CREATE ステータス)であらかじめ送信したいデータを設定しておき、送信を実行するUNIT処理( SEND ステータス)で sending メソッドの戻り値が null 以外になるまで繰り返しています。

※ sending メソッドは設定された送信データを送信しきるまで null を返す処理です。
※ getStatusName メソッドは現在実行中のステータス名を取得するものです。

受信の場合も同じように受信サイズを設定するメソッド setReceivingSize と受信しきるまで null を返すメソッド receiving に分けています。

このように UNIT の処理の実装は極力処理の停滞(ブロッキング)がおきないように構成する事が求められます。

また、UNIT ステータスはリターン値によって遷移していきます。そしてキューの処理が全て完了したら null を返すルールとなっています。
処理の流れはリターン値で制御できるので、同じような処理を繰り返し行ったり、分岐したいケースがある場合には処理を構造化しながらコントロールする事が可能になります。

このようなキューとステータスUNITをコントロールする役割を CycleDrivenManager が担っています。

throwブレイク

このシステムでの throw ブレイクは、UNIT のステータスを維持したまま処理を中断して待ち受け状態に戻る事を意味します。
ステータスUNIT内で UnitException クラスを用いて例外( throw )を発行する事で throw ブレイクとなります。

UNIT定義クラス

UNIT の処理はプロトコル/コマンド部を問わず IEntryUnits インターフェースに従って実装する必要があります。
プロトコル部を作成する時は craft:protocol コマンドを、コマンド部を作成する時は craft:command コマンドを実行してファイルを生成します。
生成されるファイルの種類は以下の通り。
  • UNIT 定義クラス
  • キュー名定義の Enum
  • ステータス名定義の Enum
UNIT 定義クラスの実装イメージは次の通りです。
SOCKET-MANAGERフレームワークのイベント処理部分であるキューとUNITを定義するクラスの内部構造イメージ
プロトコル部のキュー名は ProtocolQueueEnum で予約されているので、作成された Enum ファイルにはこの予約された Enum 値がエイリアス名として定義されます。

コマンド部のキュー名の定義は自由です。UNIT 定義クラスの実装に合わせてキュー名を追加していく事になります。

■ getQueueList メソッドの実装
プロトコル部、コマンド部それぞれで定義されたキュー名の Enum 値をピックアップしてリストに追加します。
■ getUnitList メソッドの実装
引数で渡されたキュー名と一致するリストを返します。プロトコル部、コマンド部それぞれで定義されたステータス名定義の Enum 値をピックアップしてステータス名に対応する UNIT メソッドとセットでリストを作成します。各キューの処理は START ステータスから始まるので必ず含める必要があります。

初期化クラス

初期化クラスは IInitSocketManager インターフェースに従って実装する必要があります。
インターフェース実装に必要な内容は以下の通りです。

UNITパラメータクラス

SocketManagerParameter クラスやそれを継承しているクラスの事を UNIT パラメータクラスと呼びます。
このインスタンスは UNIT 処理の引数として渡されるもので、SocketManager との橋渡し役を担っています。

SocketManagerParameter クラスを継承する事でデータの送受信やディスクリプタ(クライアント接続子)の操作を行うための機能を提供すると同時に、アプリケーションで利用するグローバルエリアを管理する役割も担っています。
プロトコル部・コマンド部・コマンドディスパッチャー間で同じグローバルエリアの利用が可能です。
接続子単位でのグローバル管理が必要なデータに関しては、UNIT パラメータクラス内の getTempBuff/setTempBuff メソッドで取得・設定が可能です。
これを図にすると以下のようになります。
SOCKET-MANAGERフレームワーク内でのグローバル管理領域のアクセス概念図
初期化クラス内の getUnitParameter メソッド でインスタンスを返す必要がありますが、インスタンス化するタイミングは初期化クラスの内側/外側を問いません。

プロトコル部では UNIT パラメータクラス内でインプリメントされた IProtocolParameter インターフェースを返す protocol メソッドを介して送受信用のメソッドを使う必要があります。
これに対してコマンド部では UNIT パラメータクラス内でインプリメントされた getRecvData メソッドを使って受信データを取得し、setSendStack メソッドを使って送信データを設定する流れが基本となります。

このような構成になっているのは以下の処理を前提としているからです。
・受信処理
受信データはプロトコル部で組み立てたものをコマンド部に渡します(コマンド部では getRecvData メソッドを通して受信データを取得します)。
・送信処理
コマンド部で組み立てた送信データはプロトコル部で必要に応じて分解され、送信されます(コマンド部では setSendStack メソッドを通して送信データを引き渡します)。
この為コマンド部の処理では、ノンブロッキングの処理を意識する事なく通信データの送受信の実装が可能となります。

※ protocol メソッドをコマンド部で呼び出すと UnitExceptionEnum::ECODE_METHOD_CALL_FAIL 例外が発生してエラー終了しますのでご注意ください。
※ 具体的にどんなメソッドが存在するのかについては SocketManagerParameter クラスの>> Referenceをご覧ください。

シリアライザー/アンシリアライザー

この処理はディスクリプタ内で管理された先入れ先出し方式である送受信スタックエリア(メモリ上のスタック領域とは無関係です)と深く関わっています。
送受信スタックエリア内ではシリアライズ化されたデータが保持されるため、データ取得時にはアンシリアライザーが、データ設定時にはシリアライザーが呼び出されます。
これを図にすると以下のようになります。

クライアントごとに管理している送受信データスタック領域とアクセス概念図

コマンドディスパッチャー

プロトコル部で受信したデータは UNIT パラメータクラスの setRecvStack メソッドを使って受信スタックへ格納する必要があります。
コマンドディスパッチャーはその受信データを解析してコマンド部に処理を振り分ける役割を担っています。

コマンド部から送信するデータは UNIT パラメータクラスの setSendStack メソッドを使って送信スタックへ格納する必要があります。
送信時はコマンドディスパッチャーの介入はなく、送信スタックに溜まったものをプロトコル部を経由して順次クライアントへ送信されます。

これを図にすると以下のようになります。

受信データを解析してコマンドディスパッチャーが処理を振り分ける概念図
コマンドディスパッチャーを介さずに送信データが送出されるまでの概念図

緊急停止時のコールバック

以下の場面で呼び出されます。
  • アライブチェック処理のタイムアウト
  • コマンドディスパッチャーで例外キャッチ時
  • 相手先による強制切断

ログライター

ライブラリ内を含めアプリケーションレイヤー内からも呼ばれるログ出力ハンドラーです。
アプリケーションレイヤーからは UNIT パラメータクラスを通して呼ばれます。

メイン処理クラス

実装イメージは以下の通りです。
メイン処理クラスの内部構造イメージ
このうち初期設定ブロックは SocketManager の準備処理として必要な部分です。
ポート設定ブロックではマルチサーバーの子サーバーとして起動する場合、親サーバーへの接続を行うための connect メソッドが使用される事があります。
ノンブロッキングループブロックは以下のイメージで動作します。
クライアント接続単体でのノンブロッキングループ動作イメージ
1つの接続に対してプロトコル部とコマンド部が連携し合って動作します。
このうち1回の周期(ループ)で実行されるのはプロトコル/コマンド部のそれぞれ1 UNIT ずつとなります。
これが複数の接続になると以下のイメージになり、1つのサーバーで処理する単位になります。
複数のクライアント接続時のノンブロッキングループ動作イメージ
そして複数のサーバーを起動している場合は以下のイメージになり、プロセスが順当に割り当てられると1つのサーバープロセスが CPU の各コア(このケースではコアが4つの場合)に割り当てられてデュアルで動作します。
複数のサーバープロセス稼働時のノンブロッキングループ動作イメージ
※ 実際にサーバーをスケーリングする際には上記のイメージを基に計算/設計していく事になりますが、サーバー上の個々のプロセスを OS が必ずしも別々の CPU に割り当てるとは限らないため、リソースや処理時間等の実測値を見ながら調整していく事になります。通常は OS が自動的に CPU 割り当てを行いますが、SOCKET-MANAGER Launcher を利用することで任意の CPU にプロセスを割り当てることが可能となり、プロセス間の競合を抑制しながらより効率的なリソース活用が行えます。

おわりに

ここでご紹介した内容は特に新規プロジェクト開発をする時に必要になる情報ばかりですが、Websocket 開発環境でもより高度な実装をするために必要な情報でもあります。
サーバー間通信を伴うマルチサーバーを構築する際の基礎的な内容でもありますので▶マルチサーバーの構成のページも合わせてご覧ください(マルチサーバーの構築を予定していない場合は読み飛ばしてもらっても構いません)。