Home

Published

- 34 min read

[ Network ] Websocket과 STOMP 프로토콜

img of [ Network ] Websocket과 STOMP 프로토콜

개요

학부생때 마지막 프로젝트가 될 예정인 SNS & 채팅 서비스의 서버를 도맡아 개발하게 되었다. SNS, 그리고 기존의 HTTP 서버는 많이 개발해봐서 금방 개발할거라 판단하고, 채팅 서버를 먼저 개발하기로 결정했다. 그래서 비동기 처리에 용이한 코틀린과 기존의 스프링을 사용하여 채팅 서버를 구축하기로 결정했다.

하지만 처음부터 쉽진 않은 것 같다. 이 부분에 대한 도메인 지식이 부족하다 보니 코드를 읽거나 이해하기가 힘들다. 그래서 websocket과 새로운 통신 방식에 대해 공부하며, 정리해두려 한다. 이번 글에서는 websocket에 관한 내용만 정리할 예정이다.

WebSocket

wikipedia

WebSocket은 단일 TCP(전송 제어 프로토콜) 연결을 통해 동시에 양방향 통신 채널을 제공하는 컴퓨터 통신 프로토콜이다. WebSocket 프로토콜은 2011년 IETF에 의해 RFC 6455로 표준화되었다. 웹 애플리케이션이 이 프로토콜을 사용할 수 있게 하는 현재 사양은 ‘WebSockets’로 알려져 있다. 이는 WHATWG에 의해 유지 관리되는 살아있는 표준이며, W3C의 ‘The WebSocket API’의 후속 버전이다.

WebSocket은 대부분의 웹 페이지를 제공하는 데 사용되는 HTTP와는 구별된다. 둘은 다르지만, RFC 6455는 WebSocket이 “HTTP 포트 443과 80에서 작동하도록 설계되었으며 HTTP 프록시와 중개자를 지원한다”고 명시하여 HTTP와 호환되게 만든다. 호환성을 달성하기 위해 WebSocket 핸드셰이크는 HTTP 프로토콜에서 WebSocket 프로토콜로 전환하기 위해 HTTP Upgrade 헤더를 사용한다.

WebSocket 프로토콜은 웹 브라우저(또는 다른 클라이언트 애플리케이션)와 웹 서버 간의 완전 양방향 상호 작용을 가능하게 하며, HTTP 폴링과 같은 반이중 대안보다 오버헤드가 낮아 서버와의 실시간 데이터 전송을 용이하게 한다. 이는 서버가 클라이언트의 요청 없이도 클라이언트에게 콘텐츠를 보낼 수 있는 표준화된 방법을 제공하고, 연결을 열어둔 채로 메시지를 주고받을 수 있게 함으로써 가능해진다. 이렇게 클라이언트와 서버 간에 지속적인 양방향 대화가 가능하다. 통신은 일반적으로 TCP 포트 443번(또는 비보안 연결의 경우 80번)을 통해 이루어지며, 이는 방화벽을 사용하여 웹이 아닌 인터넷 연결을 차단하는 환경에 유리하다. 또한 WebSocket은 TCP 위에 메시지 스트림을 가능하게 한다. TCP 자체는 메시지의 개념 없이 바이트 스트림만을 다룬다. 이와 유사한 양방향 브라우저-서버 통신은 Comet이나 Adobe Flash Player와 같은 임시 기술을 사용하여 비표준화된 방식으로 달성되어 왔다.

프로토콜

과정

  1. Opening handshake(HTTP request + HTTP response) to establish a connection
  2. Data messages to transfer application data.
  3. Closing handshake (two Close frames) to close the connection

(1) Opening handshake

클라이언트가 HTTP 요청을 보낸다 (GET 메서드, 버전 ≥ 1.1). 성공 시 서버는 상태 코드 101(프로토콜 전환)의 HTTP 응답을 반환한다. 이는 핸드셰이크가 HTTP와 호환되기 때문에 WebSocket 서버가 HTTP(80)와 HTTPS(443)와 같은 포트를 사용할 수 있음을 의미한다.

접속 요청은 HTTP로 한 뒤, 웹소켓 프로토콜로 변경된다.(ws)

