WebSocket(Socket.io)を触ってみる(チャットアプリの作成まで)

HTML5 Conference 2012やJavaScriptの本を見てるとWebSocketを使って何かしたくなったので早速始めてみる。

WebSocketを使うにはnode.jsでSocket.IOを使うのが一番良さそう。ということで色々解説記事などを漁って試してみたけど、バージョンの違いのせいかなかなか動かせない。仕方ないので公式サイトを見ながらぼそぼそとやっていくことにした。

使った環境


  • Mac(OS X 10.7.4)

  • node.js v0.6.21-pre

  • Express 3.0.0rc4

  • Socket.IO 0.9.10


環境構築

前にほんのちょっとだけnodeやexpressなんかをいじっていたおかげで大体の環境は整っていた。とりあえず各々のバージョンを上げてやる。

$ nvm install v0.6.21
$ npm update express -g

これで node v0.6.21とexpress 3.0.0rc4が入った。

アプリの作成

$ express socketio_test -e
$ cd socketio_test

ejsを使う。

npm installする前にpackage.jsonをいじってsocket.ioもインストールするようにする。こういうのに後方互換性が無いことはもう痛いほど分かったので、*ではなくきちんとバージョンを指定する。

{
  "name": "application-name",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app"
  },
  "dependencies": {
    "socket.io": "0.9.10",  // 追加
    "express": "3.0.0rc4",
    "ejs": "*"
  }
}

編集

とりあえずGitHubのコードを写していく。ただし幾つか変更点がある。

ポート番号の変更

サンプルのコードでは80番ポートを指定しているけど、特権ポートを使用するにはsudoコマンドを使う必要があるらしくて面倒なので、適当に8000番ポートを使用するように変更する。

appの設定の変更

ここらへんはかなりあやふやで、まあ動いてるからいいかという感じでやったから間違ってるかもしれない。

まず前提として、


  1. Socket.IOのlisten()にはhttp.Serverを渡さないといけない。

  2. express3.0からexpress()の戻り値がhttp.Serverのインスタンスではなくなった。(※参考)

  3. なので、自前でserver=http.createServer(app)してそのインスタンスを渡す必要がある。

  4. 最後にserver.listen(8000)でポートを設定する。


ここまではほぼサンプル通りであってるはず。だけど、これだけだとポート関連がぐちゃぐちゃして結局うまく動かせない(サーバーが二つ?建っちゃったり、EADDRINUSEになっちゃったり)。

なので「ひょっとしたら、自前でserver作ったし、ポートも指定したからappの方ではそういう設定しなくてもいい?」と思って


  • app.set('port', process.env.PORT || 3000);
    の行をコメントアウトしてみる


と、一応ちゃんと動いてくれるようになった。(追記あり)

最終的に

app.jsとindex.ejsのコードは次のようになった。

/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')
  , user = require('./routes/user')
  , http = require('http')
  , path = require('path');

var app = express()
  , server = http.createServer(app)
  , io = require('socket.io').listen(server);

server.listen(8000);

io.sockets.on('connection', function (socket) {
  socket.emit('news', { hello: 'world' });
  socket.on('my other event', function (data) {
    console.log(data);
  });
});


app.configure(function(){
  // app.set('port', process.env.PORT || 3000);
  app.set('views', __dirname + '/views');
  app.set('view engine', 'ejs');
  app.use(express.favicon());
  app.use(express.logger('dev'));
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(path.join(__dirname, 'public')));
});

app.configure('development', function(){
  app.use(express.errorHandler());
});

app.get('/', routes.index);
app.get('/users', user.list);

http.createServer(app).listen(app.get('port'), function(){
  console.log("Express server listening on port " + app.get('port'));
});
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <script src="/socket.io/socket.io.js"></script>
    <script>
      var socket = io.connect('http://localhost');
      socket.on('news', function (data) {
        console.log(data);
        socket.emit('my other event', { my: 'data' });
      });
      socket.on("say", function(data){
        console.log(data)
      })
    </script>
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
  </body>
</html>

追記


見直してみたら簡単な話だった。とりあえず修正版のapp.jsのコード

/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')
  , user = require('./routes/user')
  , http = require('http')
  , path = require('path');

var app = express();

app.configure(function(){
  app.set('port', process.env.PORT || 8000);
  app.set('views', __dirname + '/views');
  app.set('view engine', 'ejs');
  app.use(express.favicon());
  app.use(express.logger('dev'));
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(path.join(__dirname, 'public')));
});

app.configure('development', function(){
  app.use(express.errorHandler());
});

app.get('/', routes.index);
app.get('/users', user.list);

var server = http.createServer(app).listen(app.get('port'), function(){
  console.log("Express server listening on port " + app.get('port'));
});

var io = require('socket.io').listen(server);

io.sockets.on('connection', function (socket) {
  socket.emit('news', { hello: 'world' });
  socket.on('my other event', function (data) {
    console.log(data);
  });
});

動かしてみる

なんとか動くようにはなったので、早速localhost:8000にアクセスしていじってみる。

とりあえずChromeからShit+Ctrl+Jでコンソールを開いてみると、早速ハローワールドオブジェクトが送られているのが分かる。お返しにとsocket.emit("my other event", "aaa")とコンソールに入力してクライアント側からイベントを起こしてやるとサーバーのログにきちんとaaaと表示される。すごい!

終わりに

以上でなんとかSocket.IOが扱えるようになった。Socket.IOのAPIは、on()でイベントハンドラを追加したり、emit()で任意のイベントを発生させたり、broadcastやjsonといったフラグを挟むことで振る舞いを変えたりと単純明快。早速チャットアプリでも作ってみようっと。

チャットアプリ

というわけでチャットアプリについてはまた別記事で書こうと思ってたんだけど、あまりにも簡単にできてしまったので同じ記事に書く。やったことといえばサーバーサイドはイベントハンドラを少し書き換えて、クライアントサイドはちょっとHTMLいじってjQueryをちょちょっと使っただけ。これだけでできるなんて、改めてすごい!と思った。

ソース一式はこちらに BlackKetchupTea512 / simple_chat_room

追記


もう少しマシなソースに仕上げたBlackKetchupTea512 / simple_chat_room2