Node.js入門

42,024 views

Published on

Published in: Technology
1 Comment
107 Likes
Statistics
Notes
No Downloads
Views
Total views
42,024
On SlideShare
0
From Embeds
0
Number of Embeds
7,994
Actions
Shares
0
Downloads
267
Comments
1
Likes
107
Embeds 0
No embeds

No notes for slide

Node.js入門

  1. 1. Node.js 入門 2011 年 4 月 16 日 森 俊夫 @ 徳島 forest1040@gmail.com http://d.hatena.ne.jp/forest1040/    1
  2. 2. 自己紹介 id:forest1040  です。 徳島で、フリーランスをやってます。 Web 系エンジニアです。最近は、 Java EE(JBoss  Seam) と Ruby をよく使っています。 1年前に息子が生まれ、イクメン中です。 Node.js on Android をやってます。     2
  3. 3. アジェンダ 基礎編  Node.js とは  非同期 I/O とイベントループ  Node.js のアーキテクチャ 実践編  インストール  デバッグ環境  Node.js を使ったリアルタイム通信     3
  4. 4. Node.js とは サーバサイド JavaScript Google の V8 エンジン搭載 シングルスレッド非同期 I/O 環境 イベントループモデル     4
  5. 5. シングルスレッド非同期 I/O 環境 と イベントループ    5
  6. 6. 頭の体操(並行処理) 突然ですが、クイズです。 シングルスレッドで並行処理を行うには? マルチスレッドとシングルスレッドの 並行処理の違いは?     6
  7. 7. 並行処理 マルチスレッド( multithread )による並行処理 呼び元と並行に処理が行われる。 シングルスレッドでのコールバック( callback )によ る並行処理 呼び元がプロセッサを使用していないときに処理 が行われる。     7
  8. 8. 非同期と同期、ノンブロッキングとブロッキング さらにクイズです。 非同期とノンブロックの違いがわかります か?     8
  9. 9. 非同期とノンブロッキング 実は、非同期( Asynchronous )とノンブロッキング ( non­blocking )は同じ意味としてよく使われて いる。 日本語に惑わされると負け。     9
  10. 10. 非同期(ノンブロッキング)とは 非同期呼び出し( Asynchronous Call )と同期呼び 出し( Synchronous Call )の違い 同期呼び出し( Synchronous Call ) 通常メソッドを呼び出すとメソッド内の処理が完了 するまで、呼び出し元には戻ってこない。このよう なメソッド呼び出しのこと。 非同期呼び出し( Asynchronous Call ) メソッドを呼び出した瞬間に呼び出し元に処理が 戻ってくるような呼び出しのこと。非同期で呼び 出されたメソッドは、環境によって処理されるタイ ミングが変わる。 まさしくノンブロッキング     10
  11. 11. ノンブロッキング I/O とは ブロッキング I/O ( blocking I/O )とノンブロッキン グ I/O ( non­blocking I/O )の違い ブロッキング I/O ( blocking I/O ) データ処理が完了するまで待たされること。 ノンブロッキング I/O ( non­blocking I/O ) データ処理の完了を待たされずに、他の処理を行 えること。     11
  12. 12. I/O モデル W. Richard Stevens の 「 UNIX ネットワークプログラミング 第 2 版 Vol.1 」 Synchronous I/O Operation ( 同期 I/O 操作 )  blocking I/O ( ブロッキング I/O)  non­blocking 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. 13. Boost application performance using  asynchronous I/O引用: Boost application performance using asynchronous I/Ohttp://www­128.ibm.com/developerworks/linux/library/l­async/     13
  14. 14. Boost application performance using  asynchronous I/O この図では、 I/O multiplexing がAsynchronous に分類されている。引用: Boost application performance using asynchronous I/Ohttp://www­128.ibm.com/developerworks/linux/library/l­async/     14
  15. 15. C10K 問題 ハードウェアの性能上は問題がなくても、あまりにもクライアントの数 が多くなるとサーバがパンクする問題のこと。 ◇ 解決方法1. 各スレッドが複数のクライアントを受け付ける。 そしてノンブロッキン グ I/O  と レベル・トリガ型の完了通知 (level­triggered readiness  notification) を利用する。2. 各スレッドが複数のクライアントを受け付ける。 そしてノンブロッキン グ I/O  と 変更型の完了通知 (readiness change notification) を利 用する。3. 各スレッドが複数のクライアントを受けつける。 そして非同期 I/O  を 使う。4. 各スレッドが一つのクライアントを受けつける。 そしてブロッキング I/O  を使う。5. サーバのコードをカーネルに組込む。     15
  16. 16. マルチスレッド vs  ノンブロッキング apache  マルチスレッドモデル (C10K の解決案 No.4) nginx  ノンブロッキング( C10K の解決案 No.1 ) 1秒あたりの処理リクエスト数 メモリ使用量 nginx  は、最高で秒間 10,000 リクエストを処理する。同時接続数を 増やしても、リクエスト処理数は少々減る程度 apache の場合、同時接続数を増やすと著しくリクエスト処理数が減 る。しかも、メモリ使用量が同時接続数に比例して増える。 つまり、同時接続数が多い場合は、ノンブロッキングが有利!!参照: http://blog.webfaction.com/a­little­holiday­present     16
  17. 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. 18. Node.js のアーキテクチャ Java Script C / C++ Node Standard Library Node Bindings ( socket, http, etc ) thread pool event loop DNSV8 http parser (libeio) (libev) (c-ares)    18
  19. 19. libev と epoll の比較 libev と epoll を使って、 echo サーバを実装し、比較 してみましょう。     19
  20. 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. 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. 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. 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
  24. 24. Node.js の処理フロー    24
  25. 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
  26. 26. インストール と パッケージ管理    26
  27. 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. 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. 29. npm のインストール npm とは Node.js のパッケージ管理システム( Ruby でいう gem ) インストール $ curl http://npmjs.org/install.sh | sh  「 nave.sh 」の「 use 」コマンドを実行した状態で、   npm をインストールすると Node.js と同じように   npm も nave により管理されるようになります。     29
  30. 30. npm 紹介 名前 概要Socket.IO WebSocket の node.js 実装です。チャット等のリアルタイム通信を作成するのにhttp://socket.io/ 使用します。イベントループが本領発揮する領域のパッケージです。 <インストール> $ npm install socket.ioExpress Rails ライクなフレームワークです。 MVC の自動生成、ルーティング機能、モデルhttp://expressjs.com/ 機能等を持っています。 <インストール> $ npm install expressEJS node.js のパッケージの中で、人気のテンプレートエンジンです。http://embeddedjs.com/ <インストール> $ npm install ejsjsdom html に対して、 dom 操作が使えるようになるパッケージです。https://github.com/tmpvar/jsdom <インストール> $ npm install jsdomnode-validator バリデーションや文字列操作、サニタイズ処理を行うパッケージです。 <インストール>https://github.com/chriso/node-validator $ npm install validatornode-oauth oauth 認証を行うパッケージです。 <インストール>https://github.com/ciaranj/node-oauth $ npm install oauthnode-mysql データベースの MySQL へ接続するためのパッケージです。 <インストール>https://github.com/felixge/node-mysql $ npm install mysql     30
  31. 31. Node.js 開発環境    31
  32. 32. Node.js 開発環境 Node.js の開発環境を構築します。 node­dev 実行中のスクリプトが更新された際に自動的に再起動します。 スクリプトを修正した時に Node.js を再起動する手間が省けます。 node­inspector ブラウザ上の IDE で、 Node.js のデバッグができます。 ※Webkit に依存するため、 Google Chrome 等の Webkit に対 応したブラウザが必要     32
  33. 33. node­dev インストール npm でインストールします。 $ npm install node-dev 使用例 node コマンドの代わりに、 node­dev コマンドを使用します。 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. 34. node­inspector インストール 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
  35. 35. デバッグ画面    35
  36. 36. Cloud9 IDE ブラウザ上の IDE です。 エディタ、シンタックスハイライト、デバッグ実行等ができます。 インストール $ npm install cloud9 実行(実行したいディレクトリで) $cloud9     36
  37. 37. Cloud9 IDE  画面イメージ    37
  38. 38. Node.js (実践編) リアルタイム通信    38
  39. 39. Ajax vs Comet Ajax Ajax を使用して、サーバへポーリング ( 一定間隔でサーバをチェックする ) ◇ デメリット  ポーリングの間隔分の遅延が発生する  データ変更があるなしに関わらずチェックを行うため、 CPU やメモリを必 要以上に使用してしまう  ポーリング間隔が短すぎればネットワーク帯域やリソースを消費しすぎる Comet HTTP を使った(無理やり)プッシュ通信技術。クライアントからのリクエス トに対してすぐに応答せずに、サーバ上でイベントが発生したときにレス ポンスを返す。 ◇ デメリット  クライアントへ 2 倍のリソースが必要。 (ブラウザからサーバへの通常のリクエストは別 HTTP コネクショ ンでやり取りするため)  ブラウザによって挙動が変わる場合も。。     39
  40. 40. WebSocket そこで、 WebSocket クライアントとサーバー間で双方向通信を実現す るための仕組み。 接続の確立までは HTTP を使用し、その後は WebSocket 独自のプロトコルに切り替える。 ※ 但し、現在、仕様策定中。。     40
  41. 41. Node.js でそれぞれのサンプルを実装    41
  42. 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. 43. サーバ側 JavaScriptvar 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. 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. 45. チープチャット ­Comet 版 ­サーバ側 JavaScriptvar 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. 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. 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. 48. クライアント側 JavaScriptvar 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. 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/l­async/ 「同期 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/nodejs­webssocket.html @IT Node.js でサーバサイド JavaScript 開発入門」  http://www.atmarkit.co.jp/fwcr/index/index_nodejs.html     49
  50. 50. ご清聴ありがとうございました。 Node.js は、 JavaScript を使って、比較的手軽 に非同期 I/O のシステムを実装できます。 ◇ 本日のふりかえり Node.js とは 非同期 I/O とイベントループ Node.js のアーキテクチャ インストール デバッグ環境 Node.js を使ったリアルタイム通信     50

×