비동기 이벤트 프로그래밍

기존의 프로그래밍 언어들은 일반적으로 함수를 부르는 형태의 프로그래밍 구조를 가지고 있다. 이를 procedural programming model이라고 하는데,  코드가 순차적으로 실행되면서 함수를 호출하는 식의 구조를 가지고 있기 때문에 코드를 보면 코드의 수행 순서를 예측할 수 있다.

 

node.js는 event driven programming 이라는 개념을 가지고 있는데, 이 개념은 특정 이벤트가 발생되면 미리 이벤트에 맵핑된 함수가 실행되는 형태이다. 즉 해당 함수가 언제 호출 되는지를 예측할 수 가 없다.

var callback = function(data){

        console.log("call back has been called "+data);

}

 

$.get('/endpoint',callback);

 

위의 코드에 있는 callback 이라는 함수는 HTTP GET /endpoint request가 발생할 때만 수행된다.

  cf. procedural programming의 경우 함수는 코드상에서 명시적으로 호출을 해줘야 발생하지만, event driven programming은 이벤트에 의해서 함수가 호출 된다.

이러한 이벤트 방식의 유사한 사례는 윈도우즈나, 자바 SWING과 같은 GUI 계통의 프로그래밍에서도 찾아볼 수 있다. Mouse Click이나Button Click 같은 이벤트에, callback 함수를 Binding 시켜놓는 형태에서 볼 수 있다.

 

다음으로 node.js 특징은 비동기 프로그래밍 방식이 라는 것인데, 앞서 설명한 바와 같이 node.js는 비동기식 IO를 이용한다. 즉 IO 요청을 보내놓고, 코드를 blocking 상태에서 기다리는 것이 아니라 다음 코드로 진행한 다음, IO 가 끝났다는 이벤트가 오면, 미리 지정해놓은 함수를 실행하는 형태이다. 이렇게 함수를 호출한후, 작업이 끝난 후에, 호출되도록 정의한 함수를 “callback”함수라고 한다.

 

아래 코드를 보자

var fs = require('fs');

 

var contents = fs.readFile('hello.txt','utf-8',function(err,contents){

        console.log('read 1:'+contents);

});

이 코드는 hello.txt 라는 파일을 읽는 코드인데, 맨 뒤에 function(err.contents)라는 함수를 정의했다. 이 함수는 파일을 다 읽었을때 호출되는 callback 함수이다. fs.readFile을 호출하면, node는 파일이 다 읽을때 까지 이 코드에서 block되어 있는 것이 아니라 다음코드로 진행을 한다음, 파일을 다 읽으면 이벤트를 발생시켜서 여기에 연결된 function(err,contents)를 수행하게 되는 것이다.

 

node.js를 공부하다보면, 가장 큰 진입장벽중의 하나가, javascript나 node.js의 라이브러리를 새롭게 배우는 것보다, 기존의 procedural programming model에서 이러한 event driven programming의 개념을 익히는 것이 더 어렵다.

 

Event Emitter

그러면 이러한 이벤트를 어떻게 정의하고 처리할까? node.js에서 이벤트를 발생시키고 처리하는 방식은 EventEmitter 객체를 상속 받아서 구현한다.

 

라디오객체를 만들어서, on,off,change channel 이라는 이벤트가 발생했을때 각각 radoTurnOnCallBack,radioChangeChannelCallback, radioTurnOffCallback 함수가 각각 호출 되도록 해보자

 

먼저 Radio 객체를 만들어 보자

Radio = function(){

    events.EventEmitter.call(this); // call super class constructor

};

다음으로 Radio 객체를 EventEmitter로 부터 상속 받도록 하자.

클래스의 상속은 util 모듈의 inherits 메서드를 사용하면 된다.

util.inherits(Radio,events.EventEmitter);

 

다음으로, 호출될 callback 함수를 정의한 다음

// this is listener

var radioTurnOnListener = function(){

        util.debug('Radio turned on!!')

    }

var radioChangeChannelListener = function(channel){

        util.debug('Channel has been changed to '+ channel);

    }

var radioTurnOffListener = function(){

        util.debug('Radio turned off!!')

    }

radio 객체를 만들어서, 이 객체에 각각의 이벤트를 바인딩해보자

이벤트에 대한 바인딩은 emitter객체.on(‘이벤트명’,callback함수); 식으로 정의하면 된다.

radio.on('turnon',radioTurnOnListener);

radio.on('changechannel', radioChangeChannelListener);

radio.on('turnoff', radioTurnOffListener);

또는 event 바인딩시에, 함수명 대신 직접 함수를 다음과 같이도 정의할 수 있다.

radio.on('turnon', function(){

        util.debug('Radio turned on!!')

    });

 

이제 event가 바인딩 된 radio 객체가 생성되었다. 이제 이 객체에 이벤트를 날려보자

radio.emit('turnon');

radio.emit('changechannel');

radio.emit('turnoff');

다음은 실행 결과 이다.



전체 소스코드

var events = require('events');

var util = require('util');

 

// This is object that generate(emit) events

var Radio = function(){

    events.EventEmitter.call(this); // call super class constructor

 

};

util.inherits(Radio,events.EventEmitter);

 

// this is listener

var radioTurnOnListener = function(){

        util.debug('Radio turned on!!')

    }

var radioChangeChannelListener = function(channel){

        util.debug('Channel has been changed to '+ channel);

    }

var radioTurnOffListener = function(){

        util.debug('Radio turned off!!')

    }

 

var radio = new Radio();

 

radio.on('turnon',radioTurnOnListener);

radio.on('changechannel', radioChangeChannelListener);

radio.on('turnoff', radioTurnOffListener);

 

radio.emit('turnon');

radio.emit('changechannel');

radio.emit('turnoff');

 

Event Emitter methods

그러면 EventEmitter의 method들을 살펴보자..

Ÿ   emitter.addListener(‘eventname’,’listener function’)

Ÿ   emitter.on(‘eventname’,’listener function’)

이 메서드들은 eventname에 해당하는 이벤트에 대해서 ‘listener function’ 이름의 함수가 매번 호출 되도록 한다. 이벤트에 함수를binding 할때는 하나의 이벤트에 여러개의 listener를 바인딩 할 수 있으며, 최대 바인딩 개수는 디폴트 값은 10개이다.

Ÿ  emitter.once(‘eventname’,’listener function’)

이 메서드들은 eventname에 해당하는 이벤트에 대해서 ‘listener function’ 이름의 함수가 처음 한번만 호출 되도록 한다.

Ÿ  emitter.removeListener(‘eventname’,’listener function’)

이 메서드는 “eventname”에 바인딩 되어 있는 “listener function” 이름의 함수와의 binding을 제거한다.

Ÿ  emitter.removeAllListener([‘eventnames’])

인자는 배열형으로, 배열내에 들어가 있는 “eventnames”에 각각 바인딩 된 모든 함수에 대한 바인딩을 제거한다.

Ÿ  emitter.setMaxListeners(n)

해당 eventEmitter에 바인딩될 수 있는 이벤트의 수를 조정한다.

Ÿ  emitter.listeners(event)

“event”이름의 이벤트에 바인딩된 모든 callback 함수 이름을 리턴한다.

Ÿ  emitter.emit(‘eventname’,[args])

“eventname”의 이벤트를 생성하고, 이벤트를 생성할 당시 [args]에 정의된 값 들을 이벤트와 함께 전달한다.

Module

모듈은 개념은, 다른 파일에서 모듈을 불러다 쓸 수 있는 일종의 라이브러리 개념이다.

java의 import되는 다른 클래스나 C에서 #include 되는 라이브러리의 개념을 생각하면 된다.

모듈은 파일 단위로 구현되는데, export를 이용하여, 외부에 노출된다. 마치 java class의 public method와 같은 개념으로 생각하면 된다. 해당 파일에 있는 함수라도 exports를 하지 않으면, 외부에서 호출할 수 없다. (일종의 java class의 private과 같은 개념)

Hello.js 파일에 hello라는 함수가 있고, 이를 다른 파일에서 불러쓰고 싶다면, Hello.js 파일에서 다음과 같이 정의한후

var hello = function(){...}

exports = hello;