SideHeaderValueMandatory
OriginVariesYes (for browser clients)
RequestHostVaries
Sec-WebSocket-Version13Yes
Sec-WebSocket-Keybase64-encode(16-byte random nonce)Yes
ResponseSec-WebSocket-Acceptbase64-encode(sha1(Sec-WebSocket-Key + “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”))Yes
ConnectionUpgrade
Upgradewebsocket
BothSec-WebSocket-ProtocolThe request may contain a comma-separated list of strings (ordered by preference) indicating application-level protocols (built on top of WebSocket data messages) the client wishes to use.

If the client sends this header, the server response must be one of the values from the list.
Sec-WebSocket-ExtensionsOptional
Other headersVaries

request 예시

   GET /chat HTTP/1.1
Host: localhost:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://localhost:9000
  • GET /chat HTTP/1.1: 웹소켓의 통신 요청에서, HTTP 버전은 1.1 이상이어야 하고, GET 메서드를 사용해야 한다.
  • Upgrade: 프로토콜을 전환하기 위해 사용하는 헤더. 웹소켓 요청시에는 반드시 websocket 이라는 값을 가짐. 이 값이 없거나 다른 값으면 cross-protocol attack 이라고 간주, 웹 소켓 접속을 중지시킨다.
  • Connection: 현재의 전송이 완료된 후 네트워크 접속을 유지할 것인가에 대한 정보. 웹 소켓 요청시에는 반드시 Upgrade 라는 값을 가진다. Upgrade와 마찬가지로 이 값이 없거나 다른 값이면 웹소켓 접속을 중지시킨다.
  • Sec-Websocket-Key: 유효한 요청인지 확인하기 위해 사용하는 키 값이다.
  • Sec-WebSocket-Protocol: 사용하고자 하는 하나 이상의 웹 소켓 프로토콜 지정한다. 필요한 경우에만 사용한다.
  • Sec-WebSocket-Version: 클라이언트가 사용하고자 하는 웹소켓 프로토콜 버전. 현재 최신 버전은 13이다.
  • Origin: Cors 정책으로 만들어진 헤더. Cross-Site Websocket Hijacking과 같은 공격을 피하기 위함이다.

response 예시

   HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
  • HTTP/1.1 101 Switching Protocols: 101은 HTTP에서 ws로 프로토콜 전환이 승인되었다는 응답 코드이다.
  • Sec-WebSocket-Accept: 요청 헤더의 Sec-WebSocket-Key에 유니크 아이디를 더해서 SHA-1 로 해싱한 후 base64로 인코딩한 결과이다. 웹 소켓 연결이 게시되었음을 알린다.

(2) Data Transfer

Opening HandShake에서 승인이 나고나면, 웹 소켓 프로토콜로 노란색 박스 부분인 Data transfer가 진행된다. 여기서 데이터는 메시지라는 단위로 전달된다.

메세지

  • 완전한 논리적 데이터 단위이다.
  • 여러 프레임으로 구성될 수 있다.
  • 텍스트나 이진 데이터 포함 가능하다.

프레임

  • 웹소켓 통신의 기본 데이터 전송 단위이다.
  • 작은 헤더와 페이로드로 구성된다.
  • 메시지를 효율적으로 분할하고 전송하는 데 사용한다.
  • 데이터 링크계층(이더넷)에서 주고 받는 가장 작은 단위이다.

프레임과 패킷은 다르다. 프레임은 데이터 링크 계층에서 사용되는 단위이고, 패킷은 네트워크 계층의 단위이다. 웹소켓에서는 ‘프레임’이라는 용어로 프로토콜 특유의 데이터 구조를 나타낸다.

이 단계에서 클라이언트와 서버는 메시지와 프레임으로 실시간 양방향 통신을 한다. 이는 HTTP의 요청-응답 모델보다 유연한 데이터 교환을 가능하게 한다.

(3) Closing handshake

웹소켓 연결 종료 시 Closing Handshake가 수행된다. 이는 연결을 깨끗하게 종료하기 위한 과정이다.

과정

  1. 연결 종료 요청
    • 클라이언트나 서버 중 한 쪽이 연결 종료를 원할 때 Close 프레임을 전송한다.
    • Close 프레임에는 상태 코드와 종료 사유를 포함할 수 있다.
  2. 종료 확인
    • Close 프레임을 받은 상대방은 자신의 Close 프레임으로 응답한다.
    • 이로써 양쪽 모두 연결 종료에 동의했음을 확인한다.
  3. 연결 종료
    • Close 프레임 교환 후 TCP 연결을 종료한다.
  • 정상적인 종료는 양방향으로 이루어진다.
  • 비정상적인 상황(네트워크 오류 등)에서는 일방적 종료도 가능하다.
  • 종료 과정은 빠르고 효율적으로 설계되어 있다.

