Node.jsとWebSocket.IOでチャットアプリを作る
前回はnvmとNode.jsとWebSocket.IOをインストールしてWebSocketアプリの環境を作るところまで終わりました。今回はNode.jsとWebSocket.IOでチャットアプリを作ってみました。まだ完全に理解できていない部分もありますが、公開します。上記は完成したチャットアプリのキャプチャです。ソースコードはGitHubにもコミットしています。
mawatari / WebSocket-Chat – GitHub
制作環境
ソフトウェア | バージョン |
---|---|
Node | 0.8.12 |
WebSocket.IO | 0.2.1 |
VMware上、LAN上、インターネット上のCentOS6.3で動作確認済み。WebサーバはApache。
クライアントでの確認は以下の通り。
Mac, WindowsのChrome 22, Firefox 16, Safari 6, Opera 12(動作不可), IE 9(動作不可).
iPad2(iOS 5.1.1), new iPad(iOS6), iPhone4~5(iOS6)のSafari, Chrome.
Galaxy S2(Android2.3.3)のブラウザ(動作不可).
目次
WebSocket Chat サーバの実装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // VMware上のCentOS6で動作させたときの例 // 8888番ポートでクライアントの接続を待ち受ける var ws = require('websocket.io'); var server = ws.listen(8888, function () { console.log('\033[96m Server running at 172.16.145.136:8888 \033[39m'); }); // クライアントからの接続イベントを処理 server.on('connection', function(socket) { // クライアントからのメッセージ受信イベントを処理 socket.on('message', function(data) { // 実行時間を追加 var data = JSON.parse(data); var d = new Date(); data.time = d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate() + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds(); data = JSON.stringify(data); console.log('\033[96m' + data + '\033[39m'); // 受信したメッセージを全てのクライアントに送信する server.clients.forEach(function(client) { client.send(data); }); }); }); |
ご覧の通り、WebSocket.IOを利用することで非常に簡単にWebSocketサーバを作ることができます。まあ大したことをやってないからというのもありますが。実行時間の追加をサーバ側でやってるくらいで、送られてきたものをそのまま返すエコーサーバに近いですね。保存の処理等が入ってくる場合は、もっとコード量が増えるでしょう。
さて、コードを見ていきます。
4 5 6 | var server = ws.listen(8888, function () { console.log('\033[96m Server running at 172.16.145.136:8888 \033[39m'); }); |
4〜6行目。
8888番ポートを指定して待ち受けるようにしています。その際、コンソールにメッセージを出力するようにしました。ファイアウォールを有効にしている場合は、8888番ポートの設定を忘れずに。
8 9 10 11 12 | // クライアントからの接続イベントを処理 server.on('connection', function(socket) { // 処理 // 処理 }); |
8行目〜。
クライアントからの接続イベントを処理したい場合は、ここに記述します。今回は接続単体では特に処理していません。
10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // クライアントからのメッセージ受信イベントを処理 socket.on('message', function(data) { // 実行時間を追加 var data = JSON.parse(data); var d = new Date(); data.time = d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate() + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds(); data = JSON.stringify(data); console.log('\033[96m' + data + '\033[39m'); // 受信したメッセージを全てのクライアントに送信する server.clients.forEach(function(client) { client.send(data); }); }); |
10行目〜。
クライアントからのメッセージ受信イベントを処理しています。サーバ側での処理の例として、受け取ったメッセージに実行時間を追加しています。ログを保存したりする場合は、ここに追記していけば良いですね。次にコンソールにデータを出力し、その後、接続している全てのクライアントにメッセージを送信しています。
WebSocket Chat クライアントの実装
HTML
何かをちゃちゃっと作るとき、最近はjetstrapで、ざっくりとイメージを組んでから、作り込みを始めます。Webデザインのセンスに優れていなくても、それなりのものが出来上がるので重宝しています。使い方は以下のような感じです。
参考リンク:jetstrap – The Bootstrap Interface Builder
- コンポーネンツウィンドウから使いたいアイテムを配置したい場所にドラッグ&ドロップします。
- ボタングループを配置したときの例。
- responsive CSSに対応。上部に表示されたアイコンをクリックすることで各デバイスでの表示を確認できます。
- 右下のCSS/HTMLタブをクリックすることで、ソースコードを確認したりダウンロードしたりできます。
今回も例に洩れず、HTMLはjetstrapを使ってざっくりと組んで、一部に修正を加えました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>WebSocket Chat</title> <meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1"> <meta name="description" content=""> <meta name="author" content=""> <!-- Le styles --> <link href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" rel="stylesheet"> <style> body { padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */ } </style> <link href="http://twitter.github.com/bootstrap/assets/css/bootstrap-responsive.css" rel="stylesheet"> <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements --> <!--[if lt IE 9]> <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"> </script> <![endif]--> <!-- Le fav and touch icons --> <link rel="shortcut icon" href="assets/ico/favicon.ico"> <link rel="apple-touch-icon-precomposed" sizes="144x144" href="assets/ico/apple-touch-icon-144-precomposed.png"> <link rel="apple-touch-icon-precomposed" sizes="114x114" href="assets/ico/apple-touch-icon-114-precomposed.png"> <link rel="apple-touch-icon-precomposed" sizes="72x72" href="assets/ico/apple-touch-icon-72-precomposed.png"> <link rel="apple-touch-icon-precomposed" href="assets/ico/apple-touch-icon-57-precomposed.png"> <style type="text/css"> .container { max-width: 768px; } #chat-area { margin-bottom: 15px; } .meta { color: #999999; font-size: 0.9em; } .chat-time { margin-left: 15px; } </style> </head> <body> <div class="navbar navbar-fixed-top navbar-inverse"> <div class="navbar-inner"> <div class="container"> <a class="brand" href="#">WebSocket Chat</a> <ul class="nav"> <li><a href="#">Home</a></li> <li><a href="#">About</a></li> <li><a href="#">Contact</a> </li> </ul> </div> </div> </div> <div class="container"> <h1>WebSocket Chat</h1> <div id="chat-area"> <div class="input-prepend"> <span class="add-on" id="user-name"><i class="icon-user"></i></span> <input type="text" id="textbox" placeholder="今なにしてる?"> </div> </div> <ul id="chat-history" class="unstyled row-fluid"></ul> </div> <script src="http://code.jquery.com/jquery-latest.js"></script> <script src="http://twitter.github.com/bootstrap/assets/js/bootstrap.js"></script> <script src="./chat_client.js"></script> </body> </html> |
CSSフレームワークは便利ですねって言うくらいで、特段、取り上げることもないのでソースコードの解説は省略します。
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | // WebSocketサーバに接続 var ws = new WebSocket('ws://172.16.145.136:8888/'); // エラー処理 ws.onerror = function(e){ $('#chat-area').empty() .addClass('alert alert-error') .append('<button type="button" class="close" data-dismiss="alert">×</button>', $('<i/>').addClass('icon-warning-sign'), 'サーバに接続できませんでした。' ); } // ユーザ名をランダムに生成 var userName = 'ゲスト' + Math.floor(Math.random() * 100); // チャットボックスの前にユーザ名を表示 $('#user-name').append(userName); // WebSocketサーバ接続イベント ws.onopen = function() { $('#textbox').focus(); // 入室情報を文字列に変換して送信 ws.send(JSON.stringify({ type: 'join', user: userName })); }; // メッセージ受信イベントを処理 ws.onmessage = function(event) { // 受信したメッセージを復元 var data = JSON.parse(event.data); var item = $('<li/>').append( $('<div/>').append( $('<i/>').addClass('icon-user'), $('<small/>').addClass('meta chat-time').append(data.time)) ); // pushされたメッセージを解釈し、要素を生成する if (data.type === 'join') { item.addClass('alert alert-info') .prepend('<button type="button" class="close" data-dismiss="alert">×</button>') .children('div').children('i').after(data.user + 'が入室しました'); } else if (data.type === 'chat') { item.addClass('well well-small') .append($('<div/>').text(data.text)) .children('div').children('i').after(data.user); } else if (data.type === 'defect') { item.addClass('alert') .prepend('<button type="button" class="close" data-dismiss="alert">×</button>') .children('div').children('i').after(data.user + 'が退室しました'); } else { item.addClass('alert alert-error') .children('div').children('i').removeClass('icon-user').addClass('icon-warning-sign') .after('不正なメッセージを受信しました'); } $('#chat-history').prepend(item).hide().fadeIn(500); }; // 発言イベント textbox.onkeydown = function(event) { // エンターキーを押したとき if (event.keyCode === 13 && textbox.value.length > 0) { ws.send(JSON.stringify({ type: 'chat', user: userName, text: textbox.value })); textbox.value = ''; } }; // ブラウザ終了イベント window.onbeforeunload = function () { ws.send(JSON.stringify({ type: 'defect', user: userName, })); }; |
うーん。あまりキレイなソースコードでない。jQueryのスマートなコーディングスタイルを学びたいです。よければ、俺ならこう書くみたいの教えてください。
Fork me!! mawatari / WebSocket-Chat – GitHub
今回はとりあえず動かすということを目標としていたので、その辺はご容赦していただくとして、コードを見ていきます。
1 2 | // WebSocketサーバに接続 var ws = new WebSocket('ws://172.16.145.136:8888/'); |
2行目。
WebSocketのインスタンスを生成しています。
4 5 6 7 8 9 10 11 12 | // エラー処理 ws.onerror = function(e){ $('#chat-area').empty() .addClass('alert alert-error') .append('<button type="button" class="close" data-dismiss="alert">×</button>', $('<i/>').addClass('icon-warning-sign'), 'サーバに接続できませんでした。' ); } |
4〜12行目。
エラー処理をしています。ここの理解がまだあまりできていません。WebSocketサーバが起動していない場合、Firefoxではこのエラーをキャッチするんですが、Safari、Chromeではダメでした。サーバが起動していない場合のエラーはまた別なのか、単にブラウザ側の実装がまだなのか、それともまた別の事情か、どうなんでしょう。
19 20 21 22 23 24 25 26 27 | // WebSocketサーバ接続イベント ws.onopen = function() { $('#textbox').focus(); // 入室情報を文字列に変換して送信 ws.send(JSON.stringify({ type: 'join', user: userName })); }; |
19〜27行目。
WebSocketサーバへの接続が確立された際のイベントを記述しています。今回は、入室イベントであるということと、ユーザ名をJSONで送っています。
61 62 63 64 65 66 67 68 69 70 71 72 | // 発言イベント textbox.onkeydown = function(event) { // エンターキーを押したとき if (event.keyCode === 13 && textbox.value.length > 0) { ws.send(JSON.stringify({ type: 'chat', user: userName, text: textbox.value })); textbox.value = ''; } }; |
61〜72行目。
発言イベントを処理しています。textboxに文字が入力され、エンターキーが押された場合を発言としています。チャットイベントであるということと、ユーザ名、チャット内容を送っています。
74 75 76 77 78 79 80 | // ブラウザ終了イベント window.onbeforeunload = function () { ws.send(JSON.stringify({ type: 'defect', user: userName, })); }; |
74〜80行目。
ブラウザ終了イベントを処理しています。終了イベントであるということと、ユーザ名を送っています。iOSではイベントを補足できませんでした。
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | // メッセージ受信イベントを処理 ws.onmessage = function(event) { // 受信したメッセージを復元 var data = JSON.parse(event.data); var item = $('<li/>').append( $('<div/>').append( $('<i/>').addClass('icon-user'), $('<small/>').addClass('meta chat-time').append(data.time)) ); // pushされたメッセージを解釈し、要素を生成する if (data.type === 'join') { item.addClass('alert alert-info') .prepend('<button type="button" class="close" data-dismiss="alert">×</button>') .children('div').children('i').after(data.user + 'が入室しました'); } else if (data.type === 'chat') { item.addClass('well well-small') .append($('<div/>').text(data.text)) .children('div').children('i').after(data.user); } else if (data.type === 'defect') { item.addClass('alert') .prepend('<button type="button" class="close" data-dismiss="alert">×</button>') .children('div').children('i').after(data.user + 'が退室しました'); } else { item.addClass('alert alert-error') .children('div').children('i').removeClass('icon-user').addClass('icon-warning-sign') .after('不正なメッセージを受信しました'); } $('#chat-history').prepend(item).hide().fadeIn(500); }; |
29〜58行目。
サーバからのpushメッセージ受信イベントを処理しています。リスト要素を生成し、イベントタイプによって出力内容をかえるようにしています。
クライアントの実装は以上です。
WebSocket Chat サーバの起動とアプリの実行
VMware上のCentOS6での実行を例に見ていきます。今回は、 chat_server.jsをユーザのホームディレクトリに設置しました。起動の例は以下の通りです。
Nodeおよび、WebSocket.IOがインストールされていて起動している前提ですので、そちらが済んでない方は、以下より設定してください。
参考リンク:nvmとNode.jsとWebSocket.IOをインストールしてWebSocketアプリの環境を作る
1 2 3 4 5 6 7 8 | # ~/ に配置したchat_server.jsを実行 cd ~/ node chat_server.js Server running at 172.16.145.136:8888 # メッセージを受け取ると以下のような感じでコンソールに表示されます。 {"type":"join","user":"ゲスト82","time":"2012-10-19 18:2:41"} {"type":"chat","user":"ゲスト82","text":"こんにちは。誰かいますか?","time":"2012-10-19 18:3:14"} |
ApacheのDocumentRoot以下に chat.htmlと chat_client.jsを設置し、 chat.htmlにアクセスすると、以下の画面が表示されるはずです。
ブラウザを2つ立ち上げて確認するもよし、2台のPCからアクセスするもよし、スマートフォンやタブレットでアクセスするもよし、WebSocketのリアルタイム性をぜひ体感してみてください。
【PHP】下らねぇ質問はID出して書き込みやがれ 128 501-550 | PHPの話題
2013年7月10日 @ 1:53 AM
[…] Node.jsとWebSocket.IOでチャットアプリを作る https://mawatari.jp/archives/make-a-chat-application-in-node-js-and-websocket-io […]