이 hello 함수를 사용하고자 하는 파일 (예를 들어 app.js에서) require 를 이용해서 모듈을 불러오고, 호출해서 사용한다.

var hello = require('./Hello');

hello();

require에는 사용하고자 하는 모듈의 파일명을 “.js” 확장자를 제외 하고 서술한다.

모듈에서 exports 될 수 있는 것은 함수와 자바스크립트 객체가 된다. 위의 예는 함수 형태를 이용하여 모듈을 사용하는 경우인데, 만약에 객체형으로 export 하고 싶다면 export하는 파일에서는

exports.hello = function(){...}

로 export하고 불러 사용하는 쪽에서는

var h = require('./Hello');

h.hello();

형태로 호출한다.

Module의 경로

앞의 예에서는 “./Hello”로 Hello.js에서 .js를 제거하고 서술하였으며, 앞에 “./”를 이용하여 경로를 서술하였다. 파일의 경로를 아래와 같이 서술할 수 있는데,

var hello = require('Hello');

 

이 경우 node.js는 현재 실행 디렉토리를 먼저 찾고 없으면, 애플리케이션 디렉토리의 하위디렉토리인 /node_modules/ 라는 디렉토리를 찾는다.

이 디렉토리는 node의 모듈을 저장하는 디렉토리이다. 만약에 이 디렉토리에서 찾지 못하면, 하위 디렉토리의 /node_modules/ 디렉토리를 찾게 된다.

예를 들어서 애플리케이션 디렉토리가 /home/terry/myapp 인 경우,

/home/terry/myapp/node_modules를 먼저 찾고 없으면 다음과 같은 순서로 찾게 된다.

Ÿ  /home/terry/node_modules

Ÿ  /home/node_modules

Ÿ  /node_modules

 

 

Module의 종류

Native Module과 javascript 모듈

node.js는 엔진은 C++로 짜야져 있고, 그 위에서 동작하는 애플리케이션은 javascript로 구현된다. 그래서 모듈도 두 가지 타입을 가지고 있다. C++/C로 된 모듈을 Native 모듈이라고 하고, Javascript로 된 모듈을 javascript 모듈이라고 한다. Javascript 모듈의 경우, 설치시에 파일이 복사되는 수준에서 설치가 되지만, native module의 경우에는 컴파일을 하면서 설치를 한다. (마치 Linux의 make install 처럼). 그래서 반드시C/C++ 컴파일러가 설치되어 있어야 한다. Linux에서는 GCC, Windows에서는 Visual Studio Expess(무료)등을 설치하면 된다.

Global Module과 Local Module

다음으로 Global Module과 Local Module이라는 개념을 가지고 있는데, Global Module은 시스템내에 설치된 모든 node.js 프로그램들이 참조할 수 있는 전역 모듈이다.

윈도우즈의 경우 디폴트로 ${user_home}/AppData/Roaming/npm/node_module 디렉토리에 설치된다.

또는 환경 변수 NODE_PATH에 그 경로를 다음과 같이 지정할 수 있다.

NODE_PATH=C:\Users\terry\AppData\Roaming\npm\node_module

Local Module의 경우 application 디렉토리의 /node_module 디렉토리에 설치되며, 해당 애플리케이션만 그 모듈을 참조할 수 있다.

기본 모듈과 확장 모듈

node의 모듈에도 node 설치시에 기본적으로 설치되는 모듈과, 추가로 설치해야 하는 확장 모듈이 있다. 기본 모듈은 http 프로토콜 핸들링이나, file system, event, cluster,TLS/SSL와 같은 암호화 등의 모듈이 있고, 확장 설치로는 웹 개발 프레임웍인 express등이 있다. 기본/확장 모듈에 대해서는 https://github.com/joyent/node/wiki/modules를 참고하기 바란다.


NPM

npm은 node package manager의 약자로, 앞서 설며한 모듈들에 대한 설치 및 의존성을 관리해 주는 도구이다. 마치 Linux의 rpm이나 Python의 pip 처럼 설치를 하면, repository에서 해당 모듈을 읽어다가 설치를 해주며, java의 maven처럼 package.json 이라는 파일에 (pom.xml과 비슷한 역할을 함) module간의 dependency (의존성)에 따라서 의존성이 있는 모듈을 같이 설치한다.

주요 명령어

여러가지 기능들이 있지만, 주요한 명령을 설명한다.

Ÿ      npm list {module} {-g} : 이 명령은 현재 디렉토리 아래에 설치되어 있는 확장 모듈을 리스트 해준다. {module}을 정해주면, 해당 모듈에 대한 리스트를 출력해주고, {-g} 옵션을 추가하면 global에 설치된 모듈 리스트들을 출력해준다.

Ÿ      npm install {-g} : npm 레파지토리 (maven 레파지토리 처럼 외부에 설치되어 있음)로부터, 모듈을 읽어서 로컬에 설치한다. –g 옵션을 적용할 경우 전역 모듈로 설치한다.

Ÿ      npm update {module} {-g} : 설치된 모듈을 최신 버전으로 업데이트 한다.

Ÿ      npm remove {module} {-g} : 설치된 모듈을 삭제한다.

Ÿ      npm info {module} : 해당 모듈의 의존성, 모듈명등 상세 정보를 출력한다.

Ÿ      npm init : 이 명령어를 수행하면 interactive prompt 모드를 통해서 package.json 파일을 만들기 위한 사용자로부터 받아서, package.json 파일을 생성해준다.

package.json 파일

package.json은 maven의 pom.xml과 같은 역할을 한다. 모듈에 대한 정보 (버전,제작자,모듈명 등) 기술을 하면서, 모듈에 대한 의존성, repository 경로등을 정의한다.

의존성 관리

아래 코드는 샘플 package.json 파일로, app이라는 모듈 0.0.1 버전에 대해서 서술하였으며, 이 모듈을 사용하기 위해서는 express 모듈 3.x버전과 redis 모듈 (버전에 상관없이 최신)을 필요로 한다. 또한 모듈은 git://xxx URL로부터 읽어서 설치하도록 되어 있다.

{

  "name": "app",

  "version": "0.0.1",

  "dependencies": {

    "express": "3.x",

    "redis": "*"

}

“repository”: {“type”:”git”,”url”,”git://xxxx”}

}

npm을 이용한 스크립트와 테스트 수행

npm은 make나 maven 처럼 custom command를 지정하여 명령어를 수행하도록 할 수 있다. 예를 들어 node server를 start하거나, test를 수행하거나 또는 빌드(?)패키징을 하도록 설정이 가능하다.

“scripts”라는 엘리먼트를 사용하면 되는데, 아래 예제는 npm start를 하면 app.js 애플리케이션으로 node.js를 실행하고, npm test를 하면, mocha 테스트 프레임웍을 수행하여,테스트를 수행하도록 하는 스크립트이다.

:중략

"redis": "*"

}

“scripts”: {“start”:”node app.js”,

             “test”:”mocha”}

}

참고

Npm 메뉴얼을 보면, “config’ 엘리먼트를 정의해놓고, 여기에 환경 변수를 설정할 수 있다.  db 접속 정보나, http listen 포트와 같이 환경에 따라서 변경이 되는 부분은 코드 상에 직접 넣지 않고, package.json 안에 설정해서, 이 파일만 변경을 하면 되도록 한다. 이를 통해서 개발,테스트 환경에 대해서package.json만 다르게 운영하거나, 운영 환경으로 배포시 간략하게 package.json만 변경하도록 한다.

예를 들어서 package.json에

{ “config”:{“dbport”:”3306’,”dbuser”:”terry”}, ..} 라고 정해 놓으면

코드내에서 http.createServer(...).listen(process.env.npm_package_config_dbport); 라고 하면, package.json에서 지정한 환경 변수를 가져다 쓸 수 있다고 한다.

https://www.npmjs.org/doc/misc/npm-config.html 문서 참고

근데, 직접해보니 안된다.

 

그래서 사용하는 방법이 별도의 config 파일을 만들고 예를 들어 config.json으로 만들고

파일내에

{ “dbport”:”3306”,

“dbuser”:”terry”}

라고 해놓고

var config = require(“./config.json”); 으로 부르면 바로 json 객체로 나온다.

다음으로 값을 참고하려면

console.log(config.dbport);