Closing Handshake는 리소스를 정리하고 연결 상태를 명확히 하는 데 중요하다. 이를 통해 웹소켓은 안정적이고 예측 가능한 방식으로 통신을 마무리한다.

STOMP

WebSocket 프로토콜은 두 가지 유형의 메세지를 정의하고 있지만 그 메세지의 내용까지는 정의하고 있지 않다.

STOMP(Simple Text Oriented Messaging Protocol)은 메세징 전송을 효율적을 하기 위해 탄생한 프로토콜이고, 기본적으로 pub/sub 구조로 되어있어 메세지를 전송하고 메세지를 받아 처리하는 부분이 확실히 정해져 있기 때문에 개발자 입장에서 명확하게 인지하고 개발할 수 있는 이점이 있다.

즉, Stomp 프로토콜은 WebSocket 위에서 동작하는 프로토콜로써 클라이언트와 서버가 전송할 메세지의 유형, 형식, 내용들을 정의하는 매커니즘이다.

(STOMP는 Simple/Streaming Text Orientated Messaging Protocol의 약자이다.)

특징

STOMP는 상호 운용 가능한 와이어 포맷(네트워크를 통해 데이터가 전송될 때 사용하는 데이터 구조와 표현방식)을 제공하여 STOMP 클라이언트가 어떤 STOMP 메시지 브로커와도 통신할 수 있게 한다. 이를 통해 많은 언어, 플랫폼, 브로커 간에 쉽고 광범위한 메시징 상호 운용성을 제공한다.

간단한 설계 STOMP는 HTTP 설계 철학을 따르는 매우 간단하고 구현하기 쉬운 프로토콜이다. 서버 측 구현은 어려울 수 있지만, 연결을 위한 클라이언트를 작성하는 것은 매우 쉽다.

  • publisher, broker, subscriber 를 따로 두어 처리 (pub / sub 구조)
  • 연결시에 헤더를 추가하여 인증 처리 구현이 가능
  • STOMP 스펙에 정의한 규칙만 잘 지키면 여러 언어 및 플랫폼 간 메세지를 상호 운영할 수 있음

pub / sub란 메세지를 공급하는 주체와 소비하는 주체를 분리해 제공하는 메세징 방법이다.

프레임 기반 프로토콜

STOMP는 HTTP 위에 올라가는 프레임으로 모델링된다. 프레임은 하나의 커맨드, 선택적인 헤더들, 그리고 선택적인 바디를 갖는다. STOMP가 사용하는 기본 인코딩은 UTF-8. 텍스트 기반 메시지를 지원하지만, 바이너리 메시지를 전송하기 위한 명세도 있다.

STOMP 서버

서버는 목적지(destination) 여럿을 보유한다. 메시지는 이 목적지를 향하여 전송된다. STOMP 프로토콜은 목적지 문자열을 이해하지 않는다. 왜냐하면 목적지 문자열의 신택스는 서버의 구현을 따르기 때문이다. 또 STOMP는 목적지의 **메시지 교환 시멘틱(delivery semantics)**에 관해서도 관심이 없다. 메시지 교환 시멘틱은 서버 별로, 목적지 별로 다양하다. 이 탓에 서버는 STOMP를 지원하는 다양한 시멘틱을 창의적으로 구성할 수 있을 것이다.

Pub/Sub에 대한 이해

두명의 사용자 클라이언트가 있다. 사용자는 서버와 웹소켓으로 연결되어 있는데, 구독하는 주소를 동일하게 no01에 구독하도록 설정하였다. 그리고 발행자가 메세지의 타겟을 no01로 설정해서 메세지를 보냈는데, 서버에서는 발행자의 메세지를 확인한 후 no01 채널을 구독하는 모든 사용자(클라이언트)에게 메세지를 보내게 된다.

만약 구독 url이 no01이 아니라 no02, 혹은 다른 url을 구독중이면 어떻게 될까?

