Node.js - Streams: 초보자 가이드

안녕하세요, 미래의 Node.js 마법사 여러분! 오늘 우리는 Node.js의 가장 강력하고 흥미로운 기능 중 하나인 Streams에 대해 깊이 알아보겠습니다. 프로그래밍에 새로운 사람이라고 걱정하지 마세요; 저는 수년간 수많은 학생들을 가르치면서 이 여정을 단계별로 안내해 드릴 것입니다. 그麼, 좋아하는 음료를 한 잔 들고 편안하게 앉아, 이 흥미로운 모험을 함께 시작해 보세요!

Node.js - Streams

Streams는 무엇인가요?

한 개의 큰 탱크에서 다른 큰 탱크로 물을 옮기는 상상해 보세요. 두 가지 선택이 있습니다:

  1. 전체 탱크의 물을 한 번에 들고 가는 것 (이는 매우 무겨우고 비 thực질적입니다).
  2. 파이프를 사용하여 물을 조금씩 옮기는 것.

Node.js의 세상에서 Streams는 그 파이프와 같습니다. Streams를 사용하면 메모리에 전체 데이터를 로드하지 않고 조각별로 데이터를 처리하고 처리할 수 있습니다. 이는 대량의 데이터를 다루거나 데이터가 완전히 사용 가능하기 전에 처리를 시작하고 싶을 때 특히 유용합니다.

Streams를 사용하는 이유는 무엇인가요?

  1. 메모리 효율성: Streams는 데이터를 작은 조각으로 처리하기 때문에, 모든 것을 한 번에 메모리에 로드할 필요가 없습니다.
  2. 시간 효율성: 첫 번째 조각을 가지고 나서 바로 데이터를 처리할 수 있기 때문에, 모든 데이터가 사용 가능하다고 기다릴 필요가 없습니다.
  3. 조합성: Streams를 쉽게 연결하여 강력한 데이터 처리 파이프라인을 만들 수 있습니다.

이를 더 잘 이해하기 위해 간단한 예제를 보겠습니다:

const fs = require('fs');

// Streams를 사용하지 않는 경우
fs.readFile('bigfile.txt', (err, data) => {
if (err) throw err;
console.log(data);
});

// Streams를 사용하는 경우
const readStream = fs.createReadStream('bigfile.txt');
readStream.on('data', (chunk) => {
console.log(chunk);
});

첫 번째 접근 방식에서는 전체 파일을 한 번에 읽습니다. 파일이 매우 크다면 이는 많은 메모리를 사용할 수 있습니다. 두 번째 접근 방식에서는 Streams를 사용하여 파일을 조각별로 읽어 메모리 효율성이 높습니다.

Streams의 종류

이제 Streams가 무엇인지 이해했으므로, Node.js에서 다양한 종류의 Streams를 탐구해 보겠습니다. 이는 다양한 목적을 위한 다양한 종류의 파이프를 배우는 것과 같습니다!

1. readable Streams

Readable Streams는 데이터의 원천입니다. 파일이나 HTTP 요청과 같은 원천에서 데이터를 읽을 수 있습니다.

다음은 readable Streams를 생성하고 사용하는 예제입니다:

const fs = require('fs');

const readStream = fs.createReadStream('example.txt', 'utf8');

readStream.on('data', (chunk) => {
console.log('Received chunk:', chunk);
});

readStream.on('end', () => {
console.log('Finished reading the file');
});

이 예제에서는 'example.txt' 파일에서 readable Streams를 생성합니다. Stream은 읽은 각 조각에 대해 'data' 이벤트를 발생시키고, 끝낼 때 'end' 이벤트를 발생시킵니다.

2. writable Streams

Writable Streams는 데이터의 목적지입니다. 파일이나 HTTP 응답과 같은 목적지에 데이터를 쓸 수 있습니다.

다음은 writable Streams를 생성하고 사용하는 예제입니다:

const fs = require('fs');

const writeStream = fs.createWriteStream('output.txt');

writeStream.write('Hello, ');
writeStream.write('Streams!');
writeStream.end();

