2010/11/26

Node.js Canvas共有に絵を保存するGallery機能追加しました

Canvas共有サーバーが機能しているかどうか、よくチェックしているのですが、たまに
「すごい!」
「wonderful!」
「ちょっとカクカクするけどすごい!感動!」
といったメッセージが書きこまれていることがありました。
こういうのを見ると、嬉しくなります!
それに加え、折角書いてもらった絵が保存されずに消されるのは勿体無いことだと感じたので、絵を保存する機能を付け加えました。

YkitamotoさんにcanvasのtoDataURLというメソッドを教えていただいたので、それを利用することにしました。クライアントがClearボタンを押すと、canvasのデータがbase64形式でエンコードされてサーバーに届きます。サーバーはそれをデコードして保存します。
消そうと思ったら保存されてしまうのです。消しゴムは今のところ意図的に付けていません。

デモ
こちらです。どんどん書いてください!
Galleryはこちら。WebSocketに対応していないブラウザでもGalleryは見れます。


参考文献
toDataURL() - WHATWG

2010/11/17

WebWorkersを使ってJavaScript並列素数計算

Web Workersはページ内でバックグラウンドで並列にスクリプトを実行するための機能です。メッセージパッシングでスレッドのような操作ができます。これを用いることにより、例えばJavaScriptで非常に時間のかかる処理をする際、ブラウザが固まってしまう状況を避けることができます。
今回はWeb Workersを使って並列に素数を数えてみようと思います。

以下のデモでは 1000000000001から 1000000001000までの素数を、子Workerを4つ生成し、それらに仕事を等分して分け与えて計算させています。( ) 内は子WorkerのIDです。
申し訳ありませんが、今のところFirefoxのみでしか動きません。ChromeやSafariではWorker内でWorkerオブジェクトにアクセス出来ないのです。

デモ
http://etsukata.com/js/ww.html


Souce code
ww.js
worker.js

Web Workersを触ってみて、これは「ミニErlangだ」と感じました。Erlang程自由にWorker間でメッセージを自由にやりとり出来ないものの、非同期メッセージパッシングというパラダイムは共通です。
Web Workersは単に「ブラウザが固まらないようにバックグラウンドで処理するだけのもの」ではありません。将来的にはJavaScriptでのマルチコアプログラミングや分散コンピューティングに使われるのでないかと予想しています。だいぶ先(5年以上)のことと思いますが。もしかすると、現在ErlangがやっていることがWeb Workersを用いてJavaScriptで記述されるようになる日が来るかもしれません。未来的超高水準言語JavaScriptがErlangすら内包するかも、と嘯いてみます。

nodejs.orgにありがたいお言葉を見つけましたので以下に転載します。
The fundamentals of scalable systems are fast networking and non-blocking design—the rest is message passing. In future versions, Node will be able to fork new processes (using the Web Workers API ) which fits well into the current design.


仕様が定まっておらず、実装が錯綜しているので今のところはなんとも言えない感触です。

追記
Chromeがsubworkerをつくることが出来ない問題についてhidekiyさんにより情報頂きました。いつもお世話になります。Chromiumにissueが出ています。
http://code.google.com/p/chromium/issues/detail?id=50432

参考文献
Web Workers specification - WHATWG
http://www.whatwg.org/specs/web-workers/current-work/
The Basics of Web Workers - HTML5Rocks
Using web workers - MDC
https://developer.mozilla.org/En/Using_web_workers

2010/11/12

Erlang + Websocket でCanvas共有

拙作のerlang_websocket_serverを用いて、Canvas共有をErlangに移植しました。
非同期メッセージパッシングというパラダイムのおかげで簡単に書けます。

こちらです!どんどん落書きしてください!
http://etsukata.com/erl/cvws.html
ChromeおよびSafariで動作確認しています。複数のウインドウを立ち上げて描いてみてください。

クライアントサイドのコードはNode.js版と全く一緒です。
サーバーサイドのコードを以下に掲載します。

githubでお読みになると、横スクロールがなくて見やすいです。
https://github.com/Etsukata/erlang_websocket_server/blob/master/example/canvas_sharing_handler.erl