위 그림과 같이 다른 구독 url로 구독중이라면, 메세지를 받지 못하게 된다. 이것이 기본적인 pub/sub 구조이다. 하지만 내가 지금 구축해야 하는 것은 채팅서버이다. 그래서 사용자가 발행자의 역할도 해야 한다. 따라서 아래 그림과 같은 구조가 나오 수 있다.

pub/sub 구조에서 좀 더 자세하게

조금만 더 구체적으로 들어가보자. 만약 사용자가 점점 늘어나면 어떻게 될까? 혹은 구독자가 일시적으로 오프라인이거나 네트워크때문에 처리 속도가 늦어지면 어떻게 될까? 메세지 전달이 되지 않을수도 있다. 아니면 서버에 부하가 심하게 되어 최악의 상황에는 서버가 내려갈 수도 있다. 이 문제는 각 구독자가 큐를 가지게 되면 해결 가능하다.

또 다른 해결방법으로는 서버를 여러대 두는 방법도 있겠다. 하지만 서버가 다수인 경우를 생각해보자. 만약 3명의 사용자가 같은 채널을 구독중이지만, 서버가 다르면 어떻게 될까?

발행자가 보낸 서버1에 구독중인 사용자 1, 2만 메세지를 전달받게 되고, 사용자 3은 메세지를 받지 못하게 된다.

저 문제를 해결하려면 어떻게 해야될까?

  • 방법1 사용자가 어떤 서버에 접속하고 있는지 기억해두는 것이다. no01 이라는 채널을 구독하고 있는 사용자는 어떤 서버에 접속중인지 전부 지정해두면 된다. no01에 구독중인 사용자3은 서버2에 소켓 통신을 연결 중이라는 걸 어딘가에 저장하면 된다. 서버2에 no01 채널에 접속중인 사용자가 있다는 정보를 알 수 있다면, 서버1에 발행 메세지를 보냈을 때, 서버1에서 서버2에서도 접속중인 사용자를 알 수 있다면, 서버2에 메세지를 보내도록 알려주면 된다.
  • 방법2 외부 메세지 브로커에서 메세지 큐를 관리한다.

발행자는 채널 no01에 구독중인 사용자에게 메세지를 보내고 싶다. 위 그림에서는 채널 no01에 구독중인 사용자 1, 2, 3에게 메세지 전달을 할 수 있다. 사용자 1, 2는 서버1에 웹소켓을 연결 중이며, 사용자3, 4는 서버2에 웹소켓 서버에 연결중이다. 발행자가 서버1에 메세지를 보냈을 때, 서버1에서는 메세지 브로커의 exchange에 메세지를 전달한다. exchange에 바인딩 되어있는 사용자 1, 2, 3 큐에 메세지를 전달하게 되며, 서버2에 연결중엔 사용자 3에도 메세지를 보낼 수 있다. 외부 메시지 브로커를 사용하기 때문에 웹소켓 몇번 서버에 연결했는지 상관없이, 구독중인 채널의 메세지를 받을 수 있다.

STOMP 클라이언트

클라이언트는 2가지 역할을 수행한다. (병행 가능)

  • 생산자: 메시지를 목적지로 보낸다. SEND 프레임을 사용한다.
  • 소비자: 목적지를 구독하기 위한 SUBSCRIBE 프레임을 보낸다. 그리고 서버로부터 MESSAGE 프레임에 담긴 메시지를 수신한다.

설계 철학

단순함과 상호운용성: STOMP는 경량 프로토콜로서 클라이언트와 서버 양측이 다양한 언어로 손쉽게 구현할 수 있어야 한다. 즉 프로토콜은 서버의 아키텍쳐에 대해 많은 제한이나 기능을 요구하지 않는다. 목적지 네이밍이나 신뢰 시멘틱 등이 그 예다.

STOMP 프레임

STOMP는 프레임 기반 프로토콜이며 신뢰할 수 있는 양방향 스트리밍 네트워크를 보장하는 프로토콜이다. 클라이언트와 서버는 STOMP 프레임 형식을 스트림으로 전송하며 통신한다.

프레임 구조

   COMMAND
header1:value1
header2:value2

Body^@

바디 직후에는 NULL 아스키 문자가 위치해야한다. 명세 문서에서는 이를 가시적으로 표현하기 위해^@로 표기한다.

인코딩