writeStream.on('finish', () => {
console.log('Finished writing to the file');
});

이 예제에서는 'output.txt' 파일에 writable Streams를 생성합니다. Stream에 데이터를 쓰고 끝냅니다. 'finish' 이벤트는 모든 데이터가 기록된 후 발생합니다.

3. Duplex Streams

Duplex Streams는 readable과 writable 모두입니다. 데이터가 양방향으로 흐를 수 있는 두 방향의 파이프라고 상상해 보세요.

Duplex Streams의 좋은 예는 TCP 소켓입니다:

const net = require('net');

const server = net.createServer((socket) => {
socket.write('Welcome to our server!\n');

socket.on('data', (data) => {
console.log('Received:', data.toString());
socket.write('You said: ' + data);
});
});

server.listen(3000, () => {
console.log('Server listening on port 3000');
});

이 예제에서 socket은 Duplex Streams입니다. 우리는 그것에 쓰고 읽을 수 있습니다.

4. Transform Streams

Transform Streams는 입력을 기반으로 출력을 계산하는 특별한 종류의 Duplex Streams입니다. 입력이 변하는 마법의 파이프라고 상상해 보세요!

다음은 입력 텍스트를 대문자로 변환하는 Transform Streams의 예제입니다:

const { Transform } = require('stream');

const upperCaseTransform = new Transform({
transform(chunk, encoding, callback) {
this.push(chunk.toString().toUpperCase());
callback();
}
});

process.stdin.pipe(upperCaseTransform).pipe(process.stdout);

이 예제에서는 대문자로 변환하는 Transform Streams를 생성합니다. 표준 입력을 이 Transform Streams를 통해 표준 출력으로 전달합니다. 이 스크립트를 실행하고 텍스트를 입력해 보세요 - 대문자로 나타납니다!

Streams 메서드와 이벤트

Streams를 효과적으로 사용하려면 그들의 메서드와 이벤트를 이해하는 것이 중요합니다. 다음은 요약입니다:

Stream Type Common Methods Common Events
Readable pipe(), read(), pause(), resume() data, end, error, close
Writable write(), end() drain, finish, error, close
Duplex pipe(), read(), write(), end() data, end, error, close, drain, finish
Transform pipe(), read(), write(), end() data, end, error, close, drain, finish

Streams를 연결하는 방법

Streams의 가장 강력한 기능 중 하나는 그들을 연결하여 복잡한 데이터 처리 파이프라인을 만들 수 있다는 것입니다.

다음은 파일을 읽고 압축하여 새 파일에 쓰는 예제입니다:

const fs = require('fs');
const zlib = require('zlib');

const readStream = fs.createReadStream('input.txt');
const writeStream = fs.createWriteStream('output.txt.gz');
const gzip = zlib.createGzip();

readStream.pipe(gzip).pipe(writeStream);

writeStream.on('finish', () => {
console.log('File successfully compressed');
});

이 예제에서는 readable Stream을 gzip Transform Stream을 통해 writable Stream으로 연결합니다. 이는 특정 목표를 달성하기 위해 다양한 종류의 파이프를 연결하는 것과 같습니다!

결론

축하합니다! Node.js Streams의 fascinatie 세계로 첫 걸음을 냈습니다. 우리는 Streams가 무엇인지, 왜 유용한지, 다양한 Streams 종류와 사용 방법을 다루었습니다. Streams는 메모리와 시간을 효율적으로 처리하고, 확장 가능한 애플리케이션을 만드는 강력한 도구입니다.

Node.js의 여정을 계속하면서 Streams를 자주 마주치게 될 것입니다. 네트워크 통신에서 파일 연산까지 어디서나 나타납니다. Streams를 프로젝트에서 실험해 보지 마세요. 어떤 기술이든 연습을 통해 더 쉬워집니다.

계속 코딩하고, 배우고, 가장 중요한 것은 즐기세요! 누구도 Node.js Streams의 마법에 대해 가르치는 사람이 될지도 모릅니다. 다음에 다시 만날 때까지, 즐거운 Streaming을 기원합니다!

Credits: Image by storyset