More Related Content
Similar to Node.js入門 (20)
Node.js入門
- 1. Node.js 入門
2011 年 4 月 16 日
森 俊夫 @ 徳島
forest1040@gmail.com
http://d.hatena.ne.jp/forest1040/
1
- 2. 自己紹介
id:forest1040 です。
徳島で、フリーランスをやってます。
Web 系エンジニアです。最近は、 Java EE(JBoss
Seam) と Ruby をよく使っています。
1年前に息子が生まれ、イクメン中です。
Node.js on Android をやってます。
2
- 3. アジェンダ
基礎編
Node.js とは
非同期 I/O とイベントループ
Node.js のアーキテクチャ
実践編
インストール
デバッグ環境
Node.js を使ったリアルタイム通信
3
- 4. Node.js とは
サーバサイド JavaScript
Google の V8 エンジン搭載
シングルスレッド非同期 I/O 環境
イベントループモデル
4
- 6. 頭の体操(並行処理)
突然ですが、クイズです。
シングルスレッドで並行処理を行うには?
マルチスレッドとシングルスレッドの
並行処理の違いは?
6
- 7. 並行処理
マルチスレッド( multithread )による並行処理
呼び元と並行に処理が行われる。
シングルスレッドでのコールバック( callback )によ
る並行処理
呼び元がプロセッサを使用していないときに処理
が行われる。
7
- 9. 非同期とノンブロッキング
実は、非同期( Asynchronous )とノンブロッキング
( nonblocking )は同じ意味としてよく使われて
いる。
日本語に惑わされると負け。
9
- 10. 非同期(ノンブロッキング)とは
非同期呼び出し( Asynchronous Call )と同期呼び
出し( Synchronous Call )の違い
同期呼び出し( Synchronous Call )
通常メソッドを呼び出すとメソッド内の処理が完了
するまで、呼び出し元には戻ってこない。このよう
なメソッド呼び出しのこと。
非同期呼び出し( Asynchronous Call )
メソッドを呼び出した瞬間に呼び出し元に処理が
戻ってくるような呼び出しのこと。非同期で呼び
出されたメソッドは、環境によって処理されるタイ
ミングが変わる。 まさしくノンブロッキング
10
- 11. ノンブロッキング I/O とは
ブロッキング I/O ( blocking I/O )とノンブロッキン
グ I/O ( nonblocking I/O )の違い
ブロッキング I/O ( blocking I/O )
データ処理が完了するまで待たされること。
ノンブロッキング I/O ( nonblocking I/O )
データ処理の完了を待たされずに、他の処理を行
えること。
11
- 12. I/O モデル
W. Richard Stevens の
「 UNIX ネットワークプログラミング 第 2 版 Vol.1 」
Synchronous I/O Operation ( 同期 I/O 操作 )
blocking I/O ( ブロッキング I/O)
nonblocking I/O ( 非ブロッキング I/O)
I/O multiplexing (I/O 多重化 )
signal driven I/O ( シグナル駆動 I/O)
Asynchronous I/O Operation ( 非同期 I/O 操作 )
Asynchronous I/O ( 非同期 I/O)
12
- 13. Boost application performance using
asynchronous I/O
引用: Boost application performance using asynchronous I/O
http://www128.ibm.com/developerworks/linux/library/lasync/
13
- 14. Boost application performance using
asynchronous I/O
この図では、 I/O multiplexing が
Asynchronous に分類されている。
引用: Boost application performance using asynchronous I/O
http://www128.ibm.com/developerworks/linux/library/lasync/
14
- 15. C10K 問題
ハードウェアの性能上は問題がなくても、あまりにもクライアントの数
が多くなるとサーバがパンクする問題のこと。
◇ 解決方法
1. 各スレッドが複数のクライアントを受け付ける。 そしてノンブロッキン
グ I/O と レベル・トリガ型の完了通知 (leveltriggered readiness
notification) を利用する。
2. 各スレッドが複数のクライアントを受け付ける。 そしてノンブロッキン
グ I/O と 変更型の完了通知 (readiness change notification) を利
用する。
3. 各スレッドが複数のクライアントを受けつける。 そして非同期 I/O を
使う。
4. 各スレッドが一つのクライアントを受けつける。 そしてブロッキング
I/O を使う。
5. サーバのコードをカーネルに組込む。
15
- 16. マルチスレッド vs ノンブロッキング
apache マルチスレッドモデル (C10K の解決案 No.4)
nginx ノンブロッキング( C10K の解決案 No.1 )
1秒あたりの処理リクエスト数 メモリ使用量
nginx は、最高で秒間 10,000 リクエストを処理する。同時接続数を
増やしても、リクエスト処理数は少々減る程度
apache の場合、同時接続数を増やすと著しくリクエスト処理数が減
る。しかも、メモリ使用量が同時接続数に比例して増える。
つまり、同時接続数が多い場合は、ノンブロッキングが有利!!
参照: http://blog.webfaction.com/alittleholidaypresent
16
- 17. Node.js の場合
「 3. 各スレッドが複数のクライアントを受けつける。 そして
非同期 I/O を使う」を採用
「 libev 」と「 libeio 」を使用して、非同期 I/O 環境を実装
libev
C 言語で書かれたイベントループライブラリ
イベントループとは、無限ループを行いながら、 I/O を監視し、利用可能や I/O 完
了等のイベントが発生するとコールバック(または、シグナル)により通知。
I/O の監視には、 I/O multiplexing モデルを使用し、環境によって最適なシステ
ムコール( Linux であれば、 epoll 、 FreeBSD では、 kqueue )を使用。
libeio
C 言語で書かれた非同期 I/O ライブラリ
実装的には、キューとスレッドプールを使い、 I/O を非同期並行処理します。
17
- 18. Node.js のアーキテクチャ
Java Script C / C++
Node Standard Library
Node Bindings
( socket, http, etc )
thread pool event loop DNS
V8 http parser
(libeio) (libev) (c-ares)
18
- 19. libev と epoll の比較
libev と epoll を使って、 echo サーバを実装し、比較
してみましょう。
19
- 20. epoll で echo サーバ
// メイン関数 // クライアントからのイベントを処理する
int main() { void event_client (int epfd, int client, struct epoll_event ev) {
int listener, epfd; char buffer[1024];
struct epoll_event ev; int n = read(client, buffer, sizeof buffer);
struct epoll_event events[MAX_EVENTS]; if (n < 0) {
perror("read");
// サーバ起動 epoll_ctl(epfd, EPOLL_CTL_DEL, client, &ev);
listener = setup_socket(); close(client);
} else if (n == 0) {
// epollの初期化 epoll_ctl(epfd, EPOLL_CTL_DEL, client, &ev);
if ((epfd = epoll_create (MAX_EVENTS)) < 0) { close(client);
die("epoll_create"); } else {
} write(client, buffer, n);
memset(&ev, 0, sizeof ev); }
ev.events = EPOLLIN; }
ev.data.fd = listener;
epoll_ctl(epfd, EPOLL_CTL_ADD, listener, &ev); // サーバへの接続要求イベントを処理する
void event_server (int epfd, int listener, struct epoll_event ev) {
// 無限ループ struct sockaddr_in client_addr;
while (1) { socklen_t client_addr_len = sizeof client_addr;
int i;
// 接続があるまで待つ int client = accept(listener, (struct sockaddr *) &client_addr,
int nfd = epoll_wait(epfd, events, MAX_EVENTS, -1); &client_addr_len);
// 接続されているクライアント数分、処理を行う if (client < 0) {
for (i = 0; i < nfd; i++) { die("accept");
// 新規接続の場合 }
if (events[i].data.fd == listener) { setnonblocking(client);
event_server(epfd, listener, ev); memset(&ev, 0, sizeof ev);
} else { ev.events = EPOLLIN | EPOLLET;
event_client(epfd, events[i].data.fd, ev); ev.data.fd = client;
} epoll_ctl(epfd, EPOLL_CTL_ADD, client, &ev);
} }
}
return 0;
}
epoll() を使う場合、自前で無限ループをつくり、その中で、 epoll_wait() を呼び出し、クライアント
からの接続を待つ必要があります。 epoll_wait() の最後のパラメータにマイナスの値を設定する
と、タイムアウトせずにひたすら待ちます。
20
- 21. libev で echo サーバ
// メイン関数 // クライアントからのイベントを処理する
int main() { void event_client (EV_P_ struct ev_io *w, int revents) {
struct ev_loop *loop; char buf[RCVBUFSIZE + 1];
ev_io watcher; size_t n = recv(w->fd, buf, RCVBUFSIZE, 0);
if (n < 0) {
// サーバ起動 perror("recv");
int listener = setup_socket(); }
if (n <= 0) {
// イベントループの初期化 close(w->fd);
loop = ev_default_loop (0); ev_io_stop(EV_A_ w);
watcher.data = loop; free(w);
} else {
// ev_ioの初期化と開始(サーバへの接続要求を監視) buf[n] = '0';
ev_io_init(&watcher, event_server, listener, EV_READ); send(w->fd, buf, n, 0);
ev_io_start (loop, &watcher); }
}
// イベントループ開始
ev_loop(loop, 0); // サーバへの接続要求イベントを処理する
close(listener); void event_server (EV_P_ struct ev_io *w, int revents) {
return 0; struct sockaddr_in client_addr;
} socklen_t client_addr_len = sizeof(client_addr);
struct ev_loop *l;
ev_io *client_watcher;
int client = accept(w->fd, (struct sockaddr *) &client_addr,
&client_addr_len);
if (client < 0) {
if (EINTR == errno) { return; }
die("accept");
}
setnonblocking(client);
client_watcher = calloc(1, sizeof(ev_io));
l = w->data;
// ev_ioの初期化と開始(クライアントのイベントを監視)
ev_io_init(client_watcher, event_client, client, EV_READ);
ev_io_start (l, client_watcher);
}
libev を使用する場合は、無限ループを作成する必要がありません。 ev_loop() で、イベント
ループとよばれる、イベントを監視するループが実行されます。イベントループに関数を設定
して、イベントを監視します。
21
- 22. int main (void) {
libeio のサンプル // EIOスレッドで実行される
printf ("pipe ()n"); void want_poll (void) {
if (pipe (respipe)) abort (); char dummy;
printf ("want_poll ()n");
printf ("eio_init ()n"); write (respipe [1], &dummy, 1);
// libeioにwant_poll()とdone_poll()を登録。 }
// libeioがpollして欲しいときに、このwant_poll()が呼び出される。
if (eio_init (want_poll, done_poll)) abort (); // EIOスレッドで実行される
do { void done_poll (void) {
// eio_open()呼び出し後、メインループを実行する。 char dummy;
eio_open ("eio-test-file", O_RDWR | O_CREAT, 0777, 0, open_cb, "open"); printf ("done_poll ()n");
event_loop (); read (respipe [0], &dummy, 1);
}
// eio_write()呼び出し後、メインループを実行する。
eio_write (last_fd, "aaaaaaaaaa", 10, 0, 0, res_cb, "write"); // mainスレッドのイベントループ
event_loop (); void event_loop (void) {
struct pollfd pfd;
// eio_read()呼び出し後、メインループを実行する。 pfd.fd = respipe [0];
eio_read (last_fd, 0, 8, 0, EIO_PRI_DEFAULT, read_cb, "read"); pfd.events = POLLIN;
event_loop ();
printf ("nentering event loopn");
// eio_close()呼び出し後、メインループを実行する。 while (eio_nreqs ()) {
eio_close (last_fd, 0, res_cb, "close"); // イベントを待つ
event_loop (); poll (&pfd, 1, -1);
} while (0); // mainスレッドでeio_poll()を実行する。
return 0; printf ("eio_poll () = %dn", eio_poll ());
} }
printf ("leaving event loopn");
}
eio-test-file を作成モードで開き、 write(2) と read(2) を実行
し、 close(2) します。 // mainスレッドで実行される
int res_cb (eio_req *req) {
libeio は、スレッドプールにより並行処理します。 main スレッドと EIO printf ("res_cb(%d|%s) = %dn", req->type,
スレッドで通信を行うために、 eio_init() で、 libeio が poll 通知できる req->data ? req->data : "?", EIO_RESULT (req));
if (req->result < 0) perror(req->errorno);
ようにします。 return 0;
main スレッドから、 eio_open() 等の eio の I/O 関数が呼ばれると、 }
キューにたまります。 // mainスレッドで実行される
EIO スレッドは、キューから取り出して、システムコールを実行し、結 int read_cb (eio_req *req) {
果をキューに返して、 eio_poll() を呼び出すよう main スレッドに通知 unsigned char *buf = (unsigned char *)EIO_BUF (req);
printf ("read_cb = %d (%s)n",
します。 EIO_RESULT (req),
main スレッドが eio_poll() を呼び出すと、事前に設定していた関数 buf);
return 0;
がコールバックで呼び出されます。コールバックは、 main スレッドで }
行われます。
22
- 23. Node.js の処理フロー
以下のような JavaScript コードを Node.js で実行し
た場合の処理フローを説明します。
var path = require('path'),
fs = require('fs'),
filepath = path.join(__dirname, 'a.txt'),
fd = fs.openSync(filepath, 'r');
fs.read(fd, 1024, 0, 'utf-8', function(err, str,
bytesRead) {
console.log(str);
});
カレントディレクトリから、 a.txt を読み込みコンソー
ルに表示するプログラムです。
23
- 25. Node.js の処理フロー
0. Node.js 起動時に eio_init() で node::EIOWantPoll() を libeio に登録する。
libeio は、 poll して欲しいタイミングになると、 node::EIOWantPoll() を呼び出すようになる。
1. node_file.cc の Read() が実行される。
2. libeio の eio_submit() が実行され、 req_queue に格納される。
req_queue には、 eio_req 構造体が保存される。
eio_read() の場合、 {TYPE=EIO_READ, FINISH=After(), DATA->cb=callback()} が設定され
る。 After() は、 node_file.cc の関数で、 libeio で eio_poll() が呼びだされた際に
main スレッド(イベントループ)で実行される。
After() の中で、 DATA->cb に設定された callback() が呼ばれる。 callback() には、
javascript の fs.read() の最後のパラメータとして
設定された関数が設定される。今回の場合は、
function(err, str, bytesRead) {
console.log(str);
}
3. EIO スレッドで、 eio_execute() が実行され、 read(2) が実行される。
5. read(2) の結果を、 req->result に設定し、 res_queue に格納する。
6. EIO スレッドで、 want_poll() が呼び出される。
7. 先程、 eio_init() で設定した、 node::EIOWantPoll() が呼び出され、 ev_async_send() により、
イベントループへ通知される。
8. イベントループで、 node::WantPollNotifier() が呼ばれ、 eio_poll() が実行される。
9. イベントループで先程の After() が実行され、 req->result の結果が読み出される。
10. 最後に callback() が実行される。
25
- 27. Node.js のインストール( nave )
nave とは
Node.js のバージョン管理ソフト( Ruby でいう rvm )
複数バージョンをひとつの環境で使える
インストール
1. Node.js をインストールするディレクトリを作成する。
$ mkdir nodejs
2. git コマンドにより、 github から nave を取得します。
$ git clone http://github.com/isaacs/nave.git
3. 展開された nave ディレクトリに移動し、「 nave.sh 」を実行します。
$ cd nave
$ ./nave.sh install latest
「 nave.sh 」の install コマンドに対して、「 latest 」オプションを指定しています。
「 latest 」とは、最新版を意味します。「 latest 」の代わりに
バージョン番号を指定することもできます。
4. 「 use 」コマンドを実行し、環境変数 PATH を設定します。
$ ./nave.sh use latest
「 latest 」を指定することにより、最新版のインストール先が PATH に設定されます。
「 install 」コマンドと同様にバージョン番号を指定して、
特定バージョンの Node.js を動作させることもできます。
5. バージョン番号が表示されれば、インストール成功です。
$ node -v
v0.4.2
27
- 28. nave のディレクトリ構成
.
# nave インストールディレクトリ
|-- README.md
|-- installed
# 各バージョンのバイナリインストールディレクトリ
| |-- 0.2.0
(略)
| |-- 0.4.2
| | |-- bin
| | | |-- node
(略)
| | | |-- npm -> ./npm@0.3.12
(略)
| | |-- include
| | | `-- node
| | | |-- config.h
| | | |-- eio.h
| | |-- lib
| | | |-- node
| | | | |-- npm -> ./npm@0.3.12
|-- src
# 各バージョンのソースコードディレクトリ
| |-- 0.2.0
(略)
| |-- 0.4.2
28
- 29. npm のインストール
npm とは
Node.js のパッケージ管理システム( Ruby でいう gem )
インストール
$ curl http://npmjs.org/install.sh | sh
「 nave.sh 」の「 use 」コマンドを実行した状態で、
npm をインストールすると Node.js と同じように
npm も nave により管理されるようになります。
29
- 30. npm 紹介
名前 概要
Socket.IO WebSocket の node.js 実装です。チャット等のリアルタイム通信を作成するのに
http://socket.io/ 使用します。イベントループが本領発揮する領域のパッケージです。
<インストール>
$ npm install socket.io
Express Rails ライクなフレームワークです。 MVC の自動生成、ルーティング機能、モデル
http://expressjs.com/ 機能等を持っています。
<インストール>
$ npm install express
EJS node.js のパッケージの中で、人気のテンプレートエンジンです。
http://embeddedjs.com/ <インストール>
$ npm install ejs
jsdom html に対して、 dom 操作が使えるようになるパッケージです。
https://github.com/tmpvar/jsdom <インストール>
$ npm install jsdom
node-validator バリデーションや文字列操作、サニタイズ処理を行うパッケージです。
<インストール>
https://github.com/chriso/node-validator
$ npm install validator
node-oauth oauth 認証を行うパッケージです。
<インストール>
https://github.com/ciaranj/node-oauth
$ npm install oauth
node-mysql データベースの MySQL へ接続するためのパッケージです。
<インストール>
https://github.com/felixge/node-mysql
$ npm install mysql
30
- 32. Node.js 開発環境
Node.js の開発環境を構築します。
nodedev
実行中のスクリプトが更新された際に自動的に再起動します。
スクリプトを修正した時に Node.js を再起動する手間が省けます。
nodeinspector
ブラウザ上の IDE で、 Node.js のデバッグができます。
※Webkit に依存するため、 Google Chrome 等の Webkit に対
応したブラウザが必要
32
- 33. nodedev
インストール
npm でインストールします。
$ npm install node-dev
使用例
node コマンドの代わりに、 nodedev コマンドを使用します。
console.log("hello world");
$ node-dev hello-world.js
9 Apr 14:06:39 - [INFO] Started
hello world hello-world.js を修正すると
自動的に最実行される。
9 Apr 14:06:48 - [INFO] Started
hello world2
33
- 34. nodeinspector
インストール
npm でインストールします。
$ npm install node-inspector
使用例
$ node-inspector
visit http://0.0.0.0:8080/debug?port=5858 to start debugging
まず node-inspector をコンソールから立ち上げます。
$ node-dev --debug [ スクリプトファイル ]
次に別のコンソールで、 node-dev (または、 node コマンド)に対して、
「 --debug 」オプションを指定して、 Node.js を実行します。
ブラウザで「 http://127.0.0.1:8080/debug?port=5858 」に接続します。
34
- 36. Cloud9 IDE
ブラウザ上の IDE です。
エディタ、シンタックスハイライト、デバッグ実行等ができます。
インストール
$ npm install cloud9
実行(実行したいディレクトリで)
$cloud9
36
- 39. Ajax vs Comet
Ajax
Ajax を使用して、サーバへポーリング ( 一定間隔でサーバをチェックする )
◇ デメリット
ポーリングの間隔分の遅延が発生する
データ変更があるなしに関わらずチェックを行うため、 CPU やメモリを必
要以上に使用してしまう
ポーリング間隔が短すぎればネットワーク帯域やリソースを消費しすぎる
Comet
HTTP を使った(無理やり)プッシュ通信技術。クライアントからのリクエス
トに対してすぐに応答せずに、サーバ上でイベントが発生したときにレス
ポンスを返す。
◇ デメリット
クライアントへ 2 倍のリソースが必要。
(ブラウザからサーバへの通常のリクエストは別 HTTP コネクショ
ンでやり取りするため)
ブラウザによって挙動が変わる場合も。。
39
- 40. WebSocket
そこで、 WebSocket
クライアントとサーバー間で双方向通信を実現す
るための仕組み。
接続の確立までは HTTP を使用し、その後は
WebSocket 独自のプロトコルに切り替える。
※ 但し、現在、仕様策定中。。
40
- 42. チープチャット Ajax Poll 版
HTML
<html>
<head>
<title>Ajax Poll Chat</title>
<script type="text/javascript" src="http://localhost/js/jquery-1.4.4.min.js"></script>
<script type="text/javascript" src="http://localhost/js/jquery-ui-1.8.2.custom.min.js"></script>
<script type="text/javascript" src="http://localhost/js/poll.js"></script>
</head>
<body>
<h1>Ajax Poll Chat</h1>
<!-- ログインフォーム -->
<form id="login_form">
<span>Enter username:</span>
<input id="user_name" type="text" size="10" value="" />
<input id="login_button" type="button" value="Login" />
</form>
<!-- メッセージ書き込みフォーム -->
<form id="write_form">
<span id="write_username"></span>
<input id="write_message" type="text" size="40" value="" />
<input id="write_button" type="button" value="Write" />
</form>
<!-- ログインアウトフォーム -->
<form id="logout_form">
<input id="logout_button" type="button" value="Logout" />
</form>
<h2>Chat Log</h2>
<span id ="result"></span>
</body>
</html>
42
- 43. サーバ側 JavaScript
var http = require('http'),
fs = require('fs'),
url = require('url'),
querystring = require('querystring');
// メッセージ
var messages = [];
// http サーバの作成
http.createServer(function(req, res) {
// index.html を表示
if (req.url == "/") {
fs.readFile(__dirname + '/index.html', function(err, content) {
res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
res.end(content);
});
// メッセージを返す
} else if (req.url == "/read_message.json") {
res.writeHead(200, {'Content-Type':'application/json; charset=utf-8'});
res.end(JSON.stringify(messages));
// メッセージの書き込み
} else {
// URL パラメータの取得
var param = querystring.parse(url.parse(req.url).query);
// メッセージの保存
messages.push(param);
console.log(param);
// メッセージを返す
res.writeHead(200, {'Content-Type':'application/json; charset=utf-8'});
res.end(JSON.stringify(messages));
}
}).listen(8192, '127.0.0.1');
console.log('http://127.0.0.1:8192/');
43
- 44. クライアント側 JavaScript
// インターバル // ログインボタン
var watch = null; function set_login_button() {
$("#login_button").click(function(){
// 初期化処理 read();
function init() { watch = setInterval(read, 5000);
$("#write_form").hide(); $("#write_form").show();
$("#logout_form").hide(); $("#logout_form").show();
$("#user_name").val(""); });
} }
// メッセージ出力 // ログアウトボタン
function show(data) { function set_logout_button() {
var result = ""; $("#logout_button").click(function(){
$.each(data, function() { clearInterval(watch);
if (this.write_message != null) { init();
result += this.user_name + ":" + this.write_message + "<br />"; });
} }
});
$("#result").html(result); $(document).ready(function() {
} init();
set_login_button();
// メッセージの読み込み set_logout_button();
function read() { set_write_button();
$.getJSON("read_message.json", null, function(data, status){ });
show(data);
});
}
// メッセージ書き込み
function set_write_button() {
$("#write_button").click(function(){
var data = {};
data.user_name = $("#user_name").val();
data.write_message = $("#write_message").val();
$.getJSON("write_message.json", data, function(data, status){
read();
});
});
}
44
- 45. チープチャット Comet 版
サーバ側 JavaScript
var http = require('http'), // http サーバの作成
fs = require('fs'), http.createServer(function(req, res) {
url = require('url'), // index.html を表示
querystring = require('querystring'); if (req.url == "/") {
fs.readFile(__dirname + '/index.html', function(err, content) {
// メッセージ console.log("connections.length:" + connections.length);
var messages = []; res.writeHead(200, {'Content-Type':'text/html;
// コネクション charset=utf-8'});
var connections = []; res.end(content);
});
// 全クライアントに通知 // メッセージ取得
function notify() { } else if (req.url == "/read_message.json") {
if (connections.length) { // コネクションに入れて Long Poll する(レスポンスを返さない)
var c = null; connections.push(res);
while ((c = connections.shift()) != null) { console.log("connections.length:" + connections.length);
// メッセージを返す } else {
c.writeHead(200, // URL パラメータの取得
{'Content-Type':'application/json; var param = querystring.parse(url.parse(req.url).query);
charset=utf-8'}); // メッセージの保存
c.end(JSON.stringify(messages)); messages.push(param);
} console.log(param);
} console.log("connections.length:" + connections.length);
// 60 秒待って、全クライアントにメッセージを返す // 全クライアントに通知
setTimeout(notify, 60000); notify();
} // メッセージを返す
res.writeHead(200,
// 60 秒待って、全クライアントにメッセージを返す {'Content-Type':'application/json; charset=utf-8'});
setTimeout(notify, 60000); res.end(JSON.stringify(messages));
}
}).listen(8192, '127.0.0.1');
console.log('http://127.0.0.1:8192/');
45
- 46. クライアント側 JavaScript
// 初期化処理 // ログインボタン
function init() { function set_login_button() {
$("#write_form").hide(); $("#login_button").click(function(){
$("#logout_form").hide(); read();
$("#user_name").val(""); $("#write_form").show();
} $("#logout_form").show();
});
// メッセージ出力 }
function show(data) {
var result = ""; // ログアウトボタン
$.each(data, function() { function set_logout_button() {
if (this.write_message != null) { $("#logout_button").click(function(){
result += this.user_name + ":" + this.write_message + "<br />"; init();
} });
}); }
$("#result").html(result);
} $(document).ready(function() {
init();
// メッセージの読み込み set_login_button();
function read() { set_logout_button();
// Comet 通知用のコネクション set_write_button();
$.getJSON("read_message.json", null, function(data, status){ });
show(data);
// 再度コネクションをはる
read();
});
}
// メッセージ書き込み
function set_write_button() {
$("#write_button").click(function(){
var data = {};
data.user_name = $("#user_name").val();
data.write_message = $("#write_message").val();
$.getJSON("write_message.json", data, function(data, status){
show(data);
});
});
}
46
- 47. チープチャット WebSocket 版
var http = require('http'),
サーバ側 JavaScript
fs = require('fs'),
ws = require('websocket-server');
// WebSocket サーバの作成
var server = ws.createServer();
// 新規接続
server.addListener("connection", function(connection) {
console.log("connect");
// メッセージ受信
connection.addListener("message", function(message){
console.log(message);
// 全クライアントにメッセージを送る
server.broadcast(message);
});
});
// クローズ
server.addListener("close", function(connection) {
console.log("close");
});
// WebSocket サーバ待ち受け
server.listen(8000);
// http サーバの作成
http.createServer(function(req, res) {
// index.html を表示
fs.readFile(__dirname + '/index.html', function(err, content) {
res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
res.end(content);
});
}).listen(8192, '127.0.0.1');
console.log('http://127.0.0.1:8192/');
47
- 48. クライアント側 JavaScript
var ws = new WebSocket("ws://localhost:8000"); // ログインボタン
function set_login_button() {
// メッセージ受信時 $("#login_button").click(function(){
ws.onmessage = function(message) { $("#write_form").show();
var data = $.parseJSON(message.data); $("#logout_form").show();
show(data); });
} }
// 初期化処理 // ログアウトボタン
function init() { function set_logout_button() {
$("#write_form").hide(); $("#logout_button").click(function(){
$("#logout_form").hide(); init();
$("#user_name").val(""); });
} }
// メッセージ出力 $(document).ready(function() {
function show(data) { init();
var result = data.user_name + ":" + set_login_button();
data.write_message + "<br />"; set_logout_button();
$("#result").append(result); set_write_button();
} });
// メッセージ書き込み
function set_write_button() {
$("#write_button").click(function(){
var data = {};
data.user_name = $("#user_name").val();
data.write_message =
$("#write_message").val();
ws.send($.toJSON(data));
});
}
48
- 49. 参考資料
私のブログ「 shutdown h now 」 http://d.hatena.ne.jp/forest1040/
developerWorks 「 Boost application performance using asynchronous I/O 」
http://www.ibm.com/developerworks/linux/library/lasync/
「同期 I/O 」と「非同期 I/O 」の定義、とか http://d.hatena.ne.jp/hirose31/20070815
非同期 I/O 概説 http://www.slideshare.net/hirose31/aio
epoll を使った echo サーバ http://d.hatena.ne.jp/odz/20070507/1178558340
libev で echo サーバを作る http://d.hatena.ne.jp/winebarrel/20080309/p2
C10K 問題 http://www.hyuki.com/yukiwiki/wiki.cgi?TheC10kProblem
node.js のソースぐらい読んでおきたい! http://d.hatena.ne.jp/edvakf/20101207/1291556433
C++ で node.js ライブラリを作る・その 2 http://nodejs.g.hatena.ne.jp/edvakf/20101214/1292287495
IT Pro Comet プッシュ型の Web アプリケーションを作る
http://itpro.nikkeibp.co.jp/article/COLUMN/20080220/294242/
node.js と WebSocket の利用シーン http://bizria.jp/technical/nodejswebssocket.html
@IT Node.js でサーバサイド JavaScript 開発入門」
http://www.atmarkit.co.jp/fwcr/index/index_nodejs.html
49
- 50. ご清聴ありがとうございました。
Node.js は、 JavaScript を使って、比較的手軽
に非同期 I/O のシステムを実装できます。
◇ 本日のふりかえり
Node.js とは
非同期 I/O とイベントループ
Node.js のアーキテクチャ
インストール
デバッグ環境
Node.js を使ったリアルタイム通信
50