node.js 에 대한 좋은 블로그 글 소개.

ITWeb/스크랩 2012. 3. 16. 14:52
Node.js 에 대해 쉽게 접근 할 수 있도록 정리된 블로그 문서가 있어 공유 합니다.

[원본문서] 


그동안 봐야지 봐야지 하던 Node.js를 이제야 보기 시작했습니다. 


간단히 말하면 Node.js는 서버사이드에서 동작이 가능한 Javascript이지만 보통 서버사이드 자바스크립트라고 하면 생각할만한건 runat="server"같은 수준의 서버사이드 자바스크립트와는 차원자체가 다릅니다.또한 Jaxer나 Narwhar같은 서버스크립트 자바스크립트 환경도 존재하지만 Node.js는 약간 다릅니다. 원래는 nettues+의 Learning Server-Side JavaScript with Node.js 를 따라하면서 포스팅을 하려고 하였지만 Node.js를 만든 Ryan Dahl 가 JSConf 2009 에서 발표한 동영상을 보는 것이 훨씬 이해하기가 좋아서 Ryan Dahl의 발표영상을 링크합니다. 


Ryan Dahl: Node.js

동영상에 나오는 발표자료는 글자가 잘 보이지 않아서 Scribd에 올라와 있는 발료자료입니다. (JSConf정도 행사에 node.js같은 프로젝트를 들고 나오는 사람이 헤져서 글자도 잘 보이지 않는 나이키티셔츠를 걸치고 나와서 한다는 것이 재밌기도 하네요.)


Node.js JSConf 2009 
 

Node.js는 서버사이드 자바스크립트이며 Google의 자바스크립트 엔진인 V8이 빌트인되어 있습니다. Event 기반이며 non-blocking I/O를 지원합니다. 자바스크립트의 표준라이브러리 프로젝트인 CommonJS 의모듈시스템을 지원합니다.

Apache같은 웹서버들은 모든 요청마다 시스템 쓰레드를 생성하는 쓰레드 기반이고 대부분의 경우에는 잘 동작하지만 Friendfeed나 Google Wave같은 리얼타임 애플리케이션처럼 많은 long-lived커넥션을 사용하는 애클리케이션에는 적합하지 않고 확장(scale)하기가 어렵습니다. 위 프리젠테이션에서 Apache와 NGINX를 비교한 그래프를 보면 쓰레드기반인 Apache는 요청이 늘어날수록 메모리가 증가하지만 NGINX는 직선을 유지하는 것을 볼 수가 있습니다. 이는 NGINX는 Event Loop를 사용하기 때문입니다.

이런 쓰레드기반의 문제를 Event Loop를 이용하면 해결할 수 있습니다.(여기서 Event Loop라는 것은 동작을 요청한뒤에 콜백을 지정하여 동작이 완료되면 콜백이 실행되는 방식을 발합니다.) 그럼 왜 모두가 Event Loop를 사용하지 않는가하는 의문이 생깁니다. 




Ryan Dahl은 문화적인(Cultural) 이유와 구조적(infrastructural)인 이유 2가지가 있다고 생각하였습니다. 


문화적인 이유는 우리가 프로그래밍을 배울때 인풋을 요청하면 요청한 인풋을 받을때까지 아무것도 하지 말라고 배웠기 때문입니다. 
?
1
2
3
puts("Enter your name: ");
var name = gets();
puts("Name: " + name);

위 코드는 인풋을 받아서 출력하는 익숙한 코드입니다. 인풋이 들어올때까지 아무것도 행하지 않습니다.
?
1
2
3
4
5
puts("Enter your name: ");
gets(function(name) {
     
puts("Name: " + name);
});

이벤트 루프방식인 위와같은 코드는 복잡하다는 이유로 거부되어 왔습니다.

구조적인 이유는 싱글쓰레드이벤트 루프는 논블럭킹 I/O를 요구하지만 대부분의 라이브러리들은 그렇지 않았습니다. POSIX I/O나 DB라이브러리들은 비동기요청을 지원하지 않았습니다. 하지만 현재는 EventMachine, Twisted등 좋은 이벤트루프 플랫폼이 존재하고 있지만 사용자들은 어떻게 다른 라이브러리들과 이것들은 조합할수 있을지 혼란스러워 하고 있으며 서버사이드 개발자들은 이벤트 루프와 넌블락킹 I/O에 대한 지식이 필요합니다.

하지만 Javascript는 Event Loop를 사용하도록 디자인되어 비동기 펑션과 클로져를 지원하고 한번에 하나의 콜백만 실행하며 DOM 이벤트 콜백을 통한 I/O를 지원하고 있습니다. 더군다나 자바스크립트의 문화는 이미 이벤트기반의 프로그래밍에 익숙해져 있습니다.



