PublickeyさんのNode.jsの記事を自分なりにまとめる。
(覚える為なのでほぼ同じ事を書いてる・・・。)
Node.jsとPHPとの本質的な違いとは何か
・Node.jsの作者:ライアン・ダール(Ryan Dahl)さん
・Node.jsはChromeにも使われている「V8」javascriptエンジンの上に構築されている。
・V8の上に多くのライブラリを載せたものがNode.js。
・一般的な処理もできるがネットワーク処理を得意にしている。
・Node.jsで足し算する例。add関数を定義して実行する。
$ node > 1+2 3 > function add(a,b) { return a + b } > add(1,2) 3 >
・Nodeにはグローバル変数がある。Nodeはウィンドウオブジェクトではなくプロセスがグローバル変数になっている。
・非同期の例(hello-world.jsの例)
setTimeout(function(){ console.log("world"); }, 2000) console.log("hello");
実行結果
$ node hello-world.js hello world
2秒待ってから”world”と表示された。
同じようなものをPHPでつくる。
echo("hello"); sleep(2); echo("world");
Node.jsとPHPのサンプルプログラムの本質的な違い
⇒PHPはsleepし、Nodeはtimeoutしている。PHPはsleepで止まってしまう。
Nodeは決して止まらないしスリープしない。Mutex(排他的)なロックも、実行の停止
(Halting Execution)もない。アイドルになるだけだ。
この“hello”と“world”の2秒間のtimeoutはビジーループを回しているのではなく、アイドルになっている。
CPU利用率はゼロになり、OSはほかの仕事をして、timeoutの時間がくるとプログラムの実行へと戻ってくる。
先程の例をsetIntervalにすると2秒毎に”world”が連続で表示される。
Nodeの大事なところ
これはコールバックがネットワークコネクションなりインターバルタイマーなりの呼び出しをずっと待っていることができる、ということなのだ。
並列処理を実際に試してみる
Webサーバの例
var http = require('http'); var s = http.createServer(function(req, res) { res.writeHead(200, {'content-type': 'text/plain'}); res.end("hello world\n"); }); s.listen(8000);
リクエスト結果
$ curl -i http://localhost:8000/ HTTP/1.1 200 OK content-type: text/plain Connection: keep-alive Transfer-Encoding: chunked hello world
Connectionが「keep-alive」になっている
⇒パーシステントなコネクションができるようになっているから
Transfer-Encodingが「chunked」になっている
⇒ストリーミング
“world”が2秒後にでるように変更
var http = require('http'); var s = http.createServer(function(req, res) { res.writeHead(200, {'content-type': 'text/plain'}); res.write("hello\n"); setTimeout(function() { res.end("world\n"); }, 2000); }); s.listen(8000);
実行結果
$ curl -i http://localhost:8000/ HTTP/1.1 200 OK content-type: text/plain Connection: keep-alive Transfer-Encoding: chunked hello world
変わらない!
1つのリクエストに対して1つのボディとして返している。
⇒Transfer-Encodingが「chunked」になっているということ!
Nodeでは“hello”から“world”のあいだの2秒間もサーバは立ち上がっていて、スリープしているのではなくアイドルになっていて、ほかのコネクションを扱うことができるようになっている、ということだ。
Apache Bench(abコマンド)で、いまのサーバに対して100回のリクエストを送ってみよう。もしも頭の悪いサーバならば、2秒後に1つ目のリクエストが終わり、4秒後に2つ目のリクエストが終わり、6秒後に3つ目のリクエストが終わる。もしもsleepで2秒待つようになっていればそういうことだ。
しかし、このサーバはアイドル状態になってほかのコネクションを扱うことができる。実際にやってみよう。100のリクエストを並列性100で実行してみる。
↓このサーバで実際にやった結果
$ ab -n 100 -c 100 http://www.mogumagu.com:8000/ Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Copyright 2006 The Apache Software Foundation, http://www.apache.org/ Benchmarking www.mogumagu.com (be patient).....done Server Software: Server Hostname: www.mogumagu.com Server Port: 8000 Document Path: / Document Length: 12 bytes Concurrency Level: 100 Time taken for tests: 2.29498 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Total transferred: 7600 bytes HTML transferred: 1200 bytes Requests per second: 49.27 [#/sec] (mean) Time per request: 2029.498 [ms] (mean) Time per request: 20.295 [ms] (mean, across all concurrent requests) Transfer rate: 3.45 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 1.5 2 4 Processing: 2006 2015 5.0 2016 2026 Waiting: 5 14 5.0 15 24 Total: 2010 2017 3.7 2018 2026 Percentage of the requests served within a certain time (ms) 50% 2018 66% 2019 75% 2021 80% 2022 90% 2022 95% 2023 98% 2023 99% 2026 100% 2026 (longest request)
Time taken for tests: 2.29498 secondsだって!
Nodeは並列性をうまく扱うことができる、決してHaltもsleepもせず、つねにコネクションをうまくハンドルできることができる
並列処理を実際に試してみる
tcp-server.jsを作る。
var net = require('net'); var server = net.createServer( function(socket){ socket.write('hello\n'); socket.end('world\n'); } ); server.listen(8000);
実行結果
$ telnet localhost 8000 Trying 127.0.0.1... Connected to localhost.localdomain (127.0.0.1). Escape character is '^]'. hello world Connection closed by foreign host.
次はエコーサーバ。サーバはコネクションを終了しない。
var net = require('net'); var server = net.createServer( function(socket){ socket.write('hello\n'); socket.write('world\n'); socket.on('data', function(data){ socket.write(data); }); } ); server.listen(8000);
実行結果。ちゃんと返ってくる。
$ telnet localhost 8000 Trying 127.0.0.1... Connected to localhost.localdomain (127.0.0.1). Escape character is '^]'. hello world aaaaaaaaaaaaa aaaaaaaaaaaaa bbbbbbbbbbbbbb bbbbbbbbbbbbbb cccccccccccccc cccccccccccccc
次はチャットサーバ。
socketでサーバを作成して8000番ポートにバインドしてlisten。
コネクションが来たら全て保持する必要があるので配列に。
ソケットからの入力をsocket.onで受け、それを全てのソケットに対してループで回して出力。
net = require('net'); var sockets = []; var s = net.Server(function(socket){ sockets.push(socket); socket.on('data', function(d){ for(var i = 0; i < sockets.length; i++) { sockets[i].write(d); } }); socket.on('end', function() { var i = sockets.indexOf(socket); socket.splice(i, 1); }); }); s.listen(8000);
実行結果。途中から別サーバからも接続して適当に入力してる。
$ telnet localhost 8000 Trying 127.0.0.1... Connected to localhost.localdomain (127.0.0.1). Escape character is '^]'. aaa aaa aaa aaa bbb bbb sss asdfa asdf asdfa asdfa eeeeeeeeeee
後はNode.jsのデバッグ機能の話とか。
とりあえずここまで。