사전 메시지
개념

메시지

gabury1

한쪽이 다른 쪽으로 보내는 데이터 한 덩이입니다. 어디서 시작해 어디서 끝나는지가 정해져 있습니다. 받는 쪽은 그 덩이 하나만 보고 무슨 뜻인지 읽어 냅니다.

상세

우편함에 꽂힌 편지 한 통을 떠올리면 됩니다. 봉투라는 물건이 어디까지가 한 통인지를 눈으로 보여 줍니다. 메시지에는 그런 봉투가 없어서, 어디서 끝나는지를 데이터 안에 적어 두어야 합니다.

메시지는 보내는 쪽이 한 번에 건네는, 시작과 끝이 정해진 데이터 한 덩이입니다. 끝이 정해져 있다는 것이 이 단위의 조건입니다. 받는 쪽이 한 덩이가 다 도착하기를 기다렸다가 읽어야 하는 것은 아닙니다. 규격에 따라서는 앞에서부터 읽어 나가며 처리를 시작합니다.

한 덩이 안쪽은 대개 두 자리로 나뉩니다. 앞쪽은 이 덩이를 어떻게 다뤄야 하는지 적는 자리입니다. 뒤쪽은 실어 나르려던 내용입니다. HTTP(HyperText Transfer Protocol) 규격인 RFC(Request for Comments) 9110 은 이 구분을 HTTP 각 버전에 두루 통하도록 네 부분으로 더 잘게 일반화합니다.

부분 무엇을 담나
제어 데이터 메시지를 설명합니다. 어디로 보낼지도 여기서 정합니다
헤더 표 이름-값 쌍을 모아 둔 표입니다. 제어 데이터를 확장합니다. 보내는 쪽·메시지·내용·맥락에 대한 정보를 함께 실어 나릅니다
내용 길이에 상한이 없을 수도 있는 스트림입니다
트레일러 표 이름-값 쌍을 모아 둔 표입니다. 내용을 보내는 동안 알게 된 정보를 전달합니다

HTTP 메시지를 보내는 순서도 같은 순서입니다. 프레이밍과 제어 데이터가 먼저 갑니다. 그 뒤에 헤더 표를 채우는 필드들이 담긴 헤더 절이 따라옵니다. 메시지에 내용이 있으면 헤더 절 다음에 내용이 옵니다. 트레일러 표에 들어갈 필드가 있으면 내용 뒤에 트레일러 절이 붙을 수 있습니다.

flowchart TD
    A[프레이밍과 제어 데이터] --> B[헤더 절]
    B --> C[내용]
    C --> D[트레일러 절]

같은 표준은 HTTP 메시지가 스트림으로 처리되기를 기대한다고 적습니다. 무엇을 위한 스트림인지가 읽는 도중에 드러난다는 뜻입니다. 그래서 제어 데이터는 받는 쪽이 당장 알아야 할 것을 서술합니다. 헤더 필드는 내용을 받기 전에 알아야 할 것을 서술합니다. 내용은 받는 쪽이 원하거나 필요로 하는 것을 담습니다. 트레일러 필드는 내용을 보내기 전에는 알 수 없던 선택적 메타데이터를 줍니다.

HTTP 메시지는 자기 설명적이도록 의도되어 있습니다. 받는 쪽이 그 메시지에 대해 알아야 할 모든 것을, 전송 중에 압축되거나 생략된 부분을 되돌린 뒤에, 메시지 자체를 봐서 알아낼 수 있어야 한다는 뜻입니다. 앞서 오간 메시지로 쌓인 보내는 쪽의 현재 응용 상태를 알지 못해도 됩니다.

이름은 자리마다 다르게 붙습니다. RFC 1122 는 하위 계층 프로토콜을 서술하는 그 문서 안에서라는 한정을 달고, 메시지를 전송 계층 프로토콜의 전송 단위로 정의합니다. 그 정의에서는 TCP(Transmission Control Protocol) 세그먼트 하나가 곧 메시지입니다. 메시지는 전송 프로토콜 헤더 뒤에 응용 프로토콜 데이터가 붙은 꼴입니다. 인터넷을 종단 간으로 지나가려면 데이터그램 안에 캡슐화되어야 합니다.

배경

