アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

はじめての共同作業 Canvas編 (node.js + websocket)

こんにちは、中川です。
先月無事に結婚をした開発者が一名おり、近年アシアルでは徐々に既婚者が増えてきている状況です。
ということで、結婚といえば共同作業ですよね。

今までは、一人で作業していて大変なことが色々あったと思いますが、
二人(複数)でやれば、乗り越えられることもあることでしょう。
Webアプリでも最近は、より共同作業がしやすい環境ができつつあるように思います。

そこで、今回はWebSocketを使ったリアルタイム通信でのやり取りを行い、
一緒にお絵描きができるサンプルアプリを作ってみました。



■■■概要■■■
・アプリ概要
・・Canvas + WebSocket

・対応ブラウザ
・・Chrome or Safari (他、WebSocketが使えるブラウザ)

・サーバ側プログラム
・・node.js 0.2.0
・・express@1.0.0rc2
・・websocket-server@1.3.50

※express, websocket-server は node.js のパッケージ管理システムの npm ( http://github.com/isaacs/npm ) でインストールしました。

node.js用のwebsocketサーバは何個かあるみたいですが
( 参照: http://github.com/ry/node/wiki/modules )、
今回は、比較的更新もされていて、手軽に利用できそうな node-websocket-server を使ってみました。


■■■アプリ構成■■■


.
`-- canvas-share
    |-- public
    |   |-- index.html
    |   `-- js
    |       `-- client.js
    `-- server.js


プログラム一式ダウンロード:canvas-share.tar.gz

■■■プログラム内容■■■

・index.html


<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8"> 
  <title>Canvas Share Demo</title>
</head>
<body>
  <canvas id="layer0" class="canvas" style="position: absolute; top: 0; left: 0; border: 10px solid #dddddd;" width="900px" height="600px"></canvas>
  <input type="button" id="clear" value="Clear" style="position:absolute;" />

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" src="/js/client.js?t=1149"></script>
<script type="text/javascript">
$(function(){

  var painter = new Painter('layer0');
  
  // WebSocket対応の場合は、コネクションを設定
  if (window["WebSocket"]) {
    var conn = new WebSocket("ws://" + document.location.host + "/");
    painter.setConnection(conn);
  } else {
    alert('This browser is not supported.');
  }
  
  $('#clear').click(function() {
    painter.clear();
  });
  
});
</script>
</body>
</html>


・js/client.js
※通信部分関連のみ抜粋



 /**
  * マウス移動時の処理
  */
 Painter.prototype.move = function(event) {
   if (!this.isDrawing) {
     return;
   }

   var points = {
     bx: this.beforeX,
     by: this.beforeY,
     ax: event.clientX - 10,
     ay: event.clientY - 10,
     c: this.strokeStyle
   };

   if (this.conn) {
     // 各座標をjson形式でサーバに通知
     this.conn.send(JSON.stringify(points));
   } else {
     this.drawLine(points);
   }

   this.beforeX = points.ax;
   this.beforeY = points.ay;
 };

 /**
  * キャンバスのクリア処理
  */
 Painter.prototype.clear = function(conn) {
   if (this.conn) {
     // CLEAR処理をサーバに通知
     this.conn.send('@CLEAR');
   } else {
     this.clearCanvas();
   }
 };

 /**
  * WebSocketのコネクション設定
  */
 Painter.prototype.setConnection = function(conn) {
   this.conn = conn;

   this.conn.onclose = function() {console.log('Close');};
   this.conn.onopen = function(){console.log('Connected');}

   var self = this;
   //メッセージ受信時の処理(クリアと描画)
   this.conn.onmessage = function(event) {
     if (event.data.indexOf('@') > -1) {
       if (event.data.indexOf('@CLEAR') > -1) {
         self.clearCanvas();
       }
     } else {
       var d = JSON.parse(event.data);
       self.drawLine(d);
     }
   };

 };


WebSocketが有効な場合は、マウスの移動座標と色をサーバに通知するようにし、また、受信したJSONデータからCanvasへの描画処理を行います。


・server.js


var sys = require("sys"),
    ws  = require('websocket-server');

/**
 * web-server
 */
var express = require('express');
var app = express.createServer();
app.configure(function(){
  // ファイルをそのまま出力するディレクトリの設定
  app.use(express.staticProvider(__dirname + '/public'));
});

/**
 * websocket-server
 */
//var json = JSON.stringify;
var server = ws.createServer({server: app});
var points = [];

server.addListener("listening", function(){
  sys.log("Listening for connections.");
});

server.addListener("connection", function(conn){

  sys.log('Hello');
  //server.broadcast("@HELLO");

  // 全てのログを初回接続時に送信
  if (points.length > 0) {
    for(var i in points) {
      conn.send(points[i]);
    } 
  }

  // メッセージ受信処理
  conn.addListener("message", function(message){
      if (message.indexOf('@') > -1) {
        points = [];
        server.broadcast(message);
      } else {
        points.push(message);
        server.broadcast(message);
      }
  });

});

server.addListener("close", function(conn){
  //server.broadcast("@BYE");
});

server.listen(3333);


expressは、単純に静的ファイル(html,js)を出力しているだけですので、今回は特に必要性はありませんが、プログラム記述の楽がしたかっただけです。

WebSocketサーバ側の処理では、投げられたjson文字列データをログ配列に保持し、そのまま broadcast で接続中のクライアントに返すようにしています。


■■■動作確認■■■

サーバを起動します。


$ cd canvas-share/
$ node server.js


あとは、対応ブラウザを複数ウィンドウ立ち上げて、それぞれ
http: //example.com:3333/ にアクセスすれば動作します。



■■■最後に■■■

今回の実装はかなりやっつけな部分もありますがご容赦いただければと思います。

現行のIEFirefoxなど主要ブラウザがまだWebSocketに対応していませんが、socket.io などのライブラリもあるようですので、対応させることもできそうです。

node.jsを利用すれば、phpでは難しい大量の同時接続をさばくこともできますので、このようなリアルタイムコラボレーション系のWebアプリも簡単につくることができそうですね。
今後も色々と試していきたいと思います。