20.9 스트림
스트림(stream)은 노드에서 중요한 개념입니다.
스트림은 스트림 형태의 데이터를 다루는 객체입니다.
스트림이란 단어는 흐름이란 느낌이 있고, 흐름은 시간이 지나면서 일어나는 일이므로 비동기적으로 이루어질 것이라는 짐작이 들 겁니다.
스트림에는 읽기(read) 스트림, 쓰기(write) 스트림, 이중(duplex) 스트림이 있습니다.
스트림의 예로 사용자의 타이핑, 클라이언트와 통신하는 웹 서비스 등을 들 수 있습니다.
파일을 읽고 쓰는 작업은 스트림을 통하지 않아도 가능하지만, 파일 작업 역시 스트림을 통할 때가 많습니다.
파일 스트림을 통해 읽기와 쓰기 스트림을 만들고, 스트림을 파이프(pipe)로 연결하는 방법을 알아봅시다.
먼저 쓰기 스트림을 만들고 거기 쓰는 것부터 시작합니다.
const fs = require('fs');
const ws = fs.createWriteStream('stream.txt', {encoding:'utf8'});
ws.write('line 1\n');
ws.write('line 2\n');
ws.end();
TIP_
end 메서드는 옵션으로 데이터 매개변수를 받을 수 있으며, 이 매개변수는 write를 호출하는 것과 동등합니다.
따라서 데이터를 단 한 번만 보낸다면, end 한 번만 호출해도 데이터를 보낼 수 있습니다.
end를 호출해서 쓰기 스트림(ws)을 종료하기 전까지는 write 메서드를 통해 스트림에 쓸 수 있습니다.
end를 호출한 다음 다시 write를 호출하면 에러가 일어납니다.
end를 호출하기 전에는 write를 여러 번 호출할 수 있으므로, 시간을 두고 데이터를 보낼 때는 쓰기 스트림이 이상적입니다.
마찬가지로 읽기 스트림을 만들어서 들어오는 데이터를 읽을 수 있습니다.
const fs = require('fs');
const rs = fs.createReadStream('stream.txt', { encoding: 'utf8' });
rs.on('data', function(data) {
console.log('>> data: ' + data.replace('\n', '\\n'));
});
rs.on('end', function(data) {
console.log('>> end');
})
이 예제에서는 줄바꿈 문자를 이스케이프한 것 외에는 다른 작업 없이 파일 컨텐츠를 콘솔에 기록하기만 했습니다.
이 두 예제를 같은 파일에서 사용할 수 있습니다.
쓰기 스트림으로 파일에 쓰는 동시에 읽기 스트림으로 그 파일에서 읽을 수 있습니다.
이중 스트림은 그리 많이 쓰이지는 않으므로 이 책에서 소개하지는 않겠습니다.
예상할 수 있겠지만 스트림에 데이터를 쓸 때는 write를 호출하면 되고, data와 end 이벤트를 주시(listen)해서 그에 맞게 대응할 수 있습니다.
데이터가 스트림을 ‘흐른다’는 표현을 보면 읽기 스트림에서 데이터를 읽는 즉시 쓰기 스트림에 쓸 수 있을 것 같습니다.
이런 작업을 파이프라고 합니다.
예를 들어 읽기 스트림과 쓰기 스트림을 파이프로 연결하면 파일 컨텐츠를 복사하는 효과가 있습니다.
const fs = require('fs');
const rs = fs.createReadStream('stream.txt');
const ws = fs.createWriteStream('stream_copy.txt');
rs.pipe(ws);
이 예제에서는 따로 인코딩을 명시하지 않았고, 그럴 필요도 없습니다.
rs는 그냥 stream.txt의 데이터를 ws에 파이프로 연결하고, ws는 그 데이터를 그대로 stream_copy.txt에 기록합니다.
인코딩은 데이터를 해석할 때만 필요합니다.
파이프는 데이터를 옮길 때도 자주 쓰입니다.
예를 들어 파일 컨텐츠를 웹 서버의 응답 부분에 파이프로 연결할 수 있습니다.
또는 압축된 데이터를 압축 해제 엔진에 파이프로 연결하고, 압축 해제 엔진은 다시 그 데이터를 파일을 기록하는 부분에 파이프로 연결하는 것도 가능합니다.