커맨드와 헤더는 UTF-8로 인코딩한다. CONNECT와 CONNECTED 프레임을 제외, 헤더에 존재하는 캐리지 리턴, 라인 피드, 콜론 문자를 이스케이프 처리한다.

C 스타일의 문자 이스케이프를 사용한다. 프레이믈 인코딩할 때 다음과 같이 이스케이프를 적용해야 한다.

  • 캐리지 리턴 → \r
  • 라인 피드 → \n
  • 옥텟 58 → \c
  • 역슬래시 → \\

프레임을 디코딩 할 때도 마찬가지이다. 열거하지 않은 이스케이프를 발견할 경우엔 치명적인 프로토콜 에러로 취급해야 한다.

바디

SEND, MESSAGE, ERROR 프레임만이 바디를 보유할 수 있다. 이외 프레임은 바디를 가질 수 없다.

표준 헤더

content-length

모든 프레임은 content-length 헤더를 명시할 수 있다. 메시지 바디의 octect 카운트를 센 값을 나타낸다. 이 헤더가 명시되었다면 수신 측은 바디에서 명시된 만큼 옥텟을 읽어들이려고 시도해야한다.

content-type

프레임에 바디가 존재하는 SENDMESSAGEERROR 프레임의 경우에 content-type 헤더를 명시하여 수신 측이 바디를 해석할 수 있도록 도움을 주어야 한다. 이 헤더가 명시되어 있다면 바디의 값을 MIME 타입으로 인지해야 한다. 헤더가 명시되어 있지 않다면 바디를 바이너리 blob으로 인지해야 한다.

  • MIME 타입 설명
    • text/ 로 시작하는 타입: UTF-8로 인코딩 된 것으로 가정한다. 만약 다른 인코딩의 텍스트를 사용했을 경우 ;charset<encoding> 을 추가적으로 덧붙여 준다. text/html;charset=utf-16 처럼.
    • text/ 로 시작하지 않는 타입: 이런 타입도 텍스트로 해석될 여지가 있다면 application/xml;charset-utf-8 인코딩을 덧붙여 표시한다.

서버와 클라이언트는 반드시 UTF-8 인코딩 및 디코딩을 할 수 있어야 하며, 상호운용성을 최대화하기 위해 UTF-8로 인코딩된 텍스트를 사용하길 권장한다.

receipt

CONNECT를 제외한 프레임은 receipt를 명시하고 임의의 값을 배정할 수 있다. 이에 서버는 클라이언트 프레임을 처리한 것에 대한 ACK로서 RECEIPT 프레임을 회신한다.

   SEND
destination:/queue/a
receipt:message-12345

hello queue a^@

반복적인 헤더 엔트리

메시징 시스템이 토폴로지를 구성할 수 있기에, 메시지는 소비자에게 도착하기 까지 복수의 메시징 서버를 지나다닐 수 있다. 각 STOMP 서버는 헤더 값을 업데이트 하고 싶다면 두 방법 중 하나를 택한다.

  1. 동일 헤더를 앞쪽에 추가하기
  2. 헤더 값을 직접 바꾸기

클라이언트 혹은 서버는 프레임에서 반복된 헤더 엔트리를 발견하게 된다. 이 경우 가장 처음 엔트리를 의미 있는 것으로 생각해야 한다.

   MESSAGE
foo:World
food:Hello

^@

크기 제한

클라이언트가 서버 쪽의 메모리 활용에 문제를 야기하지 않도록, 서버는 프레임 크기 제한을 걸어도 된다.

  • 한 프레임이 포함할 수 있는 헤더의 개수
  • 헤더 영역의 최대 라인
  • 바디의 최대 크기

제한을 어긴 프레임을 발견할 경우 ERROR 프레임으로 회신하면 된다.

연결의 잔류

STOMP 서버는 클라이언트가 빠른 속도로 연결 - 연결 해제 하는 동작을 지원해야 한다.

즉 서버는 닫힌 연결을 오직 짧은 시간 동안만 잔류하도록 하여, 연결이 재설정되기 전에 제거해야 한다.

결국 클라이언트는 서버가 보낸 맨 마지막 프레임을 못 받게 될 수도 있다. (예를 들면, DISCONNECT 프레임에 대한 서버의 ERROR 회신 혹은 RECEIPT 회신)

연결하기

