Node.jsとは何かを自分で実際に試す。


Node.jsとは何かを自分で実際に試す。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のデバッグ機能の話とか。

とりあえずここまで。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です