2023. 1. 31. 22:12ㆍ프로그래밍/JavaScript
- 목차
Node.js 작업 처리
Node.js 가 V8과 libuv 를 바인딩 시켜줌
- V8: 자바스크립트 코드를 실행해 줌
- libuv: 자바스크립트 코드를 읽는 것 이외의 것들을 처리함 (DB접근, 파일 읽기 등)
libuv
유니콘 벨로시랩터 라이브러리
이벤트 루프를 기반으로 하는 비동기 I/O에 대한 지원을 제공하는 다중 플랫폼 C 라이브러리
파일 시스템, DNS, 네트워크, 파이프, 신호 처리, 폴링/스트리밍을 처리하는 메커니즘 제공
원래 unix와 window는 파일 컨트롤 방법이 다르지만 libuv가 서로 호환 가능하게 해주기 때문에
Node.js를 사용하면 unix, window에서 동일한 방법으로 사용 가능
동기 & 비동기
동기 (Synchronous)
console.log('1');
console.log('2');
// 1
// 2
이전의 일이 끝나야 다음 일을 할 수 있음
자바스크립트는 동기 언어 (한 줄씩 실행)
비동기 코드를 작성하기 위해 자바스크립트 이외의 브라우저/Node api를 사용함
비동기 (Asynchronous)
setTimeout(() => {
console.log('1');
}, 1000);
console.log('2');
// 2
// 1
동시에 여러 가지 일을 할 수 있음
Node.js 에서 주로 사용함
데이터베이스에서 데이터를 읽을 때, 저장할 때 등 대부분의 요청이 비동기로 이루어짐
대부분의 작업이 어느 정도의 시간을 필요로 하기 때문
비동기를 사용하면 다른 작업을 기다리지 않고 여러 개의 작업을 처리할 수 있음
Blocking
Node.js 프로세스에서 자바스크립트 작업을 위해서
자바스크립트가 아닌 작업이 완료될 때 까지 기다려야 하는 경우
Blocking 함수: JSON.stringify, window.alert
Node.js Blocking 메서드
const fs = require('fs');
const data = fs.readFileSync('/file.md');
console.log(data);
moreWork(); // console.log 이후에 실행됨
파일을 읽어오는 동안 전체 프로세스가 멈추고 데이터를 가져온 후에 진행됨 (동기식 처리)
Node.js Non-Blocking 메서드
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data);
});
moreWork(); // console.log 이전에 실행됨
파일을 읽어오는 동안 전체 프로세스가 멈추지 않고 다음으로 넘어감 (비동기식 처리)
Blocking 코드와 Non-Blocking 코드를 함께 사용할 때 발생할 수 있는 문제
const fs = require('fs');
fs.readFile('file.md', (err, data) => {
if (err) throw err;
console.log(data);
});
fs.unlinkSync('/file.md');
fs.readFile(비동기식)보다 fs.unlinkSync(동기식)가 먼저 실행될 가능성이 높음
파일을 읽기 전에 파일 지우기가 먼저 실행됨
const fs = require('fs');
fs.readFile('/file.md', (readFileErr, data) => {
if (readFileErr) throw readFileErr;
console.log(data);
fs.unlink('/file.md', (unlinkErr) => {
if (unlinkErr) throw unlinkErr;
});
});
fs.readFile의 콜백 안에서 fs.unlink를 호출하여 올바른 작업 순서 보장 가능
프로세스 & 스레드
프로세스 (Process)
어떤한 일을 하고 있는 상태
프로세서가 프로세스를 하나씩 빠르게 처리해서 동시에 모든 프로세스를 처리하는 것처럼 보이는 동시성과
여러 개의 프로세서가 여러 개의 프로세스를 동시에 처리하는 병렬성을 같이 이용해서 처리함
실행파일 클릭 시 메모리(RAM) 할당이 이루어지고 이때부터 해당 프로그램은 프로세스라고 불리게 됨
스레드 (Thread)
프로세스 내에서 일을 처리하는 세부적인 실행 단위
한 프로세스 내에서 여러 가지 작업을 동시에 처리하기 위해 이용함
싱글스레드
하나의 프로세스에서 하나의 스레드를 실행함
프로세스 내의 작업을 순차적으로 실행함
작업 처리가 늦어질 수 있음
멀티스레드
하나의 프로세스에서 여러 개의 스레드를 실행함
각 스레드가 다른 작업을 할당받아서 프로세스가 병렬적으로 여러 작업을 동시에 수행할 수 있음
멀티 스레딩의 단점
공유 자원에 동시 접근 시 신경을 써주어야 함
병목 현상으로 인한 성능 저하 가능성
Node.js 비동기 작업 처리
자바스크립트는 싱글 스레드이며 Node.js 는 자바스크립트 언어를 사용함
Node.js는 비동기 작업 처리를 위해 libuv 에서 제공하는 Event Loop를 이용함
Event Loop
Node.js가 여러 비동기 작업을 관리하기 위한 구현체
비동기 작업들을 모아서 관리하고 순서대로 실행할 수 있게 해주는 도구
Event Loop 단계
1. Timer
setTimeout(), setInterval()에 의해 예약된 콜백을 실행함
2. Pending Callbacks
TCP 오류 유형과 같은 일부 시스템 작업에 대한 콜백을 실행함
3. Idle, Prepare
내부적으로만 사용됨
이 단계에서 이벤트 루프는 아무 작업도 수행하지 않음
Idle 상태이며 Poll 단계로 이동할 준비를 함
4. Poll
대부분의 I/O 관련 콜백을 실행함
(close 콜백, 타이머에 의해 예약된 콜백, setImmediate() 를 제외한 모두)
5. Check
setImmediate() 콜백을 호출함
6. Close Callbacks
종료 이벤트와 관련된 콜백을 실행함
(socket.on('close', fn), process.exit() 등)
Event Emitter
Node.js의 Event 모듈은 이벤트를 처리하는 데 사용되는 EventEmitter 클래스를 제공함
- on(): 이벤트 발생 시 콜백 함수를 실행함
- emit(): 이벤트를 발생시킴
const EventEmitter = require('events');
// 이벤트 처리에 사용되는 EventEmitter 클래스
const celebrity = new EventEmitter();
// update post 이벤트 발생 시 콜백 함수 실행
celebrity.on('update post', (type) => {
console.log(`This ${type} post is good`);
});
celebrity.on('update post', () => {
console.log('This post is awesome');
});
// celebrity가 update post 이벤트를 발생시킴
celebrity.emit('update post', 'image');
Node.js 모듈
1. Core Module
Node.js에서 기본적으로 제공하는 모듈
- http: Node.js http 서버를 생성하기 위한 클래스, 메소드, 이벤트 포함
- url: URL 확인 및 구문 분석을 위한 메서드 포함
- querystring: 쿼리 문자열을 처리하는 메서드 포함
- path: 파일 경로를 처리하는 메서드 포함
- fs: 파일 I/O 작업을 위한 클래스, 메서드, 이벤트 포함
- util: 프로그래머에세 유용한 유틸리티 기능 포함
2. Local Module
Node.js 애플리케이션에서 로컬로 생성된 모듈
별도의 파일과 폴더에 애플리케이션의 다양한 기능을 포함함
3. Third Party Module
NPM을 사용하여 온라인에서 사용할 수 있는 모듈
프로젝트 폴더에 설치하거나 전역적으로 설치 가능
(mongoose, express ...)
모듈을 사용하는 이유
- 존재하는 코드를 재사용할 수 있음
- 관계가 있는 코드끼리 모아 놓아 코드를 정리할 수 있음
- 관계없는 자세한 부분은 숨기고 직접 사용되는 코드만 가져와서 보여줄 수 있음
(해당 모듈 전체를 가져오는게 아니라 특정 함수/변수/클래스만 가져와서 사용 가능)
모듈 캐싱
모듈에서 다른 모듈을 가져올 때 해당 모듈을 캐싱하는 것 (ECMAScript / CommonJS 모듈 모두)
require 시 생성한 인스턴스 객체를 저장해둔 후, 해당 객체를 계속 사용함
CommonJS 와 ECMAScript 모듈의 차이
CommonJS Module
Node.js 에서 기본 모듈로 사용됨
- 내보내기: module.exports
- 가져오기: require
ECMAScript Module
JavaScript 표준
Node.js 13.2.0 버전부터 지원함
모든 주요 브라우저가 지원함
- 내보내기: export
- 가져오기: import
Node.js 에서 ECMAScript 모듈 이용하기
- 파일 확장자를 .mjs 로 변경
- require 를 import 로 변경 (파일 확장자 이름까지 써주어야 함)
- module.exports 를 export 로 변경
웹 서버
텍스트, 이미지 등의 웹 사이트 컨텐츠를 요청하는 클라이언트(브라우저)에 전달함
HTTP를 사용하여 웹 브라우저와 통신함
동적 컨텐츠를 제공하기 위해 스크립팅 언어를 지원함
- 브라우저에서 HTTP로 웹 서버에 요청을 보냄
- 웹 서버에서 JSON, XML, TEXT 등의 데이터를 브라우저에 보냄
웹 서버 생성
// http built-in 모듈 가져오기
const http = require('http');
// 3000번 포트 이용
const PORT = 3000;
const server = http.createServer((req, res) => {
// writeHead는 한 번만 호출되어야 하며 end()가 호출되기 전에 호출되어야 함
// 상태 코드와 응답 헤더를 클라이언트에 보냄
res.writeHead(200, {
'Content-Type': 'text/plain'
});
// 데이터가 로드되었음을 서버에 알림
res.end('Hello');
// res.statusCode = 200;
// res.setHeader('Content-Type', 'text/html');
// res.end('<h1>Hello, World!</h1>');
});
// 서버는 지정된 포트 3000에서 수신 대기하도록 설정됨
// 서버가 준비되면 수신 콜백 함수가 호출됨
server.listen(PORT () => {
console.log(`Listening on port ${PORT}...`);
});
CreateServer 메서드
server 객체 생성
server 객체는 EventEmitter 기반 ==> server.on('request', 콜백 함수);
server 객체
컴퓨터의 포트를 수신함
요청이 만들어질 때 마다 requestListener 함수 실행 가능
server.listen() ==> 서버 실행
server.close() ==> 서버 종료
RequestListener 함수
서버가 요청을 받을 때마다 호출됨
사용자의 요청과 사용자에 대한 응답을 처리함
request 객체
request, response 객체는 노드가 전달해줌
request 객체는 IncomingMessage의 인스턴스임
IncomingMessage 객체는 서버에 대한 요청을 나타냄
response 객체
ServerResponse 객체는 requestListener 함수의 두 번째 매개변수로 전달됨
클라이언트에 웹 페이지를 제공하기 위해 사용함
클라이언트로 JavaScript Object 보내기
const server = http.createServer((req, res) => {
res.writeHead(200, {
// Content Type 변경
'Content-Type': 'application/json'
});
// res.end에는 문자열을 넣어주어야 함
res.end(JSON.stringify({
a: "a",
b: "b"
}));
});
HTTP Routing
const http = require('http');
let dataObject = {
a: 'a',
b: 'b',
};
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/home') {
req.on('data', (data) => {
console.log(data.toString());
const stringifiedData = data.toString();
// body 에 넣어 보낸 데이터가 dataObject와 병합됨
Object.assign(dataObject, JSON.parse(stringifiedData));
});
} else {
if (req.url === '/home') {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(dataObject));
} else if (req.url === '/about') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.write('<html>');
res.write('<body>');
res.write('<h1>About Page</h1>');
res.write('</body>');
res.write('</html>');
res.end();
} else {
res.statusCode = 404;
res.end();
}
}
});
server.listen(3000, () => {
console.log('서버 실행');
});