-module(canvas_sharing_handler).
-import(websocket_server, [unicast/2, broadcast/2, sendall/1]).
-compile(export_all).

go() -> websocket_server:start("etsukata.com", 9000, ?MODULE, canvas_sharing_handler, [[],[]]).

canvas_sharing_handler(IDList, PointList) ->
  receive
    {open, ConnectionID} ->
      broadcast("@NC:" ++ ConnectionID, ConnectionID),
      unicast("@ID:" ++ ConnectionID, ConnectionID),
      StrIDList = lists:foldr(fun(X, Xs) -> X ++ "," ++ Xs end, "", IDList),
      Str = case StrIDList of
              []   -> [];
              _Any -> lists:sublist(StrIDList, length(StrIDList) -1)
            end,
      unicast("@PT:" ++ Str, ConnectionID),
      lists:foreach(fun(X) -> unicast(X, ConnectionID) end, lists:reverse(PointList)),
      canvas_sharing_handler([ConnectionID|IDList], PointList);
    {message, Data, ConnectionID} ->
      case lists:member($@, Data) of
        true ->
          case string:substr(Data, 1, 3) of
            "@CU" ->
              broadcast(Data, ConnectionID),
              canvas_sharing_handler(IDList, []);
            _Any ->
              canvas_sharing_handler(IDList, PointList)
          end;
        false ->
          broadcast(Data, ConnectionID),
          canvas_sharing_handler(IDList, [Data|PointList])
      end;
    {closed, ConnectionID} ->
      broadcast("@CL:" ++ ConnectionID, ConnectionID),
      canvas_sharing_handler(lists:delete(ConnectionID,IDList), PointList);
    Any ->
      io:format("Any:~w~n", [Any]),    
      canvas_sharing_handler(IDList, PointList)
  end.
Node.js版のソースコードとほとんど長さは変わっていません。
Node.jsのスピード感のある開発も楽しいです。しかしErlangのメッセージパッシングで考えたほうがほうが単純に物事を捉えることができ、これもまた楽しいのです。僕はNode.jsとErlang、どっちも好きです!

2010/11/11

ErlangでWebSocket Server実装を書きました

こちらで公開しています
https://github.com/Etsukata/erlang_websocket_server
この記事はGithubに公開している情報を日本語で若干加筆して提供いたします。


動機
以前Node.jsのWebSocket Server実装であるnode-websocket-serverを用いてCanvas共有アプリをつくりました。そのとき手軽でスピード感のある開発に快感を覚えたので、ネットワークとの親和性のが非常に高く、今注目を集めているErlangではできないものかと思い、作りました。
Dave Bryson氏による実装であるerlang_websocketを主に参考にさせて頂きました。氏の実装では一対一の相互通信を行うものであったので、それを拡張し、特定のクライアントにデータを送信したり、マルチキャストしたりできるようにしました。
なにより、node-websocket-serverのように手軽にWebSocketを用いたアプリをつくれるようにするのが目的です。

Process Design Pattern
Erlangでシステムを構築するときはどのようなProcess Design Patternを採用するかが鍵になります。当ライブラリでは以下のような構造になっています。



Socket Receiverがクライアントからデータを受け取り、順々に右のプロセスに渡していきます。ReceiverはWebSocket Frameから生のデータを取り出しHandlerに渡します。Handlerはそれを加工しSenderに渡します。SenderはWebSocket Frameをつくり、各クライアントとつながっているSocket Senderに対しユニキャストまたはマルチキャストでデータを配信します。Socket Senderは受け取ったものをクライアントに送信します。
と、いった感じです。
ユーザーはHandlerの振る舞いについてのみ記述すればいいようになっています。

サンプル
エコーサーバーを書く場合は以下で十分です。

-module(echo_handler).
-compile(export_all).
-import(websocket_server, [unicast/2]).
go() ->
   websocket_server:start("localhost", 9000, ?MODULE, default_echo_handler, []).
default_echo_handler() ->
  receive
    {message, Data, ConnectionID} ->
      unicast(Data, ConnectionID),
      default_echo_handler();
    _Any -> default_echo_handler()
  end.