통신선 위로는 바이트가 이어져 흘러갈 뿐입니다. 보내는 쪽이 세 번에 나눠 보냈는지 한 번에 몰아 보냈는지는 그 흐름에 드러나지 않습니다. 받는 쪽에는 이것이 곤란한 일입니다. 데이터를 해석하려면 먼저 어디까지가 한 벌인지 알아야 하는데 흘러오는 바이트만으로는 그 선이 보이지 않습니다.

그래서 끝을 표시하는 방법이 필요해졌습니다. 앞에 길이를 적어 두는 방법이 있습니다. 정해진 구분자를 끝에 붙이는 방법도 있습니다. 아예 전달 단위 자체를 한 덩이로 고정해 버리는 방법도 있습니다. 어느 쪽이든 결과는 같습니다. 흐름 위에 경계가 생깁니다. 받는 쪽은 그 경계를 기준으로 한 벌씩 끊어 읽습니다.

그렇게 끊어 낸 한 벌에 붙은 이름이 메시지입니다. 그래서 메시지는 바이트를 적는 규칙이 아니라 그 규칙을 따라 적힌 단위입니다. 규칙 쪽에는 프로토콜이나 포맷이라는 다른 이름이 따로 붙습니다.

예시

HTTP/1.1 메시지

RFC 9112 는 HTTP/1.1 클라이언트와 서버가 메시지를 보내서 통신한다고 적습니다. 메시지 하나는 시작줄로 시작합니다. 그 뒤에 줄바꿈이 오고, 헤더 필드 줄이 0개 이상 이어지고, 헤더 절의 끝을 알리는 빈 줄이 옵니다. 마지막에 메시지 본문이 선택적으로 붙습니다. 표준은 이 형식을 다음처럼 적어 둡니다.

http-message   = start-line CRLF *( field-line CRLF ) CRLF [ message-body ]
start-line     = request-line / status-line
request-line   = method SP request-target SP HTTP-version
status-line    = HTTP-version SP status-code SP [ reason-phrase ]
field-line     = field-name ":" OWS field-value OWS
message-body   = *OCTET
trailer-section = *( field-line CRLF )

메시지는 클라이언트에서 서버로 가는 요청일 수도 있고 서버에서 클라이언트로 오는 응답일 수도 있습니다. 문법으로 보면 두 종류는 시작줄에서만 갈립니다. 요청이면 request-line 이고 응답이면 status-line 입니다. 나머지 차이는 메시지 본문의 길이를 정하는 알고리즘뿐입니다.

경계를 어떻게 찾는지가 파싱 절차에 그대로 드러납니다. 시작줄을 읽어 구조체에 담습니다. 각 헤더 필드 줄을 필드 이름을 키로 삼아 해시 테이블에 넣습니다. 빈 줄을 만나면 헤더 절이 끝난 것입니다. 그렇게 파싱한 데이터로 메시지 본문이 뒤따르는지 판정합니다. 본문이 있다고 표시되어 있으면 본문 길이만큼의 옥텟을 읽을 때까지, 또는 연결이 닫힐 때까지 스트림으로 읽습니다.

flowchart TD
    A[시작줄을 읽는다] --> B[헤더 필드 줄을 빈 줄까지 읽는다]
    B --> C{본문이 있다고 표시됐나}
    C -->|그렇다| D[본문 길이만큼 옥텟을 읽는다]
    C -->|아니다| E[메시지 하나가 끝난다]
    D --> E

MQTT 제어 패킷

MQTT(Message Queuing Telemetry Transport) v5.0 표준은 응용 메시지를 이렇게 정의합니다. MQTT 프로토콜이 응용을 위해 네트워크로 실어 나르는 데이터입니다. 응용 메시지가 MQTT 로 전송될 때 그 안에는 페이로드 데이터, 서비스 품질(Quality of Service, QoS), 속성 모음, 토픽 이름이 들어 있습니다. 클라이언트는 다른 클라이언트가 관심을 가질 만한 응용 메시지를 발행합니다. 받고 싶은 응용 메시지는 구독으로 요청합니다.

전송되는 실물은 MQTT 제어 패킷입니다. 표준은 제어 패킷 하나가 최대 세 부분으로 이루어지고 언제나 아래 순서를 지킨다고 적습니다.

부분 어디에 있나
고정 헤더 모든 MQTT 제어 패킷에 있습니다
가변 헤더 일부 제어 패킷에만 있습니다
페이로드 일부 제어 패킷에만 있습니다

POSIX 메시지 큐

