■ジュークボックスシステムはサーバー・クライアントに分散される構成なのでプロセス間通信が必須になる。ワークファイルを経由させてもいいけれど、それはそれで後が面倒なので、メッセージキューや共有メモリを使いました。技術的には古いので使っているから殊更なんだと言うものではないですが、実装上のメモとして。
メッセージキューそのものは方向性を持たない一種のデータバスとして利用できますが、通信プロトコルを持たないので双方向通信はやっかいです。自分で書き込んだメッセージを自分で読み出してしまったりします。なので、クライアント・サーバー間に2本のメッセージキューを用意しました。__construct()の中でキューへのアクセスキーを2つ取得しました。
$this->keySvTx = ftok(__FILE__, "t");
$this->keySvRx = ftok(__FILE__, "r");
:
:
$this->hndTx = msg_get_queue($this->keySvTx);
$this->hndRx = msg_get_queue($this->keySvRx);
__destruct()の中で、キューをリムーブします。
$rc = @msg_remove_queue($this->hndRx);
$rc = @msg_remove_queue($this->hndTx);
送信・受信では、クライアントからサーバーへの送信には、msgtypeにプロセス番号を割り当てています。サーバーからクライアントへの返信にはmsgtypeにクライアントのプロセス番号を割り当てることで、特定のクライアントが読み出せるようにしました。全クライアントへの同報通信(ブロードキャスト)は実装しませんでした。
クライアントからの送信:
$mtype = $this->msgtype; (クライアント自身のプロセス番号)
return msg_send($this->hndRx, $mtype, substr($message, 0, $this->msgmaxsize));
サーバーからの返信:
$mtype = $msgtype;(返信先のプロセス番号)
return msg_send($this->hndTx, $mtype, substr($message, 0, $this->msgmaxsize));
メッセージキューで送信できるメッセージはstring型で、長さに上限はないけれど、ジュークボックスシステムでは、コントロール用のキーワード送信にしか使っていない。playとかstopとか、その程度。クライアントからコントロールキーワードが送信され、サーバーがそのキーワードに対応する処理を終えると'ACK'文字列を送信する。クライアントはサーバーからのメッセージ受信が終わるまでウェイトします。サーバーは逆にメッセージがなければスルーします。その制御はmsg_receiveのフラグで指定されます。
クライアント:
$msgtype = $this->msgtype;
$flag = MSG_NOERROR;
$rc = msg_receive($this->hndTx, $msgtype,&$this->rcvid,$this->msgmaxsize,&$message,false,$flag,&$this->opterror);
サーバー:
$msgtype = 0;
$flag = MSG_IPC_NOWAIT|MSG_NOERROR;
$rc = msg_receive($this->hndRx, $msgtype,&$this->rcvid,$this->msgmaxsize,&$message,false,$flag,&$this->opterror);
クライアント・サーバーの双方で、送信、受信に使うキー(hndTx, hndRx)をたすきがけにすることで、それぞれのキーに対応するメッセージキューは一方向通信のみとして機能します。
今回はメッセージキューの双方向通信機能をクラスにまとめ、メッセージキーワードの実装までは取り込みませんでした。サーバー側は直接このクラスを利用しますが、クライアントは特定のキーワードを送信する機能をメソッドにしたラッパークラスを利用します。
共有メモリはサーバーが確保している再生待ちファイルのウェイティングリスト情報を伝達するために使います。クライアントからサーバーへの通信には使われない片方向通信で、制御情報のやり取りにも使われないので特にシビアな排他処理もしていません。
ただし、安全のため、書き込みができるのはサーバーのみで、クライアントはリードオンリーとしてます。
サーバー側:
@$this->hndShm = shmop_open($this->shmKey, "n", 0644, $size);
クライアント側:
@$this->hndShm = shmop_open($this->shmKey, "a", 0644, $size);
共有メモリはサイズが決まっているけど、書き込まれるデータサイズは不定長となります。そのため、共有メモリ先頭10バイトを、書き込みデータの管理領域として、書き込まれたデータサイズの記録領域に割り当てています。データそのものはオフセット10バイトを持って書き込まれることになります。
データを読み出すときは、まず先頭10バイトを読んでデータサイズを取得したあと、オフセット10バイト以降、データサイズ長分を読みだす動きをします。
書き込み:
(まず、管理領域をクリアします)
@$rc = shmop_write($this->hndShm, " ", 0);
(データサイズ情報を書き込みます)
@$rc = shmop_write($this->hndShm, strlen($string), 0);
(データ本体を書き込みます)
@$rc = shmop_write($this->hndShm, $string, 10);
読み出し:
(データサイズを読み出します)
@$strSize = shmop_read($this->hndShm, 0, 10);
(整数型にキャストします)
$numSize = (integer)$strSize;
(オフセット10バイト以降にあるデータ本体を読み出します)
return shmop_read($this->hndShm, 10, $numSize);
メッセージングキューも共有メモリも、実装する上では、エラー処理がさらに必要になりますが、ここでは割愛しています。