ドキュメント
こちらにあります。
http://etsukata.com/erl/docs/


今後
これからはどのような機能を盛りこんでいくかを決めるために、いくつかこのライブラリを用いたWebSocketアプリをつくっていくつもりです。経験を積んで、何が必要で重要なのか、何が要らない物なのかを見定めたいと思っています。

参考URL
Comet is dead long live websockets
http://armstrongonsoftware.blogspot.com/2009/12/comet-is-dead-long-live-websockets.html
davebryson / erlang_websocket
https://github.com/davebryson/erlang_websocket
MiCHiLU / erlang_websocket
https://github.com/MiCHiLU/erlang_websocket

2010/11/07

[Node.js] How to handle ECONNRESET, Connection reset by peer and automatic crashing.

Error Message
node.js:63
    throw e;
    ^
Error: ECONNRESET, Connection reset by peer
    at Stream._readImpl (net:304:14)
    at IOWatcher.callback (net:454:24)
    at node.js:768:9

The error 'ECONNRESET, Connection reset by peer' like the above is thrown when a connection was forcibly closed by peer. For example, a sudden lost of connection due to unstable network environment or unexpected crash of a browser, etc. 
If such exception bubbles all the way back to event loop, the node process print a stack trace and exit as the default action. Try/catch blocks can't catch the excption which occurs in event loop.
To prevent these errors from crashing down your node server, please use

process.on('uncaughtException', function (err) {
  // handle the error
});

Reference
[nodejs.org] Event: 'uncautException'
[nodejs@googlegroups.com] How to prevent node from dying on error?
[technet.microsoft.com] Connection reset by peer

Node.js + Websocket Canvas共有システムが時折ダウンする問題について

2010/10/27にCanvas共有Websocketサーバーを作動させて以来、サーバーがのべ三度にわたりダウンするという問題が発生していました。原因の究明を進めていたところ、一部ダウンする状況の再現及び対処法を突き止めましたのでご報告申し上げます。なお、ダウン中にアクセスされ、Canvas共有を体験なさることが出来なかった読者の方々には深くお詫び申し上げます。これからもダウンの折には早急な原因の解明と対処に努めますので、どうか今後ともよろしくお願いいたします。

サーバーダウン時のエラー出力の内容
node.js:63
    throw e;
    ^
Error: ECONNRESET, Connection reset by peer
    at Stream._readImpl (net:304:14)
    at IOWatcher.callback (net:454:24)
    at node.js:768:9

原因
Node.jsでは発生した例外がイベントループまで到達した場合、デフォルトの動作として、スタックトレースを出力し、終了することになっています。上記の例外が発生した場合、サーバーはダウンします。なお、スタックトレースをご覧いただければわかりますとおり、例外がイベントループ内において発生していますので try/catchで捕まえることはできません。
問題は、どのような状況において上記の例外が発生するか、ということです。
上記の例外は時折にしか発生せず、原因がなにかわからなかったのですが、ようやく突き止めました。Websocketクライアントが正常に接続を終了しない場合に発生していたのです。試しにサーバーへのコネクションを張ったまま、LANケーブルを引っこ抜いてみると、まんまと上記の例外が出ました。おそらくは不安定なネットワーク環境のもとCanvas共有にアクセスし、途中でネットワークが切断された際やブラウザがクラッシュした際等に発生していたのでしょう。

対策
例外発生時の動作をデフォルトのものから変更します。
参照:Event: 'uncaughtException'

ちなみに「Connection reset by peer node.js」で検索したところ、同じ問題で困っていた仲間が大量にいました。


Node.jsで何か作る際には、あらかじめ例外発生時の動作を適切なものに設定なさることをおすすめします。デフォルトだとプロセスは終了してしまいます。
ただ、なんでもprocess.on('uncaughtException')を使って何事もなかったかのようにサーバーを動かし続けることには注意が必要な場合があります。以下を御覧ください。
node.js - Dealing with uncaught exceptions
http://debuggable.com/posts/node-js-dealing-with-uncaught-exceptions:4c933d54-1428-443c-928d-4e1ecbdd56cb