console.log(config.dbuser);

식으로 사용하면 된다.

 

버전 Semantics

Module의 버전Semantics를 살펴보자. 보통 3자리로 구성되는데 1.2.3일 경우

Ÿ   1: major version

Ÿ   2: minor version

Ÿ   3:patch level

이다.package.json에서 dependency에 대한 버전을 정의할 수 있는데,

"dependencies" : {

   "mymodule" : "1.8.1

}

는 1.8.1 버전에 대한 의존성을

"dependencies" : {

   "mymodule" : "~1"

}

이 의미는 : >= 1.0.0 <2.0.0

"dependencies" : {

   "mymodule" : "~1.8”

}

이 의미는 : >=1.8 <2.0.0 까지

을 정하는 것인데, 설명은 했지만, node.js의 경우 한참 개발되고 있는 신생 에코 시스템이기 때문에 모듈 버전간의 변화가 심할 수 있기 때문에 되도록이면 range 방식은 사용하지 않는 것이 바람직하다.

회사 같은 곳에서 HTTP proxy를 사용하는 경우 해결 방법

npm install 인스톨시 회사 내부 네트워크에서 사용할 경우, 회사에서 proxy를 사용하면, npm install시 proxy 를 타지 않아서 설치가 npm 설치가 제대로 동작하지 않는 경우가 있다. 이런 문제를 해결 하려면,npm 환경 변수 세팅에 http proxy 서버를 지정해주면 되는데, 지정 방법은 다음과 같다.

Ÿ   npm config set proxy http://proxy.company.com:8080

Ÿ   npm config set https-proxy http://proxy.company.com:8080


다음 연재에서는 node.js의 웹 프레임웍인 Express에 대해서 소개하겠다.



