前回の記事であっという間に組み込みが完了してしまったnode.jsですが、さっそくプログラムを動かしてみたいと思います。
今回はnode.jsの特徴や実力を手がるに知ることができ、定番として多くのサイトで紹介されるチャットアプリを新しいライブラリSocket.ioを使いながら紹介していきます。
リモート版はこちらです。
すぐに試したい方へ
サンプルファイルとその使い方はこちらにあります。
今回やろうとしていること
node.js上で動くチャットアプリを作ります。後からでも使い回せるように、下のような事を意識しながら進めたいと思います。
- コードが長くなっても分かりやすさを優先する
- そのままDLして使える(動く)ものを作る
- css装飾など、あとで各自追加できそうなものはできる限り省く
リアルタイム双方向通信を支える「ソケット通信」
前回の記事でnode.jsの特徴として紹介した「イベントループ」と「ノンブロッキングI/O」は、(ブラウザ等の)クライアントからのリクエストをサーバ側で受付け、処理結果が出た時にサーバからクライアントへ通知する仕組み(”プッシュ型”と呼ばれています)を採用しているのですが、受付後に完全に切り離されてしまうと、処理結果を受け取ることができなくなってしまいます。
今回実装するソケットは接続を維持し、サーバとクライアントがいつでも通信できる状態を保ってくれます。
つまり、ソケット通信は今回作るチャットに必要な技術という事になります。
調べてみたところ、ソケット接続を実現するモジュールにはWebSocketとsocket.ioの2つがあるようですが、WebSocketの拡張版と言われているsocket.ioが広く使われているようなので、今回はsocket.ioを使って進めていきたいと思います。
追記:WebSocketとsocket.ioの違いについて書かれている良い記事がありました。socket.ioを使うとPC以外の環境で関係なしで使える、ってあたりが違うみたいですね。
パソコン入門: Socket.IOって結局何なの!?と思いながらtransportを変えるとどうなるのか試したりした Socket.IOって結局何なの!?と思いながらtransportを変えるとどうなるのか試したりした Socket.IOって結局何なの!? 前回、”Socket.IOって何だろうと思っていじってたら … |
動作環境
node.js
今回の記事ではチャットアプリは前回の記事で紹介しているnode.jsのセットアップ後の環境を前提に進めていきますので、まだの方はこちらを先に済ませておいて下さい。
5分で終了。node.jsの環境構築が拍子抜けするほど簡単だったのでサンプルプログラム付きでまとめてみました【Mac編】 : Tettori!!
socket.ioをインストール
-
今回のプログラムを入れるフォルダを作成します。(ここではデスクトップに nodejs_chat という名前でフォルダを作っています)
-
ターミナルを起動します
-
以下のコマンドを入力して先ほど作成したフォルダに移動します。
cd desktop/nodejs_chat
-
移動した後、下のコマンドでsocket.ioのインストールします。
npm install socket.io
-
Enterを押すとインストール作業が完了まで自動的に進むので、プロンプト($マーク)が表示されるまでしばらくお待ちください。(途中表示される数字などはバージョンによって変わります)
-
上の画面になればsocket.ioのインストール完了です。フォルダを開いてみるとnodejs_chatの中にnode_modulesというフォルダが作られ、中にいろいろ入っていることが確認できると思います。
追記:下の記事で知ったのですが、node_modulesフォルダはnpmコマンドで追加していくモジュールが入るフォルダになっているようで、基本さわらなくていいみたいですね。
Node.jsを使ってみよう(1):Node.js、Socket.IO、MongoDBでリアルタイムWeb (1/2) – @IT
Expressを使ってWebアプリを作成 先ほどの例の通り、Webアプリを作成するにはNode.jsの標準ライブラリを使用して作成できる。しかしながら標準ライブラリのみでWebアプリを構築しようと …
モジュールインストール完了!
これで下準備完了です。次はいよいよプログラミングに入りますが、先にお話しておくと、最終的に2つのファイル(app.jsとindex.html)が最初に作ったフォルダ内に追加されます。手順に沿って保存するでも先に空ファイルを作っておくでも結構ですので、参考までにキャプチャを貼り付けておきます。
実装
いきなりですが、サーバサイドのコードを先に全て紹介したいと思います。
サーバサイドjavascript : ファイル名 app.js
var http = require("http"); var socketio = require("socket.io"); var fs = require("fs"); var server = http.createServer(function(req, res) { res.writeHead(200, {"Content-Type":"text/html"}); var output = fs.readFileSync("./index.html", "utf-8"); res.end(output); }).listen(process.env.VMC_APP_PORT || 3000); var io = socketio.listen(server); io.sockets.on("connection", function (socket) { // メッセージ送信(送信者にも送られる) socket.on("C_to_S_message", function (data) { io.sockets.emit("S_to_C_message", {value:data.value}); }); // ブロードキャスト(送信者以外の全員に送信) socket.on("C_to_S_broadcast", function (data) { socket.broadcast.emit("S_to_C_message", {value:data.value}); }); // 切断したときに送信 socket.on("disconnect", function () { // io.sockets.emit("S_to_C_message", {value:"user disconnected"}); }); });
コメント行が入ったりと、若干冗長なところもありますが、それでも30行程度。これだけでwebサーバが立ち上がり、さらにチャットもできてしまうのは驚きです。では、順に分けて説明して行きましょう。
機能を追加するrequire
これは他の言語でも同じ命令文で使われることがあるので知っている方は多いかもしれませんね。これは機能(モジュール)を追加する時に使われるもので、順番に以下のものが追加されています。
var http = require("http"); // http関連 var socketio = require("socket.io"); // ソケット通信 var fs = require("fs"); // ファイルの読書き
requireされたモジュールはあとあと使うためにvarに続く変数(http,socketio,fs)に入ります。名称はモジュール名と同じにするのが慣習みたいですね。具体的な使い方等については続けて書いていきます。
Webサーバを立ち上げる http.createServer()
var server = http.createServer(function(req, res) { res.writeHead(200, {"Content-Type":"text/html"}); // 起動直後にhttpヘッダに書き込む内容 var output = fs.readFileSync("./index.html", "utf-8"); // index.htmlファイルを読み込む res.end(output); // index.htmlを表示 }).listen(process.env.VMC_APP_PORT || 3000); // webサーバで利用するportを自動選択(リモート or ローカル)
var output = fs.readFileSync(“./index.html”, “utf-8”)
これはindex.htmlを読み込んでoutputという変数にまるごと入れています。そして、
res.end(output)
ここで表示させています。この res.end() はサーバ起動後に表示させるものを指定できるもので、一連の流れは「サーバが立ち上がったら自動的にindex.htmlを表示させてね」と理解できそうです。
process.env.VMC_APP_PORT
これは環境変数と呼ばれるものの1つで、リモートサーバのポート番号を保持しています。番号はサーバによって違うため変数化する事でその違いを吸収できるメリットがあります。ちなみに、本記事の最後にも書いていますが、次回の記事ではリモートサーバで動くチャットアプリの設置手順を紹介する予定なので、これについてはまた説明させてもらいます。
これでひとまずWebサーバは立ち上がりました。次はそこにソケット機能を追加していきます。
サーバとソケットを紐付ける socketio.listen()
var io = socketio.listen(server);
先ほどrequireしたsocketioのlisten関数のパラメータとして、同じく先ほど立ち上げたばかりのserverが設定されています。これにより、var で定義された変数 io がソケットを通してサーバとクライアントの連携部分を受け持つことなります。(ここではioにしていますが、変数名は任意で構いません)
io.sockets.on("connection", function (socket) { });
この部分は1つの大きなブロックになっていて、プログラムの残り全体を囲っています。ここはクライアントからのアクションを受付ける窓口になる部分で、書式についてはそのまま覚えてしまいましょう。(オフィシャルサイトも含めてそのパターンしか見つけられなかったのでひとまずこれで問題ないと思います)
functio(socket)の中の socket はクライアントからアクションがあった時にサーバが受け取るもので、これでクライアントを特定し、個別にメッセージを送ったりする事が可能になります。
では、続けて窓口で受け取った処理を処理する部分を見ていきましょう。
個別の要件を受け付けるイベントハンドラ
socket.on(){}は似たような形で3つ並んでいます。これらは窓口が受けた内容によって個別に実行されるもので、「イベントハンドラ」と呼ばれています。それぞれ最初の1行めを抜き出してみましょう。
socket.on("C_to_S_message", function (data) { socket.on("C_to_S_broadcast", function (data) { socket.on("disconnect", function () {
最初の””で囲まれた文字列と、function()の中にdataがあるかないかの違いだけで、3つとも書式が統一されています。文字列はイベント名と呼ばれ、クライアントが指定する事によってによって該当するものが実行されます。
オフィシャルサイトによると、イベント名は’connect’、’message’、’disconnect’以外であれば自由に使うことができます。自分で作ったイベントはもともと用意されているイベントと区別してカスタムイベントと呼ばれることもあります。今回は1,2番目がカスタムイベントになります。
function(data)で受け取るデータは、イベントを受け取る際に必要な情報が入ってきます。今回のケースで言えば、チャットのメッセージがそれにあたり、実際に1,2番目のイベントでデータを受け取っていることがわかると思います。
次は実際の処理内容を見てみましょう。
ソケットを使ってサーバ→クライアントでイベントを送信する
では今度は各イベントハンドラ内の処理を3つ抜き出してみましょう。
io.sockets.emit("S_to_C_message", {value:data.value}); socket.broadcast.emit("S_to_C_message", {value:data.value}); // io.sockets.emit("S_to_C_message", {value:"user disconnected"});
3つとも 命令文(“文字列”,{データ}) の形になっていると思います。これらはイベントハンドラと対になるもので、イベントを送信する側で使われる書式です。最初にイベントハンドラの内容を説明しているので、文字列とデータがそれぞれイベントハンドラのイベント名とデータと対になって使われることは想像できると思いますのでスルーして残りの命令文について説明します。
- io.sockets.emit → 自分を含む全員にメッセージを送信する
- socket.broadcast.emit → 自分を以外の全員にメッセージを送信する
いかがでしたか?プログラムの大部分がイベントによるデータのやりとりについての記述で、そこを理解できれば割りと早く習得できそうだと思われたかと思います。
これからクライアントサイドのjavascriptの説明に移りますが、内容が似通っているので、イベントの概念を理解していれば実はもう8割方説明は終わっています。あと少しです!
クライアントサイドjavascript : ファイル名 index.html
ここもすべてのコードを先に紹介します。
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <title>node.js chat</title> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script type="text/javascript"> // var s = io.connect(); //リモート var s = io.connect('http://localhost:3000'); //ローカル //サーバから受け取るイベント s.on("connect", function () {}); // 接続時 s.on("disconnect", function (client) {}); // 切断時 s.on("S_to_C_message", function (data) { addMessage(data.value); }); //クライアントからイベント送信(イベント名は自由に設定できます) function sendMessage() { var msg = $("#message").val(); //取得 $("#message").val(""); //空白にする s.emit("C_to_S_message", {value:msg}); //サーバへ送信 } function sendBroadcast() { var msg = $("#message").val(); //取得 $("#message").val(""); //空白にする s.emit("C_to_S_broadcast", {value:msg}); // サーバへ送信 } //jqueryでメッセージを追加 function addMessage (value,color,size) { var msg = value.replace( /[!@$%<>'"&|]/g, '' ); //タグ記号とかいくつか削除 $("#msg_list").prepend("<div class='msg'>" + msg + "</div>"); } </script> <style> *{ font-size:30px; margin:0; padding:0; } </style> </head> <body> <div id="msg_list" style="height:300px; overflow:auto;"></div> <form action="" method="post" onsubmit="return false;"> <input type="text" class="text" style="width:95%; padding:10px" id="message"/> <input type="submit" class="button" style="padding:10px" onclick="sendMessage();" value="みんなに送信" /> <input type="submit" class="button" style="padding:10px" onclick="sendBroadcast();" value="自分以外に送信" /> </form> </div> </body> </html>
htmlとjQueryの組み込み部分の説明については他のサイトの方が情報が豊富なのでそこへお願いするとして、ここではjs部分を抜粋して説明していきます。
socket.ioの組み込み
<script src="/socket.io/socket.io.js"></script>
ここは特に問題なさそうですね。socket.ioを使う際にはこれが必要になる、とだけ覚えておくと良いでしょう
クライアントからサーバへソケット接続する
// var s = io.connect(); //リモート var s = io.connect('http://localhost:3000'); //ローカル
こちら2行ありますが、上の1行はコメントアウトで無効になっていますので、今回は無視しても大丈夫です。
下の1行が意味するところは「http://localhost:3000 にこれからソケット接続をしますよ!接続後は s が仕切ります。」とう感じです。ローカルサーバへ接続する際に必要になる決まり文句だと思って問題ないと思います。(3000の数字は任意で変わってきます)
//サーバから受け取るイベント s.on("connect", function () {}); // 接続時 s.on("disconnect", function (client) {}); // 切断時 s.on("S_to_C_message", function (data) { addMessage(data.value); });
この部分は3つのブロックに分かれていますが、サーバサイドのスクリプトと殆ど一緒なので、理解できると思います。上の2つは”connect”と”disconnect”という基本イベントで一応入れておきましたが、処理自体がないので、説明は省きます。3番目は「通常のjavascript関数でaddMessage(data.value)を呼び出しています。
残りの部分は下のソースと一緒にまとめて説明します
//クライアントからイベント送信(イベント名は自由に設定できます) function sendMessage() { var msg = $("#message").val(); //取得 $("#message").val(""); //空白にする s.emit("C_to_S_message", {value:msg}); //サーバへ送信 } function sendBroadcast() { var msg = $("#message").val(); //取得 $("#message").val(""); //空白にする s.emit("C_to_S_broadcast", {value:msg}); // サーバへ送信 } //jqueryでメッセージを追加 function addMessage (value,color,size) { var msg = value.replace( /[!@$%<>'"&|]/g, '' ); //タグ記号とかいくつか削除 $("#msg_list").prepend("<div class='msg'>" + msg + "</div>"); } (途中略) <input type="text" class="text" style="width:95%; padding:10px" id="message"/> <input type="submit" class="button" style="padding:10px" onclick="sendMessage();" value="みんなに送信" /> <input type="submit" class="button" style="padding:10px" onclick="sendBroadcast();" value="自分以外に送信" />
- sendMessage() → イベント名”C_to_S_message”でテキスト枠の内容と一緒にサーバへ送信
- sendBroadcast() → イベント名”C_to_S_broadcast”でテキスト枠の内容と一緒にサーバへ送信
- addMessage() → イベントハンドラから送られてきたdataを使ってjQueryで”<div>を生成し表示枠に1行追加
以上です。後半駆け足になりましたがサーバサイドもクライアントサイドも同じようなフォーマットでメッセージを送り合っているという事がイメージできていれば問題ないと思います。
動作確認
ファイル一式はこちらからダウンロードできます
socket.ioを含むここまでのファイル一式を用意しておきましたので、必要な方はこちらからダウンロードしてください。
動作確認手順
-
ターミナルを起動し、プログラムのあるディレクトリへ移動します。(ここではデスクトップに nodejs_chat という名前でフォルダを作っています)
cd desktop/nodejs_chat
-
サーバプログラムを起動します。下のコマンドを入力して下さい。
node app.js
-
下のような表示になればサーバの起動は完了です。
-
続けて表示確認です。お好きなブラウザを起動して、下のアドレスを表示してください。
下のような表示になればアクセス成功です。node.jsのwebサーバからチャットプログラムが起動しました
-
チャットの動作確認の為、複数ウィンドウで動作確認をします。(ローカルサーバなので、同一端末で確認してください。)
-
一番左からAサン、Bサン、Cサンとしてメッセージを書いてみましょう。まずはAから送信します。
-
Enterもしくは”みんなに送信”ボタンを押すと、一斉に送信される事が確認できます。
-
次はBからブロードキャスト配信します。”自分以外に送信”ボタンをクリックします
-
AとCにメッセージが配信されました。
以上です。とても基本的な動作ではありますが、この仕組をベースにして送信するデータを画像にしたり、一緒にお絵かきしたりと、応用次第では可能性はどんどん広がると思います。このプログラムが役に立てば幸いです。また感想などあればお聞かせください!
次回はリモートサーバに挑戦します
今回はローカルサーバで同じPC内でのみ動くチャットを作りましたが、次回は無差別でチャットできるリモートサーバで動かしてみたいと思います。
次回はこれ↓を作ります。既に動くので複数のいろんな端末(PC,Mac,iOS,Android等)でアクセスしてみて下さい。(★注意★ここのURLにアクセスする人全員にメッセージが配信されるので、大事な内容は書かないようにしてください!)
続けて、以下に更に理解を深めるための参考サイトを集めてみました。node.jsをもっと深く学びたい方はこちらを参考にしてみて下さい。
もっと詳しく知りたい方へ
下記参考にさせて頂いたサイトです。もっと詳しい情報が欲しい方はこちらが参考になると思います。
socket.io本家サイト
Socket.IO: the cross-browser WebSocket for realtime apps. Introducing Socket.IO v.9 (4553 members) Email: HOME HOW TO USE BROWSER SUPPORT FAQ WIKI What i … |
こちら和訳サイトです
Socket.IO: the cross-browser WebSocket for realtime apps. Introducing Socket.IO v.9 (4553 members) Email: HOME HOW TO USE BROWSER SUPPORT FAQ WIKI How to … |
socketonついては触れていませんが、node.jsの基本的な部分をかなり丁寧に説明してくれています。オススメです。
Nodeビギナーズブック » Node.jsチュートリアル » Node.js 教程 本書は、Node.jsでのアプリケーション開発を始めようとする皆さんに、 ”高度な”JavaScriptについて知るべきあらゆることを解説します。 よくある”Hello World”チュートリアルの、 … |
こちら2サイトのサンプルはすぐに試すことができました。
[node] Socket.IOを使ったチャットアプリ。インストールから実装まで。 – YoheiM .NET [node] Socket.IOを使ったチャットアプリ。インストールから実装まで。 4月はnode.jsを使い倒すという目標のもと、 node.jsをインストールしたら、 … |
【Node.js】socket.ioを使って簡単にチャットを作る方法 | creator note よくFlashの記事を見て頂いてますが、最近はNode.jsが好物です。将来的に美味しいのかはわかりません!汗 Flash Sample Flash JavaScript Node.js API … |
僕はまだ試せていませんが、リッチなチャットアプリをお求めな方にオススメできそうなサイトです。
Node.jsとWebSocket.IOでチャットアプリを作る | mawatari.jp mawatari.jpウェブエンジニアのメモ帳 ブログ ブックマーク 本棚 GitHub プロファイル 2012年10月22日 Node.jsとWebSocket.IOでチャット … |
チャットに限らずここからいろいろ情報を得られそうです。僕も後でじっくり見させてもらいます。
Node.jsアプリ開発で参考になる記事 まとめ | Developers.IO Developers.IO ヘルプ 執筆者 ランキング 検索 ホーム Node.jsアプリ開発で参考になる記事 まとめ 2013年02月20日 執筆者 袴田 RANK 24 ツイート 145 いいね … |