스트림 혹은 TCP 위에서 클라이언트는 CONNECT 프레임을 서버에 전송한다.

   CONNECT
accecpt-version:1.0,1.1,1.2
host:stomp.github.org

^@

서버가 연결을 수락하면 CONNECTED 프레임을 회신한다. 연결을 거부할 경우 ERROR 프레임에 거절 사유를 포함하여 회신한다.

   CONNECTED
VERSION:1.1

^@

하트 비팅

원격지 간 TCP 연결의 헬스 체크를 진행하기 위해 Heart-beating을 이용할 수 있다.

양측은 CONNECT와 CONNECTED 헤더에 heart-beat 헤더를 추가하고 두 개의 양수를 콤마로 구분해 표기한다.

  • 첫 번째 양수
  • 본인이 하트 비트를 몇 ms 주기로 보낼 수 있는지 명시한다. 0을 기입할 경우 하트 비트를 전송할 수 없음을 의미.
  • 두 번째 양수
  • 상대방의 하트 비트를 몇 ms 주기로 수신하고 싶은지 명시한다. 0을 기입할 경우 하트 비트를 수신하고 싶지 않다는 뜻.

이 헤더는 선택사항이다. 하지만 이 헤더를 명시하지 않았을 경우 heart-beat:0,0 으로 취급하여 상호 헬스 체크를 절대 진행하지 말아야 한다.

하트 비트 정책 파악 방법

   CONNECT
heart-beat:<cx>,<cy>

CONNECTED
heart-beat:<sx>,<sy>
  • <cx> 혹은 <sy> 가 0이면 클라이언트는 하트 비트를 보내지 않는다.
  • 그렇지 않다면 MAX(<cx>, <sy>)ms의 주기로 클라이언트는 하트 비트를 보낸다.
  • 서버 측 하트 비트 전송 정책은 <sx><cy> 로 치환하여 생각하면 된다.

하트 비트에 대하여

일단 상대로부터 어떤 데이터를 전송 받았다면, 이로부터 상대방은 살아있는 상태라고 인지할 수 있다.

만약 n 밀리초마다 하트 비트를 수신하고 싶다고 가정하자.

  • 상대방은 매 n 밀리초 이내에 새로운 데이터를 보내와야 함.
  • 그렇다고 해서 상대방은 보내야 할 STOMP 프레임이 없을 수도 있음. 그렇다면 end-of-line 를 대신 보내면 된다.
  • 상대방이 n 밀리초 윈도우 동안 아무 데이터를 수신하지 않았으면, 상대방이 죽은 상태라고 인지할 수 있다.
  • 하지만 타이밍이 부정확할 수도 있으니, 수신자 측은 에러 마진을 두고 좀 더 인내하는 정책을 사용하길 권장한다.

클라이언트의 프레임

  • SEND
       SEND
    destination:/queue/a
    content-type:text/plain

    hello queue a
    ^@
  • 서버의 목적지로 메시지를 보낸다. 목적지 네이밍은 서버에서 정의한다. 딜리버리 시멘틱을 명시하는 네이빙 방식을 서버에서 구현해야 한다. 신뢰 시멘틱 역시 서버에서 구현을 정한다. 에를 들어 transaction 같은 추가적인 헤더를 사용할 수 있다.
  • SUBSCRIBEid는 해당 구독에 식별자를 부여한다. 그러므로 추후 발생하는 MESSAGE나 UNSUBSCRIBE 프레임이 해당 ID에 연관되도록 한다. 한 커넥션에서 여러 구독을 신청하는 경우 ID를 다르게 사용해야 한다.
       SUBSCRIBE
    id:0
    destination:/queue/foo
    ack:client

    ^@
  • ack는 autoclientclient-individual 값을 갖는다. ACK 프레임 전송에 대한 정책을 표시하는 것이다. 클라이언트는 서버가 보내온 구독 메시지를 처리하고 ACK 프레임을 전송할 수 있다.
  • 서버의 목적지를 구독한다. 구독한 목적지의 자료를 서버에서 MESSAGE 프레임으로 지속 전송할 것이다.
  • UNSUBSCRIBE
  • UNSUBSRIBE id:0 ^@
  • BEGIN
       BEGIN
    transaction:tx1

    ^@
  • 트랜잭션 시작을 알린다. 여기서 tx1은 트랜잭션 동안 SEND와 ACK 메시지의 트랜잭션 헤더에 사용되며, 트랜잭션 동안의 메시지들은 원자적으로 처리된다.
  • COMMIT
       COMMIT
    transaction:tx1

    ^@
  • 진행중인 트랜잭션을 커밋한다.
  • ABORT
       ABORT
    transaction:tx1

    ^@
  • 진행죽인 트랜잭션을 파기하고 롤백한다.
  • ACK
       ACK
    id:12345
    transation:tx1

    ^@
  • 클라이언트가 구독을 할 때 ack:clientclient-individual 헤더를 명시했다면, 구독 메시지를 수신한 다음 ACK를 날릴 수 있다. ACK 프레임엔 id 헤더를 꼭 포함해야 한다. transaction 헤더는 ACK가 언급된 트랜잭션에 포함될 때 쓴다.
  • NACK
  • ACK의 반대이다. 메시지 소비를 실패했다고 서버에게 보낸다.
  • DISCONNECT
       DISCONNECT
    receipt:77
    ^@

