Web

[NodeJS] Node.js 동작 방식

elisom 2024. 2. 16. (Last updated:

 

 

 

Node.js 동작 방식

https://www.appinessworld.com/blogs/serverside-development-with-nodejs/

자바스크립트 엔진인 V8과 libuv의 조합으로 구성되어 있다.

Node.js API에는 FileSystem, Crypto, HTTP 등 여러 API를 가지고 있다. 이 API들은 C++이나 C언어로 작성되어 있는 것도 있어 Javascript에서 처리하기 위해서는 Node.js Binding을 통해 처리한다.

V8에서 자바스크립트 작업을 진행하고, 파일 시스템 같은 처리가 필요할 때 Node.js API를 호출한다.

 

Example

Node.js가 어떻게 작업을 처리하는지 다운로드를 예로 한 번 보자.

1. Javascript로 다운로드 요청을 보낸다.

2. Node.js의 http module api를 사용해야 한다.

3. Node.js Bidning을 통하여 libuv로 할 일을 건넨다.

4. libuv를 통해 파일을 다운로드한다. 이 작업은 비동기 input/output task를 처리하도록 되어 있기 때문에 Javascript도 동시에 다른 일을 할 수 있다.

 - Windows, Mac, Linux 등 파일 시스템 처리 등의 방법이 모두 다 다른데, 이를 libuv에서 핸들링해준다.

 

다시 정리하자면, V8이 Javascript 코드를 해석하며 Node.js APIs 중 하나의 함수를 호출하고, Node.js Binding을 통해 libuv에 원하는 작업을 처리하게 한다.

 

V8

Javascript 엔진으로 Javascript 코드를 실행하는 소프트웨어 구성 요소

일반적으로 Javascript 엔진은 웹 브라우저 공급업체에서 개발함.

그중 V8은 Google Chrome에서 개발되어 가장 많이 사용되는 Javascript 엔진임.

 

libuv

이벤트 루프를 기반으로 하는 Asynchronus I/O에 대한 지원하는 다중 플랫폼 C 라이브러리.

libuv를 사용하면 여러 플랫폼(Windows, Mac, Linux .. )의 가장 빠른 비동기 IO 인터페이스로 통일된 코드를 돌릴 수 있다는 장점이 있음.

 

오픈 소스 코드를 통해 확인하기

Node.js Code

우선 Node.js의 오픈 소스 코드는 https://github.com/nodejs/node에서 확인할 수 있다.

 

GitHub - nodejs/node: Node.js JavaScript runtime ✨🐢🚀✨

Node.js JavaScript runtime ✨🐢🚀✨. Contribute to nodejs/node development by creating an account on GitHub.

github.com

 

위의 디렉터리들 중 lib과 src의 소스 코드를 볼 것이다.

lib 폴더의 코드들은 Node.js API의 자바스크립트 부분이고,

scr 폴더의 코드들은 Node.js Binding의 C++ 부분으로 Javascript와 C++을 연결해준다.

 

libuv Code

libuv 오픈소스 코드는 https://github.com/libuv/libuv에서 확인할 수 있다.

 

GitHub - libuv/libuv: Cross-platform asynchronous I/O

Cross-platform asynchronous I/O. Contribute to libuv/libuv development by creating an account on GitHub.

github.com

여기에서는 src 폴더를 볼 건데, 이 안에는 unix와 win 폴더가 있다.

unix 폴더에서는 Mac과 Linux에 대한 처리가, win 폴더에서는 Windows에 대한 처리가 이루어질 것이다.

 

Example

fs.open()으로 파일을 여는 과정을 보자.

1. /lib/fs.jsopen 함수를 보면 Node.js Binding을 사용하는 것을 확인할 수 있다. 

function open(path, flags, mode, callback) {
  path = getValidatedPath(path);
  if (arguments.length < 3) {
    callback = flags;
    flags = 'r';
    mode = 0o666;
  } else if (typeof mode === 'function') {
    callback = mode;
    mode = 0o666;
  } else {
    mode = parseFileMode(mode, 'mode', 0o666);
  }
  const flagsNumber = stringToFlags(flags);
  callback = makeCallback(callback);

  const req = new FSReqCallback();
  req.oncomplete = callback;

  binding.open(pathModule.toNamespacedPath(path),
               flagsNumber,
               mode,
               req);
}

2. /src/node_file.ccOpen 함수를 보면은 uv_fs_open으로 libuv 함수를 호출하는 것을 확인할 수 있다.

static void Open(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  const int argc = args.Length();
  CHECK_GE(argc, 3);

  BufferValue path(env->isolate(), args[0]);
  CHECK_NOT_NULL(*path);

  CHECK(args[1]->IsInt32());
  const int flags = args[1].As<Int32>()->Value();

  CHECK(args[2]->IsInt32());
  const int mode = args[2].As<Int32>()->Value();

  if (CheckOpenPermissions(env, path, flags).IsNothing()) return;

  if (argc > 3) {  // open(path, flags, mode, req)
    FSReqBase* req_wrap_async = GetReqWrap(args, 3);
    CHECK_NOT_NULL(req_wrap_async);
    req_wrap_async->set_is_plain_open(true);
    FS_ASYNC_TRACE_BEGIN1(
        UV_FS_OPEN, req_wrap_async, "path", TRACE_STR_COPY(*path))
    AsyncCall(env, req_wrap_async, args, "open", UTF8, AfterInteger,
              uv_fs_open, *path, flags, mode);
  } else {  // open(path, flags, mode)
    FSReqWrapSync req_wrap_sync("open", *path);
    FS_SYNC_TRACE_BEGIN(open);
    int result = SyncCallAndThrowOnError(
        env, &req_wrap_sync, uv_fs_open, *path, flags, mode);
    FS_SYNC_TRACE_END(open);
    if (is_uv_error(result)) return;
    env->AddUnmanagedFd(result);
    args.GetReturnValue().Set(result);
  }
}

3. /src/unix/fs.cuv__fs_open 함수를 보면 파일 오픈 동작을 확인할 수 있다. (unix)

4. /src/win/fs.cuv_fs_open 함수를 보면 파일 오픈 동작을 확인할 수 있다. (win)