출처 : 조대협 (http://bcho.tistory.com)

'Programming > Node.js' 카테고리의 다른 글

MongoDB 연동 (mongo-native)  (0) 2014.05.08
웹개발 프레임웍 Express 2/2  (0) 2014.05.08
웹 개발 프레임웍 Express 1/2  (0) 2014.05.08
설치와 개발환경 구축  (0) 2014.05.08
node.js의 소개와 내부 구조  (0) 2014.03.27

Node.js 설치하고 개발환경 설정하기

다운로드 하기

http://www.nodejs.org 페이지에서 install 버튼을 누르면 OS에 맞는 인스톨러를 다운로드 해준다.



다음으로 installer를 실행한다.



설치가 되었으면, 설치된 디렉토리를 PATH에 추가한다.

set PATH=%PATH%;c:\dev\was\nodejs

자아 이제 node.js가 설치되었는지 확인하자. node.js는 대화형 cli를 제공한다.



설치를 끝냈으면 이제 간단한 웹서버를 만들어보자

var http = require('http');

http.createServer(function(request, response) {

response.writeHead(200);

response.write("Hello, this is dog.");

response.end();

}).listen(3000);

console.log('Listening on port 3000...');

이 코드를 app.js로 저장한다.

다음으로 다음과 같이 해당 파일을 실행한다.



이제 웹브라우져로 확인해보면 다음과 같이 메세지가 출력되는 것을 확인할 수 있다.



다음으로 개발환경을 설정해보자, 개발툴은 eclipse 등 여러가지 툴이 있지만 개인적으로 IntelliJ를 만든 JetBrain社의 WebStorm (http://www.jetbrains.com/webstorm/을 추천한다. 유료이긴 하지만, 30일 Trial로 사용할 수 있고, 가격은 개인용 버전의 경우 49$이다. (좋은 소프트웨어는 구매하자)

자바스크립트와 웹개발에 최적화 되어 있고, 빠르고 매우 직관적이다. 그리고 Bootstrap과 같은 자바스크립트 프로젝트나, mocha와 같은 자바스크립트 테스트 프레임웍들도 잘 지원한다.REST API를 테스트하기 위한 기능이나 디버깅 기능도 상당히 직관적이라서 어렵지 않게 사용이 가능하다.

  오픈소스를 사용하고 있거나 강의에서 사용할 경우에는 무료 라이센스를 신청할 수 있다.

 



Figure 1. JetBrain社의 자바스크립트, node.js 개발환경인 WebStorm 7


출처 : 조대협 (http://bcho.tistory.com)

'Programming > Node.js' 카테고리의 다른 글

MongoDB 연동 (mongo-native)  (0) 2014.05.08
웹개발 프레임웍 Express 2/2  (0) 2014.05.08
웹 개발 프레임웍 Express 1/2  (0) 2014.05.08
Event,Module,NPM  (0) 2014.05.08
node.js의 소개와 내부 구조  (0) 2014.03.27

빠르게 훝어보는 node.js

#1  node.js의 소개와 내부 구조

조대협 (http://bcho.tistory.com)


요즘 들어서 새로운 기술에 대한 인식도 많이 떨어지고, 공부하는 것도 게을러 져서, 어쩌다 보니 우연한 기회에 스터디를 하게 되었습다. 스터디 주제는 팀원들이 골랐기 때문에 자연히 따라가게 되었는데, 주제는 무려 node.js. 때 맞침 vert.x를 보고 있었기 때문에, 유사 솔루션을 보는 것도 괜찮겠다고 해서 스터디를 시작했는데, 몇주가 지난후에 지금까지 스터디를 하면서 node.js에 대한 내용을 가볍게 정리해보고자 한다.


node.JS에 대한 소개

node.js는 single thread 기반으로 동작하는 고성능의 비동기 IO (Async / Non-blocking IO)를 지원하는 네트워크 서버이다. 2009년 Ryan Dahl에 의해서 개발이 시작되어 있으며, 현재 수많은 지원 모듈과, 레퍼런스, 에코 시스템을 가지고 있는 오픈 소스 프로젝트 중에 하나이다.

Google Chrome V8 엔진으로 개발되어 있으며, 프로그래밍 언어로는 Java script를 사용하며, Event 기반의 프로그래밍 모델을 사용한다. (나중에 자세한 사항을 설명하도록 하자). 근래에 들어서 많은 인터넷 기업들이 node.js를 도입하고 있다. Linked in이나 Paypal 그리고 얼마전에는 그루폰까지 상당 부분의 내부 시스템을 node.js로 전환하였다.


node.JS 의 장점

먼저 node.js의 장점을 짚고 넘어가보면 다음과 같다.

Javascript 기반이고, 개발 구조가 매우 단순화 되어 있어서 빠르게 개발이 가능하다. 즉 클라이언트에서 front end를 자바스크립트를 통해서 개발하던 FE(front end) 개발자들도 손쉽게 서버 프로그래밍이 가능하다는 것이고, 조직의 입장에서도 FE와 BE(BackEnd) 엔지니어의 기술셋을 나눌 필요가 없다는 것이다. node.js가 빠르다고는 하지만, 실제 성능보다는 이러한 Learning curve나, 조직내의 FE/BE 기술 통합에서 오는 장점이 더 큰이유가 아닐까 싶다.

다음으로는 socket.io를 이용한 웹 push 구현이 매우 쉽게 구현이 가능하다. 여타 플랫폼도 WebSocket을 이용한 Push 를 지원하기는 하지만,WebSocket은 브라우져 종류나 버전에 따라서 제한적으로 동작한다. node.js의 경우 웹브라우져의 종류에 따라서 WebSocket뿐만 아니라, Long Polling등 다른 push 메커니즘을 브라우져 종류에 따라서 자동으로 선택하여 사용하고 있으며, 이러한 push 메커니즘은 socket.io API 내에 추상화 되어 있기 때문에, 어떤 기술로 구현이 되어 있던간에 개발자 입장에서는 socket.io만 쓰면 간단하게 웹 기반의 push 서비스가 구현이 가능하다.

마지막으로, non-blocking IO 모델을 지원하는데, 뒤에서 자세하게 설명하겠지만, 일반적인 서버들은 io 요청을 보낸후, 요청을 보낸 thread나process가 io 요청이 끝날 때 까지 io wait 상태로 응답을 기다리고 있다. 이로 인해서, 동시에 서비스할 수 있는 클라이언트 수 (thread가 계속 기다리기 때문에)에 제약이 있고, CPU 사용 효율에도 제약을 갖는다. 이러한 문제를 해결하기 위해서 node.js는 non-blocking io 컨셉을 사용하는데, io 요청이 있으면, io 처리를 던져 놓고, thread나 process는 다른 일을 하고 있다가, io 처리가 끝나면 이에 대한 이벤트를 받아서, 응답을 처리하는 형태가 된다.


node.js의 내부 작동 원리 구조

다음으로는 node.js의 내부 구조에 대해서 가볍게 살펴보도록 하자.

Node.js는 Google의 Chrome V8 자바스크립트 엔진을 기본으로 동작한다. 이를 기반으로 Single Thread 기반의 Event Loop (libuv)가 돌면서 요청을 처리하며, 시스템적으로 non-blocking io를 지원하지 않는 io 호출이 있는 경우, 이를 비동기 처리 하기 위해서 내부의 Thread pool (libio)을 별도 이용하여 처리한다.

그 위에 네트워크 프로토콜을 처리하는 socket, http 바인딩 모듈이 로드 되고, 맨 윗단에, node.js에서 제공하는 standard library (파일 핸들링, console등)이 로드 된다.



이제부터 각각의 중요한 내부 작동원리에 대해서 조금 더 자세하게 알아보도록 하자.

C10K

인터넷이 발전하고 서비스가 거대화 되면서, 서버 대당 처리할 수 있는 동시접속자수에 대한 한계가 재기 되었고, 이를 정의한 문제가 C10K (Connection 10,000) 문제이다. 즉 서버에서 10,000 개 이상의 소켓을 생성하고 처리를 할 수 있느냐 에 대한 문제이다. 인터넷 전이나 초기 같으면,동시에 하나의 서버에서 10,000개의 connection을 처리한 것은 아주 초대용량의 서비스 였지만, 요즘 같은 SNS 시대나, 게임만해도 동접 수만을 지원하는 시대에, 동시에 많은 클라이언트를 처리할 수 있는 능력이 요구 되었다. 메모리나 CPU가 아무리 높다하더라도 많은 수의 소켓을 처리할 수 없다면, 동시에 많은 클라이언트를 처리할 수 없다는 문제이다.

Unix의 IO 방식이 이 문제의 도마위에 올랐는데, 기존의 Unix System Call인 select()함수를 이용하더라도, 프로세스당 최대 2048개의 소켓 fd (file descriptor) 밖에 처리를 할 수 없었다. 이를 위한 개선안으로 나온 것이 비동기 IO를 지원하는 API인데, Windows의 iocp와 같은 비동기 시스템 호출이다. 세부적은 동작 방식은 다르지만 이러한 비동기 방식의 IO의 개념을 설명하면 다음과 같다.

Async / Non blocking IO

먼저 동기식 IO는 다음과 같이 동작한다. file write io를 예를 들어보면, file_write를 호출하면, 디스크에 파일 쓰기 요청을 하고, 디스크가 파일을 쓰는 동안 프로그램은 file_write 부분에 멈춰서 대기하게 된다. (블록킹상태). 파일을 쓰는 동안에는 CPU가 사용되지 않기 때문에, CPU는 놀고,파일이 다 써지만 디스크에서 리턴해서 file_write 함수 다음 코드로 진행을 하게 된다.



 비동기식 IO는 어떻게 처리가 될까?



파일 쓰기 요청을 할 때, 파일 요청이 끝나면 호출될 함수(callback)를 같이 넘긴다.

파일 쓰기 요청이 접수되면 프로그램은 파일이 다 써지는 것을 기다리지 않고, 요청만 던지고 다음 코드로 진행을 계속하낟. 파일을 다 쓰고 나면 앞에서 등록했던 callback 함수를 호출하여 파일 쓰기가 다 끝났음을 알리고 다음 처리를 한다.

비교를 해서 설명하자면 파일 쓰기가 떡뽁이를 주문하는 과정이라면, 앞의 블록킹 IO 방식의 경우에는 떡뽁이를 주문해놓고, 나오기 까지 기다리고 있는 형태라면, 비동기식 방식은 떡뽁이를 주문해놓고, 나가서 다른 일들을 하다가 나오면, 떡뽁이가 나왔으니 가져가라고 전화(이벤트)를 하는 형태가 된다.

Single Thread Model

Tomcat,JBoss와 같은 웹애플리케이션 서버나 Apache와 같은 일반적인 웹서버는 Multi Process 또는 Multi Thread의 형태를 가지고 있다.



톰캣과 같은 서버는 위의 그림과 같이 Client에서부터 요청이 오면, Thread를 미리 만들어 놓은 Thread Pool에서 Thread를 꺼내서 Client의 요청을 처리하게 하고, 요청이 끝나면 Thread Pool로 돌려보낸 후, 다른 요청이 오면 다시 꺼내서 요청을 처리하게 하는 구조이다. 동시에 서비스 할 수 있는 Client의 수는 Thread Pool의 Thread 수와 같은데,물리적으로 생성할 수 있는 Thread의 수는 한계가 있다. 예를 들어 Tomcat의 경우 500개 정도의 쓰레드를 생성할 수 있다. (물론 2,000개 정도까지도 생성할 수 있지만, 한계가 있다.) 즉 동시에 처리할 수 있는 Client 수에 한계가 있다.

또한 IO 효율면에서도 보면, 아래 그림과 같이 Client에 할당된 Thread는 IO 작업 (DB,Network,File)이 있을 경우 IO 호출을 해놓고, Thread는CPU를 사용하지 않는 Wait상태로 빠져 버리게 된다.



이런 문제를 해결 하기 위한 것이 Single Thread 기반의 비동기 서버인데, 하나의 Thread만을 사용해서 여러 Client로부터 오는 Request를 처리한다. 단, IO 작업이 있을 경우 앞에서 설명한 비동기 IO방식으로 IO 요청을 던져놓고, 다시 돌아와서 다른 작업을 하다가 IO 작업이 끝나면 이벤트를 받아서 처리하는 구조이다.

아래 그림에서 처럼, Client A가 요청을 받으면, CPU 작업을 먼저하다가 IO작업을 던져놓고, Client B에서 요청이 오면, CPU작업을 하다가 IO작업을 던져놓고, Client A의 IO작업이 끝나면 이를 받아서 Client A에 리턴하는 식의 구조이다. IO작업시 기다리지 않기 때문에(Block 되지 않기 때문에), 하나의 Thread가 다른 요청을 받아서 작업을 처리할 수 있는 구조가 된다.  이 요청을 받아서 처리하는 Thread를 ELP (Event Loop Thread)라고 한다.



Thread pool

Node.js도 single thread만 사용하는 것이 아니라 내부적으로 multi thread pool을 사용하기는 한다. 예를 들어 file open등과 같은 일부 IO는OS에 따라서 nonblocking function을 지원하지 않는 경우가 있기 때문에, 이러한 blocking io function을 호출할 경우에는 어쩔 수 없이blocking이 발생하는 데, 이 경우 single thread로 구현된 event loop thread가 정지되기 때문에 이러한 문제를 해결 하기 위해서 내부적으로thread pool을 별도로 운영하면서 blocking function call의 경우에는 thread pool의 thread를 이용하여 IO 처리를 하여 event loop thread가 io에 의해서 block되지 않게 한다.



Event Loop

그러면 이 하나의 Thread로 여러 클라이언트의 요청, 즉 여러 개의 socket connection을 어떻게 처리할까? 방법은 Multiplexing에 있다. 여러 개의 socket이 동시에 연결되어 있는 상태에서 하나의 Thread는 어느 socket으로부터 메시지가 들어오는 지 보다가, socket에서 메시지가 들어오면, 그 메시지를 꺼내 받아서 처리를 하는 방식이다. (epoll, kqueue, dev/poll ,select등을 이용)



개념적으로 생각하면,

socket fd = array[연결된 socket connections]

for(int i=0;i<fd.length;i++){

if(fd 가 이벤트가 있으면){

   알고리즘 처리

}// if

}// if

와 같이 표현할 수 있다. (실제 구현체는 다르지만.).

단 이런 single thread 모델에서 주의해야 하는 점은 CPU 작업이 길어질 경우에는 다음 request를 처리하지 못하기 때문에, 다음 request처리가 줄줄이 밀려버릴 수 있다는 것이다. 예를 들어 보자 커피 전문점이 있다고 보자, 주문을 받는 사람이 Single Thread이다. 커피 주문이 들어오면 들어오면 주방에서 일하는 사람에게 커피 주문을 넘기고, 다음 고객의 주문을 받는다. 앞에서 주문한 커피가 주방에서 나오면 이를 주문한 사람에게 커피를 넘겨준다.

커피 주문을 request, 커피를 response, 그리고 커피를 만드는 과정을 IO라고 생각해보자. 만약에 커피를 주문받거나 커피를 건네주는 과정에 많은 시간이 소요된다면 (CPU 작업이 많다면), 뒤에 손님이 기다리는 일이 발생하게 된다. 예를 들어 주문에 1분씩 소요된다면, 첫번째 손님은 1분을, 두번째는 2분을… 60번째는 60분을 기다리게 된다. 그래서 이러한 single thread model에서는 각 request가 CPU를 많이 사용하는 경우request 처리가 줄줄이 지연되면서 성능에 심각한 영향을 줄 수 있기 때문에 CPU intensive한 작업에는 적절하지가 않다.


언제 node.js를 쓰거나 쓰지 말아야 할까?

node.js의 사용 용도에 대해서는 논쟁이 많기는 하지만, 공통적으로 공감하는 부분은 prototyping에는 무지 빠르다. mysql이나 mongodb같은  persistence를 이용해서 CRUD rest api를 implementation하는데, 코딩양이 20~30줄? 정도밖에 안된다. (자동 생성되는 코드를 빼면 이것 보다 적을지도.)

Async IO를 사용하기 때문에, file upload/download와 같은 network streaming 서비스에 유리하다. 또한 real time web application, 예를 들어 채팅 서비스 같은 곳에 socket.io를 이용하면 쉽게 만들 수 있으며, Single page app 개발에 좋다. 가볍고 생산성이 높은 웹 개발 프레임웍을 가지고 있고, 간단한 로직을 가지면서 대용량 그리고 빠른 응답 시간을 요구로 하는 애플리케이션에 적절하다.

그러면 어디에 쓰지 말아야 할것인가?

공통적으로 대답하는 것은. CPU 작업이 많은 애플리케이션에는 절대 적당하지 않다. Node.js는 single thread 구조이다. 그래서 하나의  request를 처리할 때 CPU를 많이 사용하면 다른 요청 처리가 지연되게 되고, 전체적인 응답시간 저하로 연결된다.

그리고 다소 이견이 있기는 하지만 CRUD가 많고 페이지가 많은 웹개발에는 적절하지 않다고 한다.  일단 기존의 Ruby on Rails나 Python,PHP등의 웹 프레임웍의 성숙도가 높기 때문이라고 한다.  (직접 테스트 해보니, express와 같은 많은 웹 개발 프레임웍이 있는데, 사실 보면 성숙도가 꽤 높다. 다른 이유가 있는지는 모르겠지만 일반적인 웹 개발에는 추천하지 않는다.)

마지막으로, 초보자나 초보팀이 쓰기에는 적절하지 않다는 것이 본인의 의견이다. javascript언어의 특성상 에러를 가지고 있는 코드 위치에 진입할 때 그때 에러가 나고, 에러가 나면 대부분 서버가 죽어버리기 때문에 운영 관점에서 trouble shooting등이 어려울 수 있으며,  single event loop의 특성상, 하나의 코드가 잘못되서 시스템이 느려지게 되면 전체 request 처리에 문제가 올 수 있기 때문에, 잘 짜야진 코드는 필수이다. 물론 자바 기반의 일반 application server 나 다른 application server도 마찬가지이기는 하지만, 코드가 잘못되었다고 node.js처럼 무작정 서버가 내려않거나, 전체 시스템이 hang up (멈춤)지는 않는다.


'Programming > Node.js' 카테고리의 다른 글

MongoDB 연동 (mongo-native)  (0) 2014.05.08
웹개발 프레임웍 Express 2/2  (0) 2014.05.08
웹 개발 프레임웍 Express 1/2  (0) 2014.05.08
Event,Module,NPM  (0) 2014.05.08
설치와 개발환경 구축  (0) 2014.05.08



1. Introduction

  하둡(Hadoop) 공식 홈페이지에 업로드 되어 있는 컴파일 된 하둡을 사용할 때 다음과 같은 문제가 발생하였다. (사실 하둡만을 운영 한다면 문제 될 에러는 아니다. Java의 native 라이브러리를 사용하려고 한다면 해결해야 할 문제이다.)

WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable

  이 오류는 32비트 운영체제에서 컴파일 된 것이기 때문에 64비트에서 사용하게되어 WARNING 에러가 발생한다. 그래서 이 문서에서는 하둡 2.2.0을 64비트로 컴파일하여 Psedo-Distributed Mode 설정까지 하려고 한다.


2. Environment

  • OS : Ubuntu 12.04 LTS 64bit
  • Hadoop : Hadoop 2.2.0
  • Proto Buffer : Protoc >= 2.5.0
  • User : hakawati
  • Building Path : /home/hakawati/tools

3. Compile
3.1. Support Package
[hakawati@hakawati-Virtualbox:~$] sudo apt-get install build-essential maven openjdk-7-jdk cmake libssl-dev ssh
  • build-essential : 프로그램 개발하는데 필요한 라이브러리를 제공하는 패키지
  • maven : 메이븐은 자바용 프로젝트 관리 도구로 Hadoop은 메이븐으로 설치
  • openjdk-7-jdk : 메이븐을 운용하기 위해서 자바 설치
  • cmake, libssl-dev : 하둡 설치 시 Common Library와 컴파일로 사용
  • ssh : 하둡에서 ssh 설정에 필요

[~$] mkdir tools

[~$] cd tools

[~/tools$] wget http://protobuf.googlecode.com/files/protobuf-2.5.0.tar.gz

[~/tools$] tar -zxvf protobuf-2.5.0.tar.gz

[~/tools$] cd protobuf-2.5.0

[~/tools/protobuf-2.5.0$] ./configure

[~/tools/protobuf-2.5.0$] make

[~/tools/protobuf-2.5.0$] sudo make install

[~/tools/protobuf-2.5.0$] sudo ldconfig

[~/tools/protobuf-2.5.0$] cd ..


  • Proto buffer : 통신을 통해 데이터를 주고 받을 때 각종 데이터를 byte형태로 바꾸어주는 도구 Ubuntu에는 기본으로 2.4.1 버전이 설치되어 있으나, Hadoop 컴파일때는 2.5.0 이후 버전을 사용해야 하기 때문에 설치


3.2. Hadoop Compile

  소스 컴파일을 할것이기 때문에 소스를 압축한 타르볼을 다운로드 받는다.

[~/tools$] wget http://mirror.apache-kr.org/hadoop/common/hadoop-2.2.0/hadoop-2.2.0-src.tar.gz

[~/tools$] tar xfz hadoop-2.2.0-src

[~/tools$] cd hadoop-2.2.0-src


3.2.1. Apply HADOOP-10110

  패치 없이 하둡을 컴파일 시 Apache Hadoop Auth 부분에서 에러가 발생하여 컴파일이 중단된다.

  아파치 하둡에서 해당 에러를 패치(Patch) 할 수 있게 대응했다. 해당 패치는 Hadoop Auth 설정 파일인 hadoop-common-project/hadoop-auth/pom.xml 파일을 패치한다. (https://issues.apache.org/jira/browse/HADOOP-10110)

[~/tools/hadoop-2.2.0-src$] wget https://issues.apache.org/jira/secure/attachment/12614482/HADOOP-10110.patch

[~/tools/hadoop-2.2.0-src$] patch -p0 < HADOOP-10110.patch

patching file hadoop-common-project/hadoop-auth/pom.xml

Hunk #1 succeeded at 55 (offset 1 line)


3.2.2. Command of Hadoop Compile

[~/tools/hadoop-2.2.0-src$] mvn package -Pdist,native -DskipTests -Dtar

정상적으로 완료되면 다음과 같은 결과를 볼 수 있다.

컴파일 완료되었다면 ~/tools/hadoop-2.2.0-src/hadoop-dist/target 폴더안에 다음과 같이 압축이 풀린 하둡 폴더와 Tarball로 압축된 하둡 두 가지 형태를 볼 수 있다. 어느 것이든 가져다 설정하여 쓸 수 있다.

3.3. Error Report

3.3.1. Haddop Common Error

  만약 Apache Haddop Common에서 FAILURE가 뜬다면 보조 모듈설치에서 설치하는 cmake와 libssl-dev를 설치하지 않았거나 설치 시 문제가 생겼을 경우이다.


4. Setting of Hadoop

4.1. Kind of Hadoop Mode

  • Stand-Alone Mode (독립 모드) : 하둡의 기본 모드로 로컬 머신에서만 실행한다. 다른 노드와 통신을 할 필요가 없기 때문에 HDFS를 사용하지 않으며 데몬들도 모두 실행하지 않는다. 독립적으로 MapReduce 프로그램의 로직을 개발하고 디버깅하는데 유용하다.
  • Pseudo-Distributed Mode (가상 분산 모드) : 한대의 메인 컴퓨터로 클러스터를 구성하고, 모든 데몬을 실행한다. 독립 모드 기능을 보완한 형태로 다른 데몬과의 상호작용에서 발생하는 일들을 모두 검사한다.
  • Fully-Distributed Mode (완전 분산 모드) : 분산 저장과 분산 연산의 모든 기능이 갖추어진 클러스터를 구성한다.

4.2. Setting of Path

환경 변수를 설정하는데, 하둡에서 사용하는 환경 변수도 함께 설정하기 때문에 환경 변수명을 바꾸어서는 안된다. 추가로 하둡 설정 파일에서 환경 변수의 JAVA_HOME 경로를 읽어들이는데, 하둡이 사용하는 사용자 그룹과 환경 변수를 사용하는 구룹은 다르다. 이 문서에서는 ~/.bashrc 에 환경 변수를 설정하고 하둡의 설정에서 복수로 설정한다. 하지만 복잡한 설정을 하고싶지 않으면 모든 사용자가 사용하는 환경변수 설정인 /etc/environment에 설정을 하면된다. (다만 environment에 설정하면 시스템을 재부팅 해줘야 한다.)

[~$] vim ~/.bashrc

#Java

export JAVA_HOME="/usr/lib/jvm/java-7-openjdk-amd64"

export PATH=$PATH:$JAVA_HOME/bin


# Hadoop

export HADOOP_PREFIX="~/hadoop-2.2.0"

export PATH=$PATH:$HADOOP_PREFIX/bin

export PATH=$PATH:$HADOOP_PREFIX/sbin

export HADOOP_MAPRED_HOME=${HADOOP_PREFIX}

export HADOOP_COMMON_HOME=${HADOOP_PREFIX}

export HADOOP_HDFS_HOME=${HADOOP_PREFIX}

export YARN_HOME=${HADOOP_PREFIX}


# Native Path

export HADOOP_COMMON_LIB_NATIVE_DIR=${HADOOP_PREFIX}/lib/native

export HADOOP_OPTS="-Djava.library.path=$HADOOP_PREFIX/lib/native"

[~$] source ~/.bashrc

4.3. Setting of SSH

  하둡이 노드들과 통신을 할 때 기본으로 SSH를 사용한다. 로컬에서 테스트를 할 목적이기에 비밀번호를 요구하지 않도록 설정한다.

[~$] ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa

[~$] cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys

[~$] chmod 644 ~/.ssh/authorized_keys


로컬로 SSH를 접근했을 때 비밀번호를 묻지 않으면 제대로 설정 된 것이다. 접근 후 exit로 빠져나온다.

[~$] ssh localhost

Welcome to Ubuntu 12.04 LTS (GNU/Linux 3.2.0-23-generic x86_64)

[~$] exit

logout

Connection to localhost closed.

4.4. Setting of Hadoop

  Pseudo-Distributed Mode 형태로 설정을 진행한다. 설정파일들은 모두 ${HADOOP_PREFIX}/etc/hadoop 디렉토리 안에 존재한다.


4.4.1. hadoop-env.sh

  하둡이 실행하는 모든 프로세스에 적용되는 시스템 환경 값에 대한 스크립트 파일로 전체 클러스터 노드에 복사해 사용하는 설정 파일이다.

# export JAVA_HOME=${JAVA_HOME} // 주석처리

export JAVA_HOME=”/usr/bin/jvm/java-7-openjdk-amd64”

export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_PREFIX/lib/native

export HADOOP_OPTS="-Djava.library.path=$HADOOP_PREFIX/lib/native"

4.4.2. yarn-env.sh

  yarn(Yet Another Resource Negotiator)는 분산된 환경을 운영 할 수 있도록 제공하는 환경 설정 파일이다.

export HADOOP_COMMON_LIB_NATIVE_DIR=${HADOOP_PREFIX}/lib/native

export HADOOP_OPTS="-Djava.library.path=$HADOOP_PREFIX/lib/native"

4.4.3. core-site.xml

  HDFS와 MapReduce에서 공통으로 사용하는 로그 파일, 네트워크 튜닝, I/O 튜닝, 파일 시스템 튜닝, 압축 등과 같은 하둡 코어를 위한 환경 설정 파일이다. MapReduce에서도 공통으로 사용한다.

<property>

    <name>fs.default.name</name>

    <value>hdfs://localhost:9000</value>

    <final>true</final>

</property>


4.4.4. hdfs-site.xml

  hdfs-site.xml은 네임노드, 보조 네임노드, 데이터 노드 등과 같은 HDFS 데몬을 위한 환경설정 구성한다. dfs.namenode.name.dir은 파일의 디렉토리 정보와 파일 정보 등을 저장하는 폴더이다. 해당 저장위치는 hdfs가 아닌 로컬에 저장을 한다. dfs.datanode.name.dir은 하둡 파일 시스템에 저장되는 모든 파일이 저장되는 위치이다.

<property>

    <name>dfs.namenode.name.dir</name>

    <value>file:${HADOOP_PREFIX}/hadoop/dfs/name</value>

    <final>true</final>

</property>


<property>

    <name>dfs.datanode.data.dir</name>

    <value>file:${HADOOP_PREFIX}/hadoop/dfs/data</value>

    <final>true</final>

</property>

 

<property>

    <name>dfs.permissions</name>

    <value>false</value>

</property>

4.4.5. mapred-site.xml

  mapred-site.xml은 JobTracker와 Task Track과 같이 맵 리듀스 데몬을 위한 환경설정 파일이다. 해당 파일은 mapred-site.xml.template로 제공되며 mapred-site.xml로 변경하여 사용한다.

<property>

    <name>mapreduce.framework.name</name>

    <value>yarn</value>

</property>

 

<property>

    <name>mapred.system.dir</name>

    <value>file:${HADOOP_PREFIX}/hadoop/mapred/system</value>

    <final>true</final>

</property>

 

<property>

    <name>mapred.local.dir</name>

    <value>file:${HADOOP_PREFIX}/hadoop/mapred/local</value>

    <final>true</final>

</property>

4.4.6. yarn-site.xml

  yarn과 관련된 데몬을 설정하는 파일이다.

<property>

<name>yarn.nodemanager.aux-services</name>

<value>mapreduce_shuffle</value>

</property>


<property>

<name>yarn.nodemanager.aux-services.mapreduce.shuffle.class</name>

<value>org.apache.hadoop.mapred.ShuffleHandler</value>

</property>

4.4.7. Create Directories

  hdfs-site.xml과 mapred-site.xml에서 설정한 디렉터리 경로에 맞게 디렉터리를 생성한다. 디렉터리 이름들은 사용자가 원하는대로 변경해도 된다.

[~$] mkdir –p ${HADOOP_PREFIX}/hadoop/dfs/name

[~$] mkdir –p ${HADOOP_PREFIX}/hadoop/dfs/data
[~$] mkdir –p ${HADOOP_PREFIX}/hadoop/mapred/system
[~$] mkdir –p ${HADOOP_PREFIX}/hadoop/mapred/local


5. Start Hadoop

5.1. NameNode Format

  최초 실행시 NameNode를 포맷해준다.

[~$] hdfs namenode -format

5.2. 데몬 실행

  데몬 실행 스크립트는 ${HADOOP_PREFIX}/sbin 에 위치해있다. start-all.sh로 모든 데몬을 실행한다.

  • 전체 데몬 실행/종료 : start-all.sh / stop-all.sh
  • HDFS만 실행/종료 : start-dfs.sh / stop-dfs.sh
  • YARN만 실행/종료 : start-yarn.sh / stop-yarn.sh


5.3. 데몬 실행 확인

5.3.1. JPS

  jps 명령으로 자바 가상 머신(JVM)의 프로세스 상태를 확인한다. 다음과 같이 하둡과 관련된 5개의 데몬과 jps 명령의 1개의 데몬 총 6개의 데몬이 실행 되어있으면 된다.

5.3.2. Command of Hadoop

  하둡의 명령어 중에서 dfsadmin -report 명령을 통해 마스터와 슬레이브의 모든 시스템들의 상태를 콘솔 형태의 레포트로 확인 할 수 있다.

5.3.3. Connection Web Interface

  하둡은 웹 인터페이스로도 다양한 관리를 제공해주고 있다.

localhost:8088 

localhost:50070


6. 참조 사이트


jQuery를 사용 하는 추세가 javascript 네이티브 문법은 거의 사용을 안하게 되는데요.

하지만, 꼭 javascript로만 사용 할 수밖에 없는 이벤트들이 있네요. 예를 들어 <onbeforeunload> 이런 이벤트는 네이티브를 꼭 써야만 하겠죠? 앞으로는 서버 언어보다는 자바스크립트 위주로 공부를 계속 해야겠네요. 

 

onabort
이미지의 다운로드를 중지할 때 (브라우저의 중지버튼)

onactivate
개체가 활성화될 때 발생
(태그의 기능이 작용할 때 발생하는 이벤트로 예를 들어 링크를 누를 경우 링크가 옮겨질때 발생하는 것을 감지하는 이벤트 핸들러)

onafterprint
문서가 출력되거나 혹은 출력하기 위해 출력미리보기를 한 후에 발생

onafterupdate
데이터영역 개체에서 발생하는 이벤트로 데이터 소스 오브젝트내의 데이터가  업데이트 되었을 때 발생(데이터 개체부분 참조)

onbeforeactivate
개체가 활성화 상태로 되기 바로 직전에 발생 (onactivate 참고)

onbeforecopy
선택 영역이 시스템의 클립보드로 복사되기 바로 직전에 발생

onbeforecut
선택 영역이 지워지기 바로 직전에 발생

onbeforedeactivate
부모 문서에서 현재 개체에서 다른 개체로 activeElement가 바뀔 때 발생
(activeElement는 개체를 지칭하는 예약어로도 쓰임)

onbeforeeditfocus
편집가능한 개체 내부에 포함된 개체가 편집활성화된 상태가 되거나 혹은 편집가능한 개체가 제어를 위해 선택될 때

onbeforepaste
시스템의 클립보드에서 문서로 붙여넣기 될 때 대상 개체에서 발생

onbeforeprint
문서가 출력되거나 혹은 출력하기 위해 출력미리보기 직전에 발생

onbeforeunload
페이지가 언로드되기 직전에 발생

onbeforeupdate
데이터영역 개체에서 발생하는 이벤트로 데이터 소스 오브젝트내의 데이터가  업데이트 되기전에 발생 (데이터 개체부분 참조)

onblur
개체가 포커스를 잃었을 때

onbounce
마퀴태그에서 alernate상태에서 스크롤이 양 사이드에서 바운드될 때 발생

oncellchange
데이터 제공 개체에서 데이터가 변화할 때 발생

onchange
개체 혹은 선택영역의 내용이 바뀔 때 발생

onclick
개체위에서 마우스의 왼쪽 버튼을 누를 때 발생

oncontextmenu
클라이언트 영역에서 사용자가 마우스 오른쪽 버튼을 눌러 콘텍스트 메뉴를 열 때 발생

oncontrolselect
사용자가 개체의 제어 영역을 만들 때 발생

oncopy
시스템의 클립보드에 선택영역 혹은 개체를 복사할 때 소스 개체로부터 발생

oncut
시스템의 클립보드에 선택영역 혹은 개체를 잘라낼때 소스 개체로부터 발생

ondataavailable
비정기적으로 데이터를 전달하는 데이터 소스 개체로부터 데이터가 도착할 때마다 정기적으로 발생

ondatasetchanged
데이터 소스개체의 변화에 의해 데이터가 노출된 상태로 될 때 발생

ondatasetcomplete
데이터 소스 개체로부터 모든 데이터가 유용한 상태로 표시될 때 발생

ondblclick
사용자가 개체에 더블클릭 할때 발생

ondeactivate
모 문서에서 현재 개체에서 다른 개체로 activeElement가 변할 때 발생

ondrag
드래그 상태가 지속되는 동안 소스 객체로부터 발생

ondragend
드래그 상태가 끝날 때 소스 객체로부터 발생

ondragenter
사용자가 개체를 드래그하여 드롭가능 위치로 지정된 영역으로 이동할 때 타갯 개체에서 발생

ondragleave
사용자가 개체를 드래그하여 드롭가능 위치로 지정된 영역을 떠날 때 타갯 개체에서 발생

ondragover
사용자가 개체를 드래그하여 드롭가능 위치로 지정된 영역내에서 드래그할 때 계속적으로 타갯 개체에서 발생

ondragstart
선택된 개체 혹은 텍스트 영역에서 사용자가 드래그를 시작할 때 발생

ondrop
드래그앤드롭 작용에서 상태가 진행되는 동안 개체가 타갯 개체에 드롭되었을 때 타갯 개체에서 발생

onerror
개체가 로드되는 동안 발생하는 이벤트

onerrorupdate
데이터 소스 개체 내에 데이터가 없데이트 되는 동안 에러가 발생할 때 데이터 영역 개체에서 발생

onfilterchange
비주얼 필터의 상태가 바뀌거나 트랜지션이 완료되었을 때 발생

onfinish
마퀴개체의 loop가 완료되었을 때 발생

onfocus
개체가 포커스를 받았을 때 발생

onfocusin
개체에 포커스가 셋팅되기 바로 직전 개체에 대해 발생

onfocusout
포커스가 다른 개체로 이동한 후에 포커스를 가고 있는 현재 개체에서 발생

onhelp
브라우저가 활성화 되어 있는 동안 F1키를 눌렀을 때

onkeydown
사용자가 키를 눌렀을 때

onkeypress
기능키를 제외한 키를 눌렀을 때 발생

onkeyup
사용자가 키를 놓았을 때 발생

onlayoutcomplete
소스 문서로 부터 콘텐드를 가지는 객체가 미리보기나 출력을 할때 현제 LayoutRect 개체를 모두 채우는 것이 끝났을 때 발생

onload
브라우저가 개체를 로드한 후에 발생

onlosecapture
개체가 마우스 캡쳐를 잃었을 때 발생

onmousedown
개체 위에 마우스 버튼을 누를 때 발생(좌우 어느 버튼이든)

onmouseenter
개체 안으로 마우스 포인터가 들어올 때 발생

onmouseleave
개체의 경계밖으로 마우스 포인터가 이동할 때 발생

onmousemove
개체위에서 마우스가 움직일 때 발생

onmouseout
개체밖으로 마우스 포인터가 빠져나갈 때 발생

onmouseover
개체위로 마우스 포인터가 들어올 때 발생

onmouseup
마우스가 개체위에 있는 동안 마우스를 누른 상태에서 해제될 때 발생

onmousewheel
마우스 휠이 돌아갈 때 발생

onmove
개체가 움직일 때 발생

onmoveend
개체가 움직임이 끝날 때 발생

onmovestart
개체가 움직이기 시작할 때 발생

onpaste
문서에 클립보드로부터 데이터가 전송될 때 타겟 개체에서 발생

onpropertychange
개체의 속성이 바뀔 때 발생

onreadystatechange
개체의 상태가 변화할 때 발생

onreset
Form 을 사용자가 리셋할 경우 발생

onresize
개체의 크기가 바뀔 때 발생

onresizeend
제어영역에서 개체의 크기가 사용자에 의해 변화가 끝날 때 발생

onresizestart
제어영역에서 개체의 크기가 사용자에 의해 변화되기 시작할 때 발생

onrowenter
데이터 소스 내에서 현재 열이 변화되거나 개체에 새로운 유용한 데이터가 입력될 때 발생

onrowexit
데이터 소스 콘트롤이 개체 내의 현재 열을  변화시킬 때 발생

onrowsdelete
레코드셋에서 열이 삭제될 때 발생

onrowsinserted
현재 레코드셋에 새로운 열이 추가된 후에 발생 (데이터 개체에서)

onscroll
사용자가 개체 내의 스크롤 바를 스크롤할 때 발생

onselect
현재 선택된 영역이 바뀔 때 발생

onselectionchange
문서의 선택 영역의 상태가 바뀔 때 발생

onselectstart
개체가 선택되기 시작할 때 발생

onstart
마퀴개체에서 루프가 매번 시작될 때 발생

onstop
사용자가 stop 버튼을 눌렀을 경우 혹은 페이지를 떠날 때 발생

onsubmit
폼이 전송되기 직전에 발생

onunload
개체가 언로드되기 직전에 발생

 

<출처 : http://topboy.tistory.com/167>

자바스크립트에서 replace 를 사용할 때 단순히

replace('a', 'b') 

라고 쓰면 맨 처음 나타나는 'a' 만을 'b' 로 단 하나만 바꾸어 준다.

하지만 정규식을 이용한다면 모든 'a' 를 'b' 로 바꾸는 것도 가능하다.

replace(/a/gi, 'b')

처럼 말이다.

여기서 a 를 바꾸어질 문자(열), b 는 바뀔 문자(열)가 되겠다.

원한다면 무엇을 바꾸어도 되겠지..

g 의 의미는: 발생할 모든 pattern에 대한 전역 검색 

i 의 의미는: 대/소문자 구분 안함 

m 의 의미는: 여러 줄 검색(여기서는 사용하지 않았다. 원한다면 하자.)

문자열을 바꾸고 싶다면 문자열을 입력해 주자.

아래 소스는 abc 라는 문자열을 def 라는 문자열로 바꾸어 주는 소스이다.

replace(/abc/gi, 'def')

더 자세한 것은 정규식(Requral Expression)을 참고하도록 하자.

테스트:

자동 replace (포커스를 벗어나면 a 를 b 로)
수동 replace (뒤에 버튼을 누르면 a 를 b 로)
소스: (아래 부분을 클릭하고 Ctrl + A 를 누르면 깔끔하게 선택 완료) <TABLE cellpadding="3" cellspacing="1" border="1"> <TR> <TD>자동 replace (포커스를 벗어나면 a 를 b 로)</TD> <TD><INPUT TYPE="text" NAME="txtAuto" value="ab aaaa bbab aa" onFocus="this.select();" onBlur="this.value = this.value.replace(/a/gi, 'b');"></TD> </TR> <TR> <TD>수동 replace (뒤에 버튼을 누르면 a 를 b 로)</TD> <TD><INPUT TYPE="text" NAME="txtManual" value="ab aaaa bbb aa" onFocus="this.select();"><INPUT TYPE="button" VALUE="변환" ONCLICK="txtManual.value = txtManual.value.replace(/a/gi, 'b');"></TD> </TR> </TABLE>

http://okjungsoo.tistory.com/entry/%ED%81%AC%EB%A1%AC-%EB%94%94%EB%B2%84%EA%B9%85%ED%95%98%EA%B8%B0

구글 크롬에도 디버깅하기 위한 여러 도구들이 포함되어 있습니다. 이 도구들은 "Control the current page > Developer"에 들어가면 볼 수 있습니다.  


  1. View source
Developer 항목에서 가장 먼저 제공되는 부분은 html 소스코드를 보여주는 항목인데요. 현재 보고 있는 페이지에서 View source를 클릭하거나 마우스 오른쪽 버튼을 눌렀을 때 나오는 컨텍스트 메뉴에서 "View page source"를 클릭해도 됩니다. 

사용자 삽입 이미지
html 페이지를 라인별로 표기해주며 script, link, a태그의 href 속성 등 외부 링크를 가지는 속성들은 클릭시 해당하는 페이지로 이동할 수 있습니다. view-source 프로토콜에서 다른 주소를 입력하여 해당 페이지의 소스코드를 볼 수도 있습니다. 
(Firefox의 WebDeveloper 플러그인의 View Generated Source와 같은 기능은 없습니다.)

  2. Debug JavaScript
크롬에서 자바스크립트 디버깅은 command 명령어로 실행됩니다. 가장 기본적으로 제공되는 help 명령어를 입력하고 엔터키를 치면 입력가능한 명령어들을 모두 보여줍니다. 사용 가능한 명령어들은 다음과 같습니다. 

  1. break [location] <condition> : break point를 설정합니다. 
    1. break functionName 을 입력하거나 break script:functionName 을 입력하여 break point를 설정할 수 있습니다. 
    2. location의 위치에는 <function> 이나 <script:function>, <script:line>, <script:line:pos>가 올 수 있습니다. 
    3. location을 보면 line 설정이 가능한 것으로 나오지만 정확한 사용방법은 잘 모르겠네요;;

  2. break_info [breakpoint #]: break 명령어를 사용해서 설정된 break point들을 보여줍니다. break_info만 입력하면 현재 설정된 모든 break point들을 보여주며 break_info 1과 같이 특정 break_point의 id를 함께 입력하면 해당하는 break point만 보여줍니다.

  3. clear [breakpoint #]: break point를 제거할 때 사용합니다. break point의 id를 함께 넣어줘야 합니다. 

  4. help [command]: 도움말을 출력합니다. help break를 입력하면 break 명령어의 상세한 내용을 출력해 줍니다. 디버깅 실행 도중에 help를 누르면 현재 상태에서 사용가능한 명령어들을 보여줍니다. (args, backtrace, continue, frame, locals, next, source, step, stepout 등의 명령어를 추가로 볼 수 있습니다.)

  5. print <expression>: 입력된 expression을 출력해줍니다. 
  6. scripts: 페이지에 추가되어 있는 모든 스크립트들의 위치를 보여줍니다. (line의 위치는 index가 0부터 시작하는 것 같습니다.)

아직은 사용하기엔 미흡한 것 같지만, 여기에 ui가 추가된다고 상상해보면 매우 기대되네요. ^^

  3. JavaScript console
JavaScript console은 "Page > Developer > JavaScript console"을 클릭하여서 창을 띄울 수도 있지만, 마우스 오른쪽 버튼을 누르면 나오는 컨텍스트 메뉴로 띄울 수도 있습니다. 

사용자 삽입 이미지

컨텍스트 메뉴로 JavaScript console 창을 띄우는 방법이 DOM inspector와 동일한 역할을 하고 있습니다. 창이 뜰 때, 가장 가까이에 있는 DOM 요소를 선택해서 보여주며 style, metrics, properties 세부 항목들도 함께 보여줍니다. (Resource interpreted as script but transferred with MIME type text/html.이라는 에러메시지와 함께 inspect되지 않는 경우도 있네요.)

사용자 삽입 이미지

console 창에서 아래 쪽의 Show console을 활성화시키면 콘솔창이 활성화 됩니다. javascript: 프로토콜이나 Firebug의 콘솔창처럼 현재 페이지에 존재하고 있는 변수나 객체에 접근이 가능하기 때문에 유용하게 쓰일 수 있습니다. 

사용자 삽입 이미지

  4. Task manager
Task manager에서는 크롬에서의 메모리 사용에 대해서 상세하게 볼 수 있는 기능을 제공하고 있습니다. 


Stats for nerds를 클릭하게 되면 좀 더 상세한 메모리 사용을 볼 수 있는데요. 재미있는 것은 익스플로러와 파이어폭스, 크롬 세가지를 모두 비교해서 보여준다는 사실입니다. (크롬의 경우에는 memory를 보여주기 위한 창이 하나 더 떠있습니다.)

N사의 메인페이지를 열었을 때 메모리 사용, 파폭에서는 플러긴 들을 모두 비활성화하였습니다.


출처: 

'Programming > Browser' 카테고리의 다른 글

Http/3은 UDP?  (0) 2020.07.08

+ Recent posts