그럼 다시 클라이언트는 영수증 프레임을 대기하고, 이를 수신하여 소켓을 회수한다.

  • RECEIPT receipt-id:77 ^@
  • 연결을 해제한다. 여기서는 서버에게 연결을 끊고 영수증을 보내 달라고 요청하고 있다.

서버의 프레임

  • MESSAGE메시지 ID는 해당 메시지 프레임에 할당되는 고유한 값이다.
       MESSAGE
    subscription:0
    message-id:007
    destination:/queue/a
    content-type:text/plain

    hello queue a^@
  • 클라이언트의 ACK가 기대된다면 ack 헤더를 추가하여 클라이언트가 ack의 ID를 사용할 수 있게 해야한다.
  • 구독의 ID와 메시지 ID를 필수로 기재한다. 구독 ID란 SUBSCRIBE 프레임에 명시된 것과 동일하며,
  • RECEIPT
  • ERROR

프레임 구조 요약

In addition to the standard headers described above (content-lengthcontent-type and receipt), here are all the headers defined in this specification that each frame MUST or MAY use:

  • CONNECTorSTOMP
  • REQUIRED: accept-versionhost
    • OPTIONAL: loginpasscodeheart-beat
  • CONNECTED
    • REQUIRED: version
    • OPTIONAL: sessionserverheart-beat
  • SEND
    • REQUIRED: destination
    • OPTIONAL: transaction
  • SUBSCRIBE
    • REQUIRED: destinationid
    • OPTIONAL: ack
  • UNSUBSCRIBE
    • REQUIRED: id
    • OPTIONAL: none
  • ACKorNACK
    • REQUIRED: id
    • OPTIONAL: transaction
  • BEGINorCOMMITorABORT
    • REQUIRED: transaction
    • OPTIONAL: none
  • DISCONNECT
    • REQUIRED: none
    • OPTIONAL: receipt
  • MESSAGE
    • REQUIRED: destinationmessage-idsubscription
    • OPTIONAL: ack
  • RECEIPT
    • REQUIRED: receipt-id
    • OPTIONAL: none
  • ERROR
    • REQUIRED: none
    • OPTIONAL: message

BNF 언어

   NULL                = <US-ASCII null (octet 0)>
LF                  = <US-ASCII line feed (aka newline) (octet 10)>
CR                  = <US-ASCII carriage return (octet 13)>
EOL                 = [CR] LF
OCTET               = <any 8-bit sequence of data>

frame-stream        = 1*frame

frame               = command EOL
                      *( header EOL )
                      EOL
                      *OCTET
                      NULL
                      *( EOL )

command             = client-command | server-command

client-command      = "SEND"
                      | "SUBSCRIBE"
                      | "UNSUBSCRIBE"
                      | "BEGIN"
                      | "COMMIT"
                      | "ABORT"
                      | "ACK"
                      | "NACK"
                      | "DISCONNECT"
                      | "CONNECT"
                      | "STOMP"

server-command      = "CONNECTED"
                      | "MESSAGE"
                      | "RECEIPT"
                      | "ERROR"

header              = header-name ":" header-value
header-name         = 1*<any OCTET except CR or LF or ":">
header-value        = *<any OCTET except CR or LF or ":">

License

This specification is licensed under the Creative Commons Attribution v3.0 license.

출처