どうも、靖宗です。
実は個人的に一番知りたかった項目で、Channelに関してです。
この章も少々長くなりそうなので2記事に分割予定です。
Channels
PhoenixのChannelは何百万のクライアントを接続するリアルタイムコミュニケーション機能です。ある意味Phoenixのコア技術と勝手に感じております(Elixir自体がたくさんのリクエストを捌くイメージなので)。
想定される使い方としては
- チャットルームとメッセージを送ったりするAPIの提供
- 速報などのニュース(地震速報とか)
- 地図上でのトラッキング(電車とかトラックとか)
- マルチユーザのゲームに於けるイベント
- センサーのモニタリングや照明のコントロール
- CSSやJavaScriptが変更されたときの通知(開発時など)
などが挙げられております。
チャンネルにクライアントが接続して、トピックにサブスクライブすると、Phoenixサーバーが他のクライアントに配信する、という仕組みだそうです。
公式にAAみたいなのがはっついてました。たぶんスマホとかでみると崩れる。
+----------------+ +--Topic X-->| Mobile Client | | +----------------+ +-------------------+ | +----------------+ | | | +----------------+ | Browser Client |--Topic X-->| Phoenix Server(s) |--+--Topic X-->| Desktop Client | +----------------+ | | | +----------------+ +-------------------+ | | +----------------+ +--Topic X-->| IoT Client | +----------------+
言うは易く行うは難しな気はしますが、ソースコードがあればできないことはない。はず・・・(´・ω:;.:...
ちなみに、様々なクライアントを想定しているだけあって様々なライブラリが整備されているようです。
The Moving Parts
Overview
おそらく簡単に使えるんでしょうが、実際の挙動はやや複雑です(とはいっても概念だけなのでそこまで難しく無さそう。)
クライアントがPhoenix Serverと通信を開始する際に1クライアント1トピック(チャットルームの様なイメージ?)につき通信用のプロセスを生成します。この際に通信で使われる%Phoenix.Socket
(いわゆる%Plug.Conn
のようなもの?)が初期化されるそうです。詳しくはたぶん後で出てくるのでそのときに確認しましょう。
一度通信が成立すると、クライアントとトピックで一意に決まるプロセス(図で言う「Sending Client, Topic 1」)へのルーティングが確立し、クライアントからの通信がきちんとチャンネルのサーバーへ繋がるようになります。
通信が確立した後にブロードキャストのメッセージを発行すると、一度Local PubSub
というプロセス?を経て同じトピックに繋がっているサーバーへ配信されるようです。
Local PubSub
に送られたメッセージは、同一トピックに接続しているプロセス全部に送信され、無事メッセージが各クライアントに届きます。
同一ノードはこのような仕組みになっていますが、別のノードはPubSub
間で通信があった後に各クライアントプロセスへ配信という仕組みになっているそうです。
Endpoint
Endpointの章で若干触りましたが、Endpoint
にsocketの設定を書く箇所があります。
defmodule HelloPhoenixWeb.Endpoint do use Phoenix.Endpoint, otp_app: :hello_phoenix socket "/socket", HelloPhoenixWeb.UserSocket, websocket: true, longpoll: false ...
URIの設定やwebscoketを利用するかロングポーリングを使用するかの設定です。
Socket Handlers
チャンネルの接続がセットアップされる度に、上記で言うHelloPhoenixWeb.UserSocket
がソケットハンドラとして呼び出されるようです。
おそらく初期化関数や認証などはHelloPhoenixWeb.UserSocket
に書くのでしょう。
Channel Routes
チャンネルのルーティングもHelloPhoenixWeb.UserSocket
に書くそうです。Phoenixプロジェクトを生成して最初から書かれているコメントアウトされている箇所にも書かれています。
defmodule HelloPhoenixWeb.UserSocket do use Phoenix.Socket ## Channels # channel "room:*", HelloPhoenixWeb.RoomChannel ...
channel "room:*", HelloPhoenixWeb.RoomChannel
でroom:lobby
やroom:123
といったトピックへのルートを生成できるようになるようです。
Channels
上記で言うHelloPhoenixWeb.RoomChannel
でしょうか。
ソケット通信でないHTTPリクエストで言うコントローラに該当するようなモジュールのようです。ただし、コントローラは基本的に受け身ですが、Channelは双方向という違いはあります。
Channelはjoin/3
、terminate/2
、handle_in/3
、handle_out/3
という4つの関数を実装して作って行くようです。
Topics
トピックはChannel Routesでワイルドカード指定した箇所が該当します。
基本的にroom:123
の要にレコードIDを付与していく形で使う事が多いそうですが、名前を付けるのかID割り振っていくのかはケースバイケースだと思います。
Messages
Phoenix.Socket.Message
というモジュールが存在し、これを利用してトピックにメッセージなどを発行するのでしょう。
Channelの通信で使うメッセージの構造を定義しているようです。
- topic - トピック名。"messages"とか"messages:123"とか。
- event - イベント名。"phx_join"とか。よくわかんないので保留。
- payload - メッセージのペイロード。たぶんメッセージ本体を意味している。"おはよう!"とか。
- ref - 一意に決まる文字列?よくわかんない。
サンプルとかもあるのでよく分からない箇所は後回しにしましょう。
PubSub
Overviewのところで出てやつ。図からも分かるとおり内部実装で稼働してる箇所なので直接使う事はほぼないらしい。ただし設定することはあるかもとのこと。
PubSubはPhoenix.PubSub
というモジュールで構成されていて、こいつはGenServer
とかいろんなものの組み合わせだそうで。
ノード間通信も面倒みてくれるのですが、ノード間の通信はデフォルトではPhoenix.PubSub.PG2
というおそらく内部実装がErlangのpg2でできてるモジュールを利用しているそうです。
なんらかの原因(古いElixirやErlangのバージョンを利用しないといけないケース?)でpg2などが利用できない場合はRedisを利用する設定もあるそうです。詳しくはドキュメントを参照とのこと。
もしRedisの方が早い環境などあったらチューニングでRedisを選択する、なんてことも想定しといたほうが良いんでしょうか?
ただ、デフォルトでPG2を指定されているなら基本的にはPG2をチョイスする方が無難な気はします。
Client Libraries
Phoenixを利用するライブラリが公式+サードパーティーから提供されています。フルスクラッチも勉強にはなると思いますが基本的にはこの辺を利用することになるでしょう。
Official
ギットハブ
3rd Party
- Swift (iOS)
- Java (Android)
- C#
- PhoenixSharp
- dn-phoenix(2019/03/14現在、404、同名のプロジェクトはGitHubに何個かあるけどどうなんでしょう・・・)
- Elixir
次回は実際にサンプルを動かしていく箇所です。たぶん。