Design Goal
  • Function은 직접 플랫폼 I/O에 접속하지 않습니다.
  • Disk, network, 프로세스를 통해 데이터를 받기 위해서는 반드시 콜백(callback)을 사용합니다.   콜백을 사용할 수도 있습니다. (함수명sync()라는 함수들이 제공되고 있다는 AJ님의 제보에 따라 수정하였습니다. 위 PT의 28페이지에 must be라는 표현으로 보아 보통의 경우 callback을 권장하는 것으로 추측(?)됩니다.)
  • TCP, DNS, HTTP같은 프로토콜을 지원합니다.
  • HTTP feature를 지원합니다.(Chunk된 requst/response, Keep-alive, Comet을 위한 리퀘스트 홀딩
  • 클라이언트 사이드 자바스크립트 프로그래밍과 과거 학생때 배운 Unix 스타일에 친숙하게 디자인되었습니다.



Internal Design
  • Google의 V8 사용
  • Marc Lehmann이 만든 이벤트루프 라이브러리 libev 사용
  • Marc Lehmann이 만든 쓰레드풀 라이브러리 libeio 사용
  • Ryan Dahl이 만든 HTTP파서 http-parser 사용
  • Ryan Dahl이 만든 libev에 기반한 스크림 소켓 라이브러리 evcom 사용
  • Michael Tokarev가 만든 넌블럭킹 DNS resolver udns 사용


더 자세한 내용은 AJ님의 core perspective to node.js 를 참고하시면 도움이 될 것입니다. 



이 포스팅은 node.js는 무엇인가? #2 : Hello World 실행하기로 이어집니다. 



이 포스팅은 node.js는 무엇인가? #1에 이어진 포스팅입니다.



Hello World에 대한 예제는 여러가지가 있지만 아무래도 node.js를 만든 Ryan Dahl가 보여준 예제가 node.js를 가장 잘 표현해 주는것 같아서 Ryan Dahl가 JSConf에 사용한 Hello World 예제를 그대로 사용하였습니다. nettuse+의 Learning Server-Side JavaScript with Node.js 에 나온 소스도 참고하시면 도움이 될 듯 합니다.



Node.js 설치하기
Node.js는 Mac OS X, Linux, FreeBSD같은 Unix기반의 시스템에서만 구동됩니다. (최근 Windows쪽에서도 할 수잇다느 포스팅을 본적이 있는것 같은데 지금은 찾을  수 없네요.) Node.js v0.6.x부터는 Windows에서도 동작합니다.

node.js를 다운로드 받습니다. node.js 공식사이트에서 최신 릴리즈버전을 다운로드 를 받을 수 있으며 현재 버전은 0.1.97입니다. 다운로드 받은 파일을 원하는 위치에 압축을 풀면 됩니다. Node.js는 GITHUB 에서 저장소를 사용하고 있기 때문에 GIT을 이용해서 git clone http://github.com/ry/node.git 명령어을 사용하면 현재 개발중인 최신소스를 받아서 테스트 해 볼 수 있습니다.

node.js가 소스가 있는 폴더로 이동하여 다음과 같은 명령어를 실행합니다.

./configure
make
sudo make install

설치할때 각 라이브러리를 체크하는데 단순히 현재 시스템에 지원여부를 체크하는 것이므로 not found라고 나와도 문제가 있는 것이 아니므로 신경쓰지 않아도 됩니다. (./configure를 하지 않으면 Project not configured 오류가 발생합니다.)

Node.js를 실행하기 위해서는 Node.js의 node명령어를 사용해야 하기 때문에 환경변수 path에 node.js가 설치된 경로(/Users/Outsider/node 같은)를 추가하면 아무데서나 node명령어를 사용할 수 있습니다.(우분투에서는 sudo gedit /etc/environment로 OSX는 sudo vi /etc/paths를 사용해서 PATH를 수정해 볼 수 있습니다. 수정된 PATH는 재로그인 후에 적용이 되며 env나 echo $PATH를 사용해서 확인할 수 있습니다.) V8은 내장되어 있고 별도의 의존성이 전혀 없기 때문에 설치하고 바로 사용할 수 있습니다.



Hello World 실행하기
아래의 예제들은 Node.js 0.1.96버전을 사용하였습니다. Ryan Dahl가 시연했던 코드와는 API가 변경된 내용이 많기 때문에 아래의 예제들은 0.1.96버전에서 동작하도록 수정하였습니다.
?
1
2
3
4
5
6
// helloworld1.js
var sys = require("sys")
setTimeout(function() {
    sys.puts("world");
}, 2000);
sys.puts("hello");

Hello World 예제입니다.

node helloworld1.js

위의 명령어를 실행하면 node.js가 실행됩니다. Node.js는 더이상 할일이 없으면 자동으로 종료됩니다.





Hello World 실행하기2
?
1
2
3
4
5
6
7
8
9
10
puts = require("sys").puts;
 
setInterval(function() {
    puts("hello");
}, 500);
 
process.addListener("SIGINT", function() {
    puts("good-bye");
    process.exit(0);
});



두번째 예제입니다. setIntervla을 이용해서 hello라는 메시지를 0.5초마다 계속 반복적으로 출력해 주고 종료명령인 Ctrl + C를 입력하면 good-bye라는 메시지를 출력한 후에 종료해줍니다. process는 V8 그 자체를 의미합니다. process에 SIGINT라는 이벤트리스너를 추가하여 해당시그널이 왔을때 이벤트가 발생하게 됩니다.



TCP 서버 예제
?
1
2
3
4
5
6
7
8
9
var tcp = require("net");
 
var s = tcp.createServer();
s.addListener("connection", function(c) {
    c.write("hello!");
    c.end();
});
 
s.listen(8000);

TCP 서버 예제입니다. 1번라인의 tcp는 현재 버전에서 net으로 변경되었으며 5번라인의 send()는 write()로, 6번라인의 close()는 end()로 변경되었기 때문에 현재 버전에 맞게 수정되었습니다.


위 화면에서 뒷쪽의 터미널에서 TCP서버를 실행시켜두고 터미널을 새로 띄워서 localhost에 TCP서버에 접속한 것입니다. 작성해 놓은대로 hello!를 출력한 뒤에 접속을 종료합니다. 9번 라인에서 TCP서버는 8000포트를 이용하도록 설정되어 있습니다. net객체는 접속이 들어올때마다 connection이벤트를 발생시키며 HTTP 업로드가 될때 각 패킷마다 body 이벤트가 호출됩니다.



아직 완전히 API가 Fix되지 않았기 때문에 버전에 따라서 변경되는 내용들이 있는 상황입니다. 일일이 다 테스트 해 본것은 아니지만 버전에 따라 변경되는 내용이 존재하고 있고 바로바로 문서에 반영되는 것은 아니기 때문에 상황에 따라서는 소스를 직접 열어보아야 하는 상황입니다만 위의 화면처럼 현재버전의 require("sys")대신 구버전에서 사용하던 require("tcp")를 사용할 경우 'tcp'가 'net'으로 변경되었다고 친절하게 알려줍니다.

connection리스너는 createServer의 첫번째 아규먼트로 사용할 수 있기 때문에 위의 코드는 아래처럼 변경할 수 있습니다.
?
1
2
3
4
5
6
var tcp = require("net");
 
 tcp.createServer(function(c) {
     c.write("hello!\n");
    c.end();
 }).listen(8000);




Simple HTTP 서버 예제
?
1
2
3
4
5
6
7
8
var http = require("http");
     
http.createServer(function(req, res) {
    res.writeHead(200, {"Content-Type": "text/plain"});
    res.write("Hello\r\n");
    res.write("World\r\n");
    res.end();
}).listen(8080);




웹브라우저에서 접속하면 서버에서 작성한대로 Hello world가 정상적으로 출력됩니다.



스트리밍 서버 예제
?
1
2
3
4
5
6
7
8
9
10
11
12
var http = require("http");
     
http.createServer(function(req, res) {
    res.writeHead(200, {"Content-Type": "text/plain"});
    res.write("Hel");
    res.write("lo\r\n");
     
    setTimeout(function() {
        res.write("World\r\n");
        res.end();
    }, 2000);
}).listen(8000);




웹브라우저에서는 스트림을 비동기로 받아지지 않아서 curl 을 사용해서 테스트하였습니다. 접속하면 Hello가 바로 찍힌뒤 setTimeout으로 설정한대로 2초뒤에 World가 출력되고 접속이 종료됩니다.



node.js의 시스템 접근 예제
?
1
2
3
4
5
6
7
8
var sys = require("sys"),
    spawn = require("child_process").spawn;
 
var ls = spawn("ls", ["-ls", "/"]);
 
ls.stdout.addListener("data", function(data) {
    sys.print(data);
});



위 예제는 시스템에 접근하는 예제입니다. ls -ls /을 실행하여 그 결과를 출력하여줍니다. Node.js는 앞에서도 언급했든이 버퍼링을 강제하지 않습니다. data를 child process의 STDIO를 통해서 스트림하도록 저레벨의 기능(facility)을 사용합니다. (Simple IPC 예제는 chile process의 방법이 완전히 바뀐것 같은데 어떻게 똑같은 예제를 만들어야 할지 전혀 모르겠더군요. ㅠㅠ)



Epilogue
간단히 헬로월드만 따라해보고는 Node.js를 감히 판단할 수는 없겠지만 Node.js가 보여주는 미래는 놀랍습니다. 이벤트드리븐을 이용해서 서버의 퍼포먼스를 엄청나게 끌어들일 수 있으면 현재 프론트앤드 개발자가 가지고 있는 Javascript 스킬을 그대로 사용해서 자신이 원하는 웹서버를 직접 만들어 낼 수 있습니다. 엔터프라이즈급의 서버까지 만들어 낼 수 있을지는 아직 판단하기 어렵지만 정해진 일정기능의 서버는 아주 간단하게 충분한 퍼포먼스를 가지고 만들어 낼 수 있을 듯 합니다. 현재 버전이 0.2도 가지 않은 상황에서수많은 모듈들 이 개발되고 있는 것으로 보아도 그 미래가 상당히 기대가 됩니다. 아마 올해 node.js를 많이 만지게 될것 같습니다.


: