사전 바이트 스트림
개념

바이트 스트림

gabury1

바이트 스트림은 주고받는 데이터를 끊김 없이 이어지는 바이트 한 줄로 보여 줍니다. 보낸 쪽이 몇 번에 나눠 썼는지는 받는 쪽에 전해지지 않습니다. 바이트가 놓인 순서만 지켜집니다. 어디까지가 한 덩어리인지는 읽는 쪽이 스스로 정해야 합니다.

쉽고 빠른 이해

바이트 스트림은 데이터를 한 줄로 이어진 바이트로 읽게 해 줍니다. 파일을 앞에서부터 한 바이트씩 읽어 나가는 것이 그렇습니다.

이게 없으면 보내는 쪽과 받는 쪽이 한 번에 주고받을 크기를 미리 맞춰야 합니다. 길이를 아직 모르는 데이터는 그 약속만으로 나를 수 없습니다.

바이트 스트림은 이렇게 돕습니다:

  1. 쓴 것을 순서대로 이어 붙여 한 줄로 만듭니다
  2. 읽는 쪽은 원하는 만큼만 떼어 갑니다
  3. 떼어 가지 않은 바이트는 줄에 남아 다음 읽기를 기다립니다

대가는 덩어리의 경계가 사라진다는 것입니다. 어디서 끊어 읽을지를 따로 정하지 않으면 두 덩어리가 붙어서 읽힙니다.

상세

바이트 스트림(byte stream)은 데이터를 길이가 미리 정해지지 않은 바이트 줄 하나로 다루는 방식입니다. 한쪽이 줄 뒤에 바이트를 이어 붙이면 다른 쪽이 앞에서부터 떼어 갑니다. 파일을 처음부터 끝까지 읽어 나가는 것, 두 프로그램을 파이프로 이어 한쪽의 출력을 다른 쪽에 흘려보내는 것, TCP(Transmission Control Protocol, 전송 제어 프로토콜) 연결로 웹 페이지를 받아 오는 것이 모두 바이트 스트림입니다.

바이트를 써 넣는 쪽이 보내는 쪽입니다. 읽어 가는 쪽이 받는 쪽입니다.

수도꼭지에서 물을 받을 때 컵을 언제 바꿔 대든 관 안의 물은 이어져 흐릅니다. 한 컵과 다음 컵을 가르는 선은 받는 사람이 정하는 것이지 물에 그어져 있는 것이 아닙니다.

입출력 라이브러리가 말하는 바이트 스트림도 같은 뜻입니다. 아래는 프로그램끼리 데이터를 주고받는 연결을 놓고 이야기합니다.

지키는 것과 지키지 않는 것

바이트 스트림이 무엇을 약속하고 무엇을 약속하지 않는지는 이렇게 갈립니다.

약속 항목 바이트 스트림에서
순서 보낸 순서대로 도착합니다
내용 바이트 값이 바뀌지 않습니다
덩어리 경계 남지 않습니다
한 번에 읽히는 양 정해져 있지 않습니다

표의 마지막 두 줄이 처음 쓰는 사람을 놀라게 합니다. 쓴 횟수와 읽은 횟수가 맞아떨어질 이유가 없습니다.

flowchart TD
    W["쓰기 세 번 · AB · CD · EF"] --> S["이어 붙은 바이트 줄 · ABCDEF"]
    S --> R["읽기 두 번 · ABCD · EF"]

그림에서 받는 쪽은 여섯 바이트를 넷과 둘로 갈라 읽었습니다. 보내는 쪽이 두 바이트씩 세 번 썼다는 것은 어디에도 남지 않았습니다.

왜 경계를 없앴나

경계를 남기려면 보내는 쪽이 쓴 덩어리를 손대지 않고 그대로 날라야 합니다. 그런데 응용 아래에서 실제로 바이트를 실어 나르는 쪽(전송 계층)은 한 번에 나를 수 있는 크기가 경로 사정에 따라 달라집니다. 덩어리가 그 크기보다 크면 어차피 쪼개야 합니다.

바이트 스트림은 이 사정을 응용 프로그램에게 숨깁니다. 응용은 한 번에 얼마나 써도 되는지 재지 않습니다. 쪼개는 일은 전송 계층이 합니다.

flowchart TD
    subgraph 응용["응용 · 한 번 씀"]
        A1["큰 덩어리 하나"]
    end
    subgraph 전송["전송 계층 · 쪼개서 나름"]
        B1["조각"]
        B2["조각"]
        B3["나머지 조각들"]
    end
    subgraph 받는응용["받는 응용 · 다시 한 줄"]
        C1["이어 붙은 바이트 줄"]
    end
    A1 --> B1
    A1 --> B2
    A1 --> B3
    B1 --> C1
    B2 --> C1
    B3 --> C1

한 번 쓴 큰 덩어리는 전송 계층에서 조각 여럿이 됩니다. 받는 응용에는 다시 이어진 한 줄로 보입니다.

길이를 아직 모르는 데이터도 바로 흘려보낼 수 있습니다. 파일을 복사하거나 로그를 내보낼 때 전체 길이를 먼저 셀 필요가 없습니다.

