websocketのサーバサイドのソースよんでみた
PHPから逃げて転職したってのに、なぜか最近PHP。
つまらんということで、websocketをこっそりつかってみようと思いました。
このソースについて
http://d.hatena.ne.jp/susan-style/20140306/1394103655
を参考にして、Saran Chamlingさんが作った「WebSocket Example」に
コメントという形で理解の履歴を書き込んでいっています。
また、簡略化のために以下のとおりの制限をもっています。
- 拡張データ長を意識していません。125までの長さしか使えません。
- ping pontやバイナリも意識していません。opcode=0x1のみを想定しています
- 必要なエラー処理がほとんどない
websocketのソースを読む上で参考にしたURLです
- websocketの細かい仕組みを解説しているページです。
- バイナリなので、2進数 16進数 10進数の変換が面倒臭い。このURLつかってみてました。
- そもそもソケットプログラムをあまり読んだことがないので、以下を参考にしました。
- また、IOを多重化するとしたらどう言ったアプローチかな?っていうのをざっくり参考にしました。
<?php $host = 'localhost'; //host $port = '9000'; //port $null = NULL; //null var //ソケットの作成 //AF_INET = ip4 //SOCK_STREAM = 時系列的、高信頼性、全二重、接続型のバイトストリーム //SOL_TCP = TCPってこと $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); //TIME_WAIT中でも再利用できるオプションをつける socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1); //ポートにソケットを割り当てる socket_bind($socket, 0, $port); //listen開始 socket_listen($socket); //接続中のソケット用配列 $clients = array($socket); while (true) { //socket_selectは破壊的に配列を更新するので、コピーしておく。 $changed = $clients; //渡されたchanged配列のソケットの中で、何か変化があったものだけを残して、他は破棄。 socket_select($changed, $null, $null, 0, 10); //待ち受けのソケットの変化はほぼ間違いなく新規接続の要請 //acceptして新しい通信用のsocketを生成する if (in_array($socket, $changed)) { $socket_new = socket_accept($socket); $clients[] = $socket_new; $header = socket_read($socket_new, 1024); //read data sent by the socket perform_handshaking($header, $socket_new, $host, $port); //perform websocket handshake //ソケットの情報を取得して、入室を同報 socket_getpeername($socket_new, $ip); //get ip address of connected socket $response = makeTextDataFrame(json_encode(array('type'=>'system', 'message'=>$ip.' connected'))); //prepare json data send_message($response); //変化のあったソケット配列から、待ち受けのソケットを除去 $found_socket = array_search($socket, $changed); unset($changed[$found_socket]); } //待ち受け以外の通信中のソケットをみる。受信のはず。 foreach ($changed as $changed_socket) { //受信出来る限りじゃんじゃん受信 //受信できる文字列長さは128までにしてるからこういう書き方でOK while(socket_recv($changed_socket, $buf, 1024, 0) >= 1) { //受信したものはmaskしてるので、decodeしてやる $received_text = decode($buf); $tst_msg = json_decode($received_text); $user_name = $tst_msg->name; $user_message = $tst_msg->message; $user_color = $tst_msg->color; //1人から受けた内容をもとに文字列つくって、送信用のデータに生成して、全員に配布 $response_text = makeTextDataFrame(json_encode(array('type'=>'usermsg', 'name'=>$user_name, 'message'=>$user_message, 'color'=>$user_color))); send_message($response_text); break 2; //送信完了したから一気に抜ける } //変更があったソケットで、データ受信がないものは、切断したはず。 //接続中のソケット配列$clientsから除去して、 //全員に抜けたことを伝える //でもなんで、socket_read? socket_recvじゃだめだったのかな $buf = @socket_read($changed_socket, 1024, PHP_NORMAL_READ); if ($buf === false) { // ==はつかえない。""を返すことがあるからね $found_socket = array_search($changed_socket, $clients); socket_getpeername($changed_socket, $ip); unset($clients[$found_socket]); $response = makeTextDataFrame(json_encode(array('type'=>'system', 'message'=>$ip.' disconnected'))); send_message($response); } } } //死んだとき殺す socket_close($sock); function send_message($msg) { global $clients; foreach($clients as $socket) { @socket_write($socket,$msg,strlen($msg)); } return true; } //ユーザーからのデータを復号する function decode($text) { //2番目の文字 = [MASK 1bit と 数値 7bit ]の8ビットの事 //127が01111111 なので、MASKを消して数値として取得 $length = ord($text[1]) & 127; if($length == 126) { $masks = substr($text, 4, 4); $data = substr($text, 8); } elseif($length == 127) { $masks = substr($text, 10, 4); $data = substr($text, 14); } else { $masks = substr($text, 2, 4); $data = substr($text, 6); } //マスクbitとデータをxorかけて文字列を取得。 //$masks[$i%4]で順繰り順繰りデコードする $text = ""; for ($i = 0; $i < strlen($data); ++$i) { $text .= $data[$i] ^ $masks[$i%4]; } return $text; } //テキストデータにヘッダつけたりして送信用のフレームを作成 function makeTextDataFrame($text) // 10000000 | ( 0001 & 1111 ) // [FIN=1 RSV1-2=000 MASK=0 OPCODE=000] | ( [MASK=0 OPCODE=001] & [4桁フルフラグ=1111] ) $b1 = 0x80 | (0x1 & 0x0f); $length = strlen($text); if($length <= 125) //opcodeまでの頭16bit, 125という数値、それぞれをUnsignedCharとして、バイナリつくりましょう $header = pack('CC', $b1, $length); elseif($length > 125 && $length < 65536) //opcodeまでの頭16bit, 126という数値、まではUnsignedCharとして、 //続く$lengthは16bit UnsignedIntとしてバイナリつくりましょう。 //phpで16bitのintったら、unsigned shortになるのかな $header = pack('CCn', $b1, 126, $length); elseif($length >= 65536) //opcodeまでの頭16bit, 127という数値、まではUnsignedCharとして、 //続く$lengthは64bit UnsignedIntとしてバイナリつくりましょう。 //phpで64bitのintったら、unsigned longが2つになるみたい //'CCJ'ではダメなのだろうか $header = pack('CCNN', $b1, 127, $length); return $header.$text; } //handshake new client. function perform_handshaking($receved_header,$client_conn, $host, $port) { /* たとえば、ユーザーからはこういうアクセスがくるはず GET /resource HTTP/1.1\r\n host: example.com\r\n upgrade: websocket\r\n connection: upgrade\r\n sec-websocket-version: 13\r\n sec-websocket-key: E4WSEcseoWr4csPLS2QJHA==\r\n それをkey=valueにする。 */ $headers = array(); $lines = preg_split("/\r\n/", $receved_header); foreach($lines as $line) { $line = chop($line); if(preg_match('/\A(\S+): (.*)\z/', $line, $matches)) { $headers[$matches[1]] = $matches[2]; } } //本当はここでいろんなヘッダーが異常な場合のエラー処理がいる。 $secKey = $headers['Sec-WebSocket-Key']; //ryu memo //sha1は”文字列として”値を返す。でも、base64はバイナリを文字に変換する関数。なので、バイナリで生成するようにする。 //もしくは文字を16進数の値と見立てて、packしてバイナリつくる。 //base64_encode(pack("H*",sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',true))); $secAccept = base64_encode(sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',true)); $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" . "Upgrade: websocket\r\n" . "Connection: Upgrade\r\n" . "WebSocket-Origin: $host\r\n" . "WebSocket-Location: ws://$host:$port/demo/shout.php\r\n". "Sec-WebSocket-Accept:$secAccept\r\n\r\n"; socket_write($client_conn,$upgrade,strlen($upgrade)); }
KinectTouchをmacでビルドしたときのメモ
KinectTouchという、ある表面にタッチしたかどうか?をKinectで取得してTUIO形式で
投げるプログラムをビルドするのに手間がかかったので、メモをしておきます。
https://github.com/robbeofficial/KinectTouch
https://www.youtube.com/watch?v=4zXtV66cFDY
こいつです。
僕の環境として、
osx 10.9.3
openfreamworksがはいってる。
openfreamworksのofxOpenCvがはいってる。
openfreamworksのofxCVがはいってる。
openfreamworksのofxOpenNIがはいってる。
OpenCvはportsとかで個別に入れたかもしれない、、、わすれた
です。
まービルドにてこずったのですが、・下記の項目をかえてどうにかビルドに成功させました。
あと、なるべくopenfreamworksのライブラリを参照するようにして、導入を簡単に出来たらなと考えました。
1、subdir.mkに直書きされてる、/home/robbe/workspace/を自分の配置してるパスに変更する。
2、usleepがundefっていわれたら、#include
3、opencvのライブラリをリンクするのに、pkgconfigがいります。もしportsつかってるなら、
port install pkgconfig
これでインストール。
4、openniのincludeパスを追加します。あと、dylibもリンクしましょう。objects.mkのLIBSに記述します。こんな感じ
/Applications/of/addons/ofxOpenNI/mac/copy_to_data_openni_path/lib/libOpenNI.dylib -I/Applications/of/addons/ofxOpenNI/include/openni
5、niConfix.xmlは、できた実行ファイルと同一ファイルにコピーする。../niConfix.xmlとかしても見えないみたい。
ここからが、本当の戦いのはずなのに、超ぐったり。kinect系だけはwindowsで実行して、tuioでmacに投げてあとはmac側で映像系の処理ってのが一番良い気がします。
air for android で video on demand を作ろうとして失敗した話
mp4ファイルをandroidへストリーミングで放映する案件がありました。
rtspを使えばandroid os標準機能で出来ますが、アプリを経由したいということでした。
ですので、airを利用しrtmp経由で再生することにしました。
シークも再生ボタンも戻るボタンも必要ということだったので、flvplaybackを利用することにししました。が、結局あきらめました。
フレーム上に直接flvplaybackを配置し、インスタンス名をmyplayとしました。
ソース一行で済みました。
import fl.video.*; myplay.play("rtmp://videoserver.com/appname?id=xxx&pass=xxxx/mp4:filename.mp4");
1、flvplaybackのイベントが取れない。
ストリーミングサーバ側で、id/passをみてrejectconnectionした場合の対応で、
myplay.addEventListener(VideoEvent.CLOSE,onPlayClose);
と書いたところ、CLOSEが未定義ですと怒られまくります。
2、再生されたり、再生されなかったりする。
これが最大の原因です。サーバ側のログを見ても、stream cleate も stream playも
うまくいっているのに、playが始まらないのです。
簡単に出来る予定で利用したflvplaybackコンポーネントですが
手間がかかるので利用を中止しました。
cakeソースをよみつつ同僚との会話
○中年
cakeさ、 たしかに自力で__constractつくるのは すごいよ?
でもさ、phpの微妙にそろった動的関数コール系統ってなんか矛盾よね。
なんか、言語の不整備をむりくりなんかしてる感じ。
まぁphpなりの黒魔術使えていいけどさ。
でもそう、
基礎部分は黒魔術よ。
4をすてたチリウムのほうに興味がある気持ちわかる。
どうも例外処理もないみたいだし、、
●同僚
そうだよね、4ではどのみち限界がある
○中年
なんか黒魔術しってもねぇ、、、ってかんじではあるよね。
素晴らしいOO設計をみたいのに。
あと、なんかこの人たち速度が遅くなるのを気にしていない実装をする。
富豪。
配列にキーが存在するかブンブンましたり。
すごい、、
たしかに10個とか程度だからおそくならないけど、、、
逆に僕の設計というか、見通しと勘があまいから怖いだけなんだろうけど、、
●同僚
あくまで中規模サイト向けな気がするんだよね
○中年
色々考えさせられるよ。。。
●同僚
ネットサービス的な
○中年
そうねぇ。
●同僚
巨大業務サイトとか無理な気がする
○中年
でも台規模でもスケールさせればいいっておもえば、
問題ない気もするなぁ。
ああそうか!dbに負荷さえかけなければ、どんな富豪してもいいんだ!
わかったよ!
わかった!!
●同僚
wwww
○中年
新しい知見じゃない?
●同僚
DBのIOに比べればっていう割り切り?
○中年
そう。
あとスケールのさせやすさ。
●同僚
オンメモリならいいじゃんという
○中年
DBのスケールは少し難しい。
WEBのスケールは簡単。
動的リンクと静的リンクの実行時のコスト差について
1秒間に1万件ほど処理するかなりハードなシステムで、あるライブラリを導入するときに「静的リンクじゃないと遅いよ?」と指摘をうけました。私は「ロードコストは確かにかかるかもしれないけど、なんで動的な方が遅いの?ロード後はアドレスジャンプだから実行時コストは変わらないのでは?」という疑問を持ちました。この答えを探した結果、結構長い調べ物になりました。
そもそもリンクとはなにをしているの?
分割コンパイルしている場合、それぞれのソースにたいしてオブジェクトファイルが生成され、そのオブジェクトファイル同士をリンクし実行ファイルを生成します。
実はあれもこれもELFフォーマット
- 実行可能ファイル
- 共有オブジェクトファイル
- コンパイラが作成するオブジェクトファイル
以上はすべてELFフォーマットです。
共有オブジェクトファイルというのは3番目のオブジェクトファイルをarコマンド(tarに似ている)でまとめたものです。
実際のELFファイルの中身
ELFファイルにはコンパイラが解析翻訳した機械語のほかに下記の情報を持っています。
- リロケータブル情報
実行時にしか「何処の実アドレスを使うか?」は決まりません。そこでオブジェクトファイルは「とりあえず先頭は0から始まる」として生成されます。そこをどのように調整するか?のオフセット情報を持っており、実行時に有効な実アドレスへと書き換え(リロケーション)されます
また動的ライブラリの場合、見つかっていない(名前未解決)シンボルだけのシンボルテーブルをもち、ローダーがこの未解決シンボルテーブルを参考にリロケーションを行います。 - シンボル情報
関数や変数の名前や位置、スコープ、タイプなどの情報です。コンパイルが終了すれば破棄されますが、残す事も出来ます。デバッグや統計情報などで利用されます。
またDLLではこのシンボル情報をもとにリローケションを行います。
面白いもので、シンボル情報さえ持っていれば実行可能ファイルもDLLとして利用可能です。 - デバッグ情報
そもそもリンクとはなにをしているの?2
僕の知っているリンクとはコンパイルの時にオブジェクトファイルをリンクすることだけでした。しかし、静的や動的などリンクの方法は色々あります。そこで共有オブジェクトのリンクのされ方をきちんと調べてみました。
- 静的リンク
実行可能ファイルのなかに共有オブジェクトファイルがコピーされ取り込まれます。取り込まれた分だけファイルサイズは大きくなります。共有オブジェクトがバージョンアップした場合は再度リンクし実行ファイルを作り直す必要があります。 - 動的リンク
実行可能ファイルのなかで「どの共有オブジェクトのどの部分をつかうか」だけを記録し、実行可能ファイルの起動時にローダー(リンクしてくれるOSの機能)がリンク作業を行います。 - もっと動的リンク
pluginなど、「どの共有オブジェクトのどの部分をつかうか」も動的に決めたい場合があります。その場合は明示的にロード作業を行い(dlopen()とかdlsym())、さらに動的にリンクすることも可能です。
(参考 http://akito.wiki.fc2.com/wiki/%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB%E3%83%BB%E3%83%AA%E3%83%B3%E3%82%AF)
(参考 http://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA)
僕の疑問の核心をつく記述がwikiにありました。
一部分を引用します。
ローダの処理は、メモリ上の各ライブラリの位置が実際にロードされるまで確定しないため、ちょっとしたトリックを必要とする。ディスク上のファイル内に絶対アドレスを書きこんでおくことはDLL内であっても不可能である。理論的にはメモリにロードされたときにライブラリを参照している部分を全て書き換えて正しいメモリ上の位置を参照するようにすることはできるが、それによって消費される時間とメモリは無視できない。その代わりに多くの動的リンクシステムではアドレス欄が空欄となったシンボルテーブルをコンパイル時に用意する。ライブラリへの参照は全てこのシンボルテーブルを経由して行われる(コンパイラはシンボルテーブルからアドレスを取り出して使うコードを生成する)。メモリにロードされたとき、ローダがこのテーブルを書き換える。
http://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA より
そうです。動的ロードの場合、関数の呼び出しコストとしてシンボルテーブルの経由コストがかかるのです。静的ロードの場合は全て実行可能ファイルに取り込まれているのでメモリ上のアドレス解決は問題ありません。というか静的ロードも、コンパイル時のオブジェクトファイルのリンクもやってる事は変わりなかったのです。
そしてようやく、「動的ロードの場合は静的ロードの場合よりもコストがかかる」という答えを得られました。
(参考 http://wiki.osdev.info/?ELF%2F%BC%C2%B9%D4%BB%FE%A4%CE%CF%C3)
メモ プロセスの挙動を調べたいとき
今から書く事はSoftware Design 201105にすべて載っていますが自分の整理のためにここに書きます。
開いているファイルがみたい。例えばログとかね。
/proc/${pid}/fd 以下のディレクトリは開いているファイルが一覧としてみられます。
ここに無いという事はsyslogか書いてないかです。
今動いているプロセスがどんな環境変数かしりたい
前あったトラブルでcronから実行すると環境変数がたりなくて大変という事態がありました。そんなときに
/proc/${pic}/environ をcatしたら環境変数がわかります。
動いているプロセスのパスがしりたい。
実行ファイルのパスは /proc/${pid}/cwd のリンク先で見られます。