Vanilla node.js
를 이용하여 간단한 HTTP 통신을 배우고 있었다. 아니 분명 간단하다고 생각했었는데, 오히려 node.js
가 php
보다 신경쓸게 더 많아 보였다. body
로 전달받는 문자열 하나를 띄우려고 글쎄 이런 코드를 작성해야 한다.
// body 받기 코드
const http = require('http');
const PORT = 3000 ;
const server = http.createServer((req, res)=>{
let body = [];
req.on('data', (data)=>{
body.push(data);
}).on('end', ()=>{
body = Buffer.concat(body).toString();
console.log(body)
})
res.statusCode = 200;
res.end(body);
})
server.listen(PORT, ()=>{console.log("This server is opened!")});
req.on(...)
으로 데이터를 받고 있다. 그리그 그 과정 중 알게모르게 낯익은 녀석 하나가 껴있다. 바로 Buffer
이다. 그리고 Buffer
는 생각보다 존재감이 컸다. 코드에 등장하자마자 이 코드가 어려워보이기 시작했다. 나는 Buffer
와 조금 친해져보려고 한다.
버퍼를 얘기하기 전, Stream
이라는 개념도 명확이 짚고 넘어가야 한다. 사실 Stream
이라는 단어는 우리가 이미 일상적으로 잘 쓰고 있는 단어이기도 하다. 넷플릭스나, 디즈니플러스 같은 스트리밍 서비스의 스트리밍이 지금 살펴보려는 Stream
과 관련이 깊다.
컴퓨터 과학에서 말하는 Stream
은 어떤 한 데이터가 다른 지점으로 이동되는 과정, 혹은 흐름이라고 얘기할 수 있겠다. 정확히 이동되는 일련의 '데이터'들이라고 정리해보자.
4K 화질 영화를 본다고 하면 한 편에 어마어마한 크기의 데이터양을 가질 것이다. 우리가 이 영화를 스트리밍 서비스로 볼 때, 꼭 이 영화의 모든 데이터가 컴퓨터에 있어야 영화를 시청할 수 있을까? 아니다, 처음 얼마간에 데이터가 모인 이후에는 불편없이 감상할 수 있다.
Stream
은 잘깨 쪼개진 데이터의 흐름이라고 인지를 해야 한다. '잘개 쪼개진 데이터'와 '흐름'을 분리해놓고는 Stream
이라고 말할 수 없다 생각된다.
Buffer
Class는 바이너리 데이터들의 스트림을 직접 다루기 위해 node.js API 에 추가되었습니다.-- 'node.js 버퍼를 제대로 이해해보자' 중
Stream
이 이동하는 일련의 데이터들이라고 했는데, 이제는 어떻게 이동하는 지에 대해서 생각해보려고 한다. 우리가 생각하는 것처럼 데이터들이 움직이기 위해서 필요한 것이 Buffer
이다.
Buffer
는 데이터의 일시적인 저장 장소이다. 이렇게만 정의하면 메모리
와 차이가 없어 보인다. 여기에 '이동하는 데이터를 위한 별도의 일시적인 메모리의 일부 영역' 라고 하면 이제 메모리와 구별해낼 수 있겠다.
실제로 Buffer
는 작업이 느린 장치와 빠른 장치 사이에서 데이터를 주고 받을 때 사용된다. 보통 주기억 장치인 RAM은 속도가 빠르고, 보조기억 장치인 ROM은 속도가 느리기 때문에, 파일을 읽어 들어올 때도 Buffer
가 사용되겠다. 이외에 위에서 예를 든 스트리밍 서비스에서도, 우리가 감상하는 시간은 정해져있는데, 영화의 데이터는 들어오니까 버퍼에 저장되어 다음 장면들을 재생시킬 것이다. Buffer
라는 개념이 사용될 때는 양자 간에 속도 차이가 적어도 존재한다고 생각하면 될 것이다. 이 때 Buffer
가 사이즈가 크고, 많을 수록 데이터의 전송 속도는 빨라진다. 하지만 전송속도와 가용 메모리 공간은 상충한다는 걸 잊지말자.
바로 그게 Node.js에서의 Buffer입니다! Node.js는 데이터가 도착하는 시간이나 전송되는 속도를 제어할 수는 없습니다. Node.js가 결정할 수 있는건 언제 데이터를 내보내느냐 입니다. 버스를 언제 출발시킬 수 있는 제어권이 있는 것과 동일합니다. 아직 데이터를 내보낼 때가 아니면, Node.js는 데이터들을 일종의 대기영역인 RAM에 작은 영역인 buffer에 데이터를 넣어놓습니다.
일상생활에서 버퍼작동을 볼 수 있는 예로는 온라인 영상을 시청할 때 입니다. 유튜브를 보는 순간을 상상해보세요. 우리의 인터넷 연결상태가 매우 좋을때에는 영상 스트리밍이 끝날때까지 버퍼를 채우고 데이터가 처리될 수 있게 빠르게 내보내고, 다시 버퍼를 채우고 빠르게 내보내고를 반복합니다.
그러나 인터넷 연결상태가 좋지 못할때에는 첫번째 데이터셋을 처리하고 나서, 영상플레이어는 로딩 아이콘을 띠우면서 ‘buffering’이라는 텍스트를 보여줄 것입니다. 이것은 데이터가 더 모이고 도착할때 까지 기다린다는 의미입니다. 만약에 버퍼가 채워지고 데이터가 처리되면, 영상이 다시 보여지게 될 것입니다. 영상을 보여주는 동안에도, 계속해서 다음 데이터가 도착하고, 버퍼에 채워질 것입니다.
-- 'node.js 버퍼를 제대로 이해해보자' 중
node.js
에서 Buffer
는 내장함수다. 어디서든 불러올 수 있다.
const buf = Buffer.from("너의 그 한 마디 말도");
console.log(buf);
// <Buffer eb 84 88 ec 9d 98 20 ea b7 b8 20 ed 95 9c 20 eb a7 88 eb 94 94 20 eb a7 90 eb 8f 84>
console.log(buf.toString());
// 너의 그 한 마디 말도
console.log(buf.toString("hex"));
// eb8488ec9d9820eab7b820ed959c20eba788eb949420eba790eb8f84
console.log(buf.toString("base64"));
// 64SI7J2YIOq3uCDtlZwg66eI65SUIOunkOuPhA==
buf
를 출력해보니 16진수로 데이터가 저장되고 있는 것을 확인할 수 있다. 이것조차도 사실 사람의 편의를 위해서 쓰인 것이고, 실상 2진수로 저장이 되어 있을 것이다. 따로 지정을 안한다면 Buffer
는 utf-8
로 문자열을 인코딩해서 저장한다. 인코딩이나 디코딩을 다른 체계로 하고 싶다면 따로 지정해 주는 방법이 있으니 참고하면 좋을 것이다.
cf.
utf-8
은 가변 인코딩 방식을 채택한다. 글자마다 사용하는 데이터 양이 다르다는 것이다. 맨 처음 쓰인 바이트eb
를 풀어 쓰면11101011
이다. 그 중 앞의 '1'이 연속 3개로 끝나니까 한글 '너'를 표현하기 위해 3 바이트를 쓰고 있다는 것을 알 수 있다. 참고로 영어 대소문자는UTF-8
로 1 바이트를 사용한다.
그럼, 다시 제일 위에 쓴 예시를 살펴보자. 콘솔에 띄우는 명령을 하기 전, 데이터를 우리가 알아볼 수 있는 문자열로 변환하는 작업을 주석처리하고 서버를 실행해보겠다. 그리고 이 서버에 똑같이 "너의의 그 한 마디 말도"라고 평문을 body
에 넣어 request
를 보냈다. 결과는 다음과 같이 나온다.
이를 통해서 node.js
http
모듈의 reqest
가 데이터를 Buffer
의 형식으로 가지고 오고 있다는 것을 알 수 있다.
정리해보자면 node.js
에서의 Buffer
클래스는 바이너리 데이터들을 저장, 처리, 가공하기 위한 인터페이스를 제공한다고 생각하면 되겠다.