대신 잃는 것이 덩어리의 경계입니다. 덩어리 단위로 주고받아야 하는 프로그램은 그 경계를 스스로 다시 세워야 합니다.

경계를 다시 세우는 세 가지 방법

덩어리를 주고받으려면 어디서 끊어 읽을지를 읽는 프로그램이 알아야 합니다. 방법은 크게 셋입니다.

방법 어떻게 끊나 약점
길이 접두 덩어리 앞에 길이를 먼저 적습니다 길이를 다 센 뒤에야 쓸 수 있습니다
구분자 약속한 바이트가 나오면 끊습니다 내용에 그 바이트가 들어가면 표기를 따로 정해야 합니다
고정 길이 덩어리 크기를 미리 못 박습니다 크기가 다른 데이터를 못 담습니다

셋은 같은 바이트 줄을 서로 다른 데서 자릅니다. 여섯 바이트짜리 덩어리 하나와 그 뒤에 붙어 온 다음 덩어리를 방법마다 이렇게 가릅니다.

block-beta
columns 5
  m1["길이 접두"]:1 a1["길이 · 6"]:1 a2["본문 · ABCDEF"]:2 a3["다음 덩어리의 길이"]:1
  m2["구분자"]:1 b1["본문 · ABCDEF"]:2 b2["구분 바이트"]:1 b3["다음 본문"]:1
  m3["고정 길이"]:1 c1["6바이트 · ABCDEF"]:2 c2["다음 6바이트"]:2

왼쪽 칸은 방법 이름입니다. 오른쪽이 한 줄로 이어진 바이트입니다. 세 줄 모두 같은 여섯 바이트를 싣고 있습니다. 끊기는 데만 다릅니다.

길이 접두를 쓰면 읽는 프로그램이 두 번 읽습니다. 앞의 네 바이트에서 길이를 읽습니다. 그다음 그 길이만큼만 다시 읽습니다.

길이 = read(4)       // 6
본문 = read(길이)    // "ABCDEF"

두 번째 읽기가 여섯 바이트에서 멈췄으므로 뒤에 붙어 온 다음 덩어리는 건드리지 않습니다.

HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜)는 두 방법을 같이 씁니다. 요청과 응답 앞머리에 붙는 정보 묶음인 헤더는 빈 줄이 나올 때까지 읽습니다. 그 뒤에 오는 본문은 헤더에 적힌 길이만큼 읽습니다.

언제 이 방식이 맞고 언제 안 맞나

길이를 아직 모르고 앞에서부터 차례로 처리하는 데이터에 맞습니다. TCP 연결로 웹 페이지를 받아 오는 일, 두 프로그램을 파이프로 이어 로그를 넘기는 일이 그렇습니다. 이런 데이터는 중간에 끊어 읽어도 손해가 없습니다. 오히려 다 모이기 전에 앞부분부터 처리할 수 있습니다.

한 덩어리가 통째로 도착해야 뜻이 서는 데이터에는 안 맞습니다. 센서가 잰 값 한 벌이나 게임의 위치 갱신 한 건은 반쪽만 읽으면 쓸 데가 없습니다. 그럴 때는 덩어리를 그 모양대로 나르는 방식을 고릅니다.

덩어리 하나를 한 번에 싣고 그 모양 그대로 도착하는 전달 단위를 데이터그램이라고 합니다. UDP(User Datagram Protocol, 사용자 데이터그램 프로토콜)로 보낸 데이터그램은 한 번 보낸 것이 한 번에 읽힙니다. 대신 도중에 빠져도 다시 보내 주지 않습니다.

실무에서는 둘을 섞어 씁니다. 바이트 스트림 위에 경계를 다시 세운 프로토콜을 얹으면 빠짐없이 순서대로 도착한다는 약속과 덩어리 단위 주고받기를 같이 얻습니다.

관련 항목

바이트 스트림을 실어 나르는 프로토콜

TCP · QUIC · TLS · 전송 계층

바이트 스트림과 맞세워지는 전달 방식

데이터그램 · 메시지 · UDP · 메시지 경계

스트림 위에 덩어리 경계를 다시 세우는 방법

길이 접두 · 구분자 · 프레이밍 · 청크 전송 인코딩 · HTTP

바이트 스트림이 쪼개져 실리는 전송 단위

세그먼트 · 패킷 · 프레임 · 옥텟 · MTU

바이트 스트림을 읽고 쓰는 창구

소켓 · 파일 디스크립터 · 파이프 · 표준 입출력 · 버퍼 · 논블로킹 입출력

바이트를 뜻으로 되돌릴 때 걸리는 규칙

직렬화 · 문자 인코딩 · 엔디언 · UTF-8 · 바이트 순서 표시

스트림이 흐르는 속도를 조절하는 장치

흐름 제어 · 혼잡 제어 · 백프레셔 · 확인 응답 · 슬라이딩 윈도

스트림을 쓰다 만나는 장애

헤드 오브 라인 블로킹 · 패킷 손실 · 타임아웃 · 재전송

다른 이름: byte stream · 바이트스트림