네트워크를 건너지 않는 자리에도 같은 단위가 있습니다. POSIX(Portable Operating System Interface) 의 mq_send() 는 msg_ptr 이 가리키는 메시지를 mqdes 로 지정한 메시지 큐에 넣습니다. msg_len 인자가 그 메시지의 길이를 바이트로 지정합니다. 이 값은 메시지 큐의 mq_msgsize 속성보다 크면 안 됩니다. 크면 mq_send() 는 실패합니다.

여기서 메시지 하나가 갖는 것이 길이만은 아닙니다. 큐가 가득 차 있지 않으면 mq_send() 는 msg_prio 인자가 가리키는 자리에 메시지를 끼워 넣은 것처럼 동작합니다. msg_prio 의 숫자 값이 더 큰 메시지가 값이 더 작은 메시지들 앞에 놓입니다.

경계

TCP 로 흘려보낸 것도 메시지인가. 누구의 눈으로 묻는지에 따라 답이 갈립니다.

전송 계층 자신의 눈으로 보면 맞습니다. RFC 1122 는 TCP 세그먼트 하나가 곧 메시지라고 적습니다. 전송 계층이 종단 간으로 실어 나르는 단위가 세그먼트이기 때문입니다.

응용이 보낸 한 덩이라는 뜻으로 물으면 아닙니다. RFC 9293 은 TCP 가 응용에게 신뢰성 있고 순서가 지켜지는 바이트 스트림 서비스를 준다고 적습니다. 응용의 바이트 스트림은 TCP 세그먼트에 실려 네트워크를 건너갑니다. 그런데 개별 TCP 세그먼트는 응용의 개별 send() 호출이나 소켓 write() 호출과 일대일로 대응하지 않는 일이 잦습니다. 응용이 상위 프로토콜의 메시지 단위로 쓰기를 수행하더라도, TCP 는 주고받은 세그먼트의 경계와 사용자 응용 데이터의 읽기·쓰기 버퍼 경계 사이에 어떤 상관관계도 보장하지 않습니다.

sequenceDiagram
    participant 보내는쪽
    participant TCP
    participant 받는쪽
    보내는쪽->>TCP: 상위 프로토콜의 메시지 단위로 쓴다
    Note over TCP: 바이트 스트림으로 이어 붙인다
    TCP->>받는쪽: 세그먼트로 나눠 보낸다
    Note over 받는쪽: 세그먼트 경계와 쓰기 경계가 일대일이 아닐 때가 잦다

그래서 TCP 위에서 메시지를 주고받으려면 응용 프로토콜이 경계를 스스로 다시 세워야 합니다. HTTP/1.1 이 빈 줄과 본문 길이로 하는 일이 그것입니다.

UDP(User Datagram Protocol) 는 이 자리에서 갈라집니다. RFC 768 은 UDP 를 패킷 교환 컴퓨터 통신 환경에서 데이터그램 방식을 쓸 수 있게 하려고 정의된 프로토콜이라고 적습니다. UDP 헤더의 길이 필드는 헤더와 데이터를 포함한 이 사용자 데이터그램의 길이를 옥텟으로 적습니다. 길이가 데이터 안에 들어 있으니 단위 자체에 경계가 있습니다. RFC 8085 는 이 성질을 이름으로 못 박아, UDP 를 최소한의, 신뢰성 없는, 최선 노력의 메시지 전달 전송이라고 부릅니다.

관련 항목

메시지를 이루는 구성 요소

제어 데이터 · 헤더 · 페이로드 · 트레일러 · 토픽 · 시작줄 · 메타데이터

계층마다 메시지를 가리키는 다른 이름

세그먼트 · 데이터그램 · 패킷 · 프레임

메시지 경계를 세우는 방식

메시지 경계 · 프레이밍 · 청크 전송 인코딩

메시지를 실어 나르는 프로토콜

HTTP · MQTT · TCP · UDP

메시지를 정의하는 표준·문서

RFC 9110 · RFC 9112 · RFC 1122 · RFC 9293 · RFC 768 · RFC 8085 · POSIX

메시지가 오가는 경로

메시지 큐 · 메시지 브로커 · 소켓 · 발행-구독 · 요청-응답

메시지 전달에 적용되는 원칙

서비스 품질 · 멱등성 · 배압 · 전달 보장 · 중복 전달

메시지가 바이트로 바뀌는 방식

직렬화 · MIME(Multipurpose Internet Mail Extensions, 다목적 인터넷 메일 확장) · 인터넷 메시지 형식

다른 이름: message