어플리케이션 구현 기능 중 1:1 채팅 기능을 구현이 있었다. 그래서 사용한 WebSocket / STOMP 이 두가지 기능에 대해서 알아볼 생각이다.
두 프로그램 간의 메세지 교환을 위한 통신 방법 중 하나이다.
일단 먼저 어떠한 방법들이 있는지 확인을 하자
HTTP Poling

클라이언트가 주기적으로 서버에 HTTP 요청을 보내며 새로운 메세지가 있는지 확인하는 방법이다. 이러한 방식으로 클라이언트는 서버에게 GET or POST를 보내고, 서버는 새로운 메시지가 있는 경우에 응답을 보내주는 방식이다.
이러한 방법의 장점으로는 구현이 간단하며 기존 HTTP/HTTPS통신 방식을 그대로 사용하기에 특별한 프로토콜의 지식이 요구되지 않는다. 하지만 단점으로는 서버에 지속적인 요청을 보낸다고 했는데 이러한 방법 때문에 트래픽과 서버의 부하가 높아지는 경우가 발생한다.
트래픽과 서버의 부하가 높아지는 문제를 해결하기 위해서 클라이언트가 서버로 보내는 요청의 주기를 길게 설정한다고 가정을 하자, 그러면 이 전보다는 덜 하겠지만 실시간성을 보장하기 어려워 진다는 단점이 생기게 된다.
마지막 단점으로는 클라이언트가 서버로 주기적인 요청을 보내기 때문에 필요없는 네트워크 자원을 낭비하게되는 단점이 있다.
HTTP Long Polling

HTTP Long Polling방법은 HTTP Polling 방법과 유사하지만 다른 점이 존재한다. 그 방법은 서버가 클라이언트의 요청에 즉각적인 응답을 하지 않고 업데이트가 발생할 때까지 기달렸다가 새로운 데이터가 발생하거나 설정된 타임아웃이 지나면 응답을 보내고, 클라리언드는 다시 요청을 보내고 기달리게 된다.
이러한 방식의 장점으로는 실시간성을 어느 정도 보장을 할 수 있다는 점이다. 그리고 HTTP Polling 방법에 비해 효율적이며, 구현이 비교적 간단하며 HTTP기반 시스템과 호환이 좋다는 점이다.
반대로 단점을 보면, 서버와의 연결을 장시간 유지하기에 다수의 클라이언트가 연결되어 있다면 서버의 리소스 소모가 크다 그리고 클라이언트와 서버 사이 네트워크 연결 또한 유지를 하기 때문에 서버의 부하가 증가한다.
Server-Sent Events, SSE

서버가 클라이언트로 부터 HTTP요청을 받고, 요청이 종료되지 않은 상태에서 실시간으로 데이터를 지속적으로 보내는 단방향 통신 방식이다.
클라이언트가 서버에 SSE 연결을 요청하면, 서버는 클라이언트와 단일 HTTP연결을 유지하며, 새로운 데이터가 있을 경우 클라이언트로 데이터를 보낸다.
이 방식은 Web Socket과 달리 단방향 통신에 적한하며 서버에서 클라이언트로 메세지 전송 시 효율적이다.
하지만 단방향 통신만을 지원하기 때문에 클라리언트에서 서버로 데이터를 보낼 때 별도의 HTTP요청이 필요하다는 단점이 존재한다.
WebSocket
앞에서 확인 한 방법들은 성능과 네트워크의 효율성 그리고 실시간성을 생각하면 좀 비효율적인 방법이 될 수 있다. 그리고 SSE방법은 양방향 소통과는 거리가 먼 단방향 소통에서 사용될 방법이라고 생각한다.
WebSocket은 클라이언트가 접속을 요청을 하고, 웹 서버가 응답을 하고 연결을 끊는 것이 아닌 연결을 그대로 유지를 한다. 그 이후 클라이언트와 서버는 요청에 헤더 없이도 메세지를 자유롭게 주고 받을 수 있으며, 이를 통하야 양방향 통신이 가능하다. WebSocket연결은 ws:// (보안 연결을 위한 wss://)프로토콜을 사용한다.

웹 소켓을 열기위한 Handshake 요청을 보내고 서버가 클라이언트에게 응답을 보내는 구조로 되어 있다. 서버와 클라이언트는 HTTP 1.1 프로토콜을 사용하여 요청과 응답을 보낸다.
Opening Handshake
요청과 응답 헤더를 보면 아래와 같이 나타난다.
GET /chat HTTP/1.1
Host: localhost:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
| Header | div | Description |
|---|---|---|
| GET | Require | 요청 명령어는 GET을 사용해야 하며, 버전은 1.1이상의 버전을 사용 |
| Host | Require | 웹 소켓 서버의 주소를 명시 |
| Upgrade | Require | 프로토콜을 전환하기 위해 사용하는 헤더이다. WebSocket 명시(대소문자 구분 X) 만약 값이 없거나 다른 값을 갖고 있다면 cross-protocol attack이라고 간주되어 웹소켓 접속 중지 |
| Connection | Require | 현재의 전송이 완료된 후 네트워크 접속을 유지할 것인지에 대한 헤더 정보이다. 반드시 Upgrade로 명시 만약 값이 없거나 다른 값을 명시한다면 접속을 중지시킨다. |
| Sec-WebSocket-Key | Require | 유효한 요청인지 확인을 위해 사용되는 Key값으로 길이가 16Byte인 숫자를 Base64 인코딩 한 값 |
| Origin | Require | 클라이언트의 주소 명시 보안을 위해 이 해더를 보낸다. Cross-Site WebSocket Hijacking 같은 공격을 피하기 위해 사용한다. 대부분 어플리케이션은 히 해더가 없는 요청을 거부하며, 이러한 이유로 CORS정책이 만들어진 것이다. |
| Sec-WebSocket-Version | Require | 클라이언트가 사용하고자 하는 웹 소켓 프로토콜 버전을 명시 |
| Sec-WebSocket-Protocol | Option | 클라이언트가 사용하고 싶은 하위 프로토콜을 명시한다. |
| Sec-WebSocket-Extensions | Option | 클라이언트가 사용하고 싶은 추가 옵션을 명시한다. |
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
| Header | div | Description |
|---|---|---|
| HTTP | Require | HTTP 버전은 1.1이며, 클라이언트로 부터 요청이 이상 없는 경우 101상태코드로 사용을 한다. |
| Upgrade | Require | 요청과 동일 |
| Connection | Require | 요청과 동일 |
| Sec-WebSocket-Accept | Require | 클라이언트로 받은 Sec-WebSocket-Key를 사용하여 SHA-1로 해싱을 한 후, Base64로 인코딩한 결과 값. 웹소켓 연결이 개시되었음을 알림 |
| Sec-WebSocket-Protocol | Option | 서버에서 서비스하는 하위 프로토콜을 명시 만약 요청하지 않은 하위 프로토콜을 명시하면 HandShake는 실패한다. |
| Sec-WebSocket-Extensions | Option | 서버에서 사용하는 추가 옵션을 명시 만약 요청하지 않은 추가 옵션을 명시하면 HandShake는 실패한다. |
Data Transfer
위 Opening Handshake과정을 통해 연결이 된다면 지금부터 데이터 전송 파트가 시작된다. 여기서 클라이언트와 서버가 메세지를 주고 받을 때 앞에서 확인한 다른 방법들과 다르게 HTTP프로토콜이 아닌 WebSocket프로토콜을 사용하여 주고받는다.
여기서 WebSocket프로토콜은 헤더의 크기가 작고 오버헤드가 적기 때문에 HTTP방식 보다 효율적인 통신이 가능해진다.
그리고 Handshake가 끝난 시점부터 서로연결이 되어 있는지 확인하기 위해 패킷을 보내며, 주기적으로 핑을 보내 확인을 한다.
Close Handshake
클라이언트와 서버 모두 connection을 종료하기 위한 컨트롤 프레임을 전송할 수 있다. 이 컨트롤 프레임은 Closing Handshake를 시작하라는 특정한 컨트롤 시퀀스를 포함한 데이터를 갖고 있다.
위 이미지에서 서버가 커넥션을 종료하기 위해 프레임을 보내면, 클라이언트는 이에 대한 대답으로 Close 프레인을 전공을 한다.
이렇게 WebSocket연결이 종료되어 이후에 수신되는 모든 추가적인 데이터는 버려진다.
일단 모든 Socket은 네트워크 통신을 위한 Socket프로그래밍 개념이다. 서로 다른 프로토콜과 통신 방식에 기반하고 각 특징과 용도가 다르다.
일단 먼저 WebSocket에 대해서 알아보면, 이는 Appilcation Layer프로토콜로 HTTP를 기반으로 한 프로토콜 업그레이드를 통하여 양방향 통신을 제공한다.
클라이언트와 서버 간 지속적인 연결을 유지하며, 실시간 데이터 전송이 필요한 어플리케이션에 많이 사용된다.
WebSocket은 표준 포트 80(HTTP), 443(HTTPS)을 사용한다.
다음으로 TCP (Transmission Control Protocol) 소켓은 전송 계층 프로토콜로, 신뢰성 있는 데이터 전송을 보장을 한다. 데이터의 순서 보장, 오류 수정, 패킷 재전송 등의 기능을 제공하여 신뢰성을 확보를 한다. 포트 번호를 사용하여 네트워크 상의 두 어플리케이션 간 연결을 설정하고 연결 기반 통신을 제공한다.
마지막으로 UDP (User Datagram Protocol)은 TCP와 달리 비연결성 전송 계층 프로토콜이다. 이는 신뢰성, 순서 보장 등 TCP와는 상반되며 빠르고 가벼운 데이터 전송을 제공을 한다.
주로 스트리밍, 온라인 게임 등 실시간의 소통이 중요한 어플리케이션에서 사용이 된다.
UDP는 패킷 손실이 발생할 수 있겠지만, 낮은 지연시간과 빠른 전송 속도를 제공을 한다는 점이 다르다.
TCP와 UDP는 Appilcation Layer가 아닌 Transport Layer에서 동작을 한다.
각 방법에 대해서 간단한 비교하여 표로 나타내면 아래와 같다.
| 특징 | WebSocket | TCP 소켓 (TCPSocket) | UDP 소켓 (UDPSocket) |
|---|---|---|---|
| 프로토콜 계층 | 애플리케이션 계층 (HTTP 업그레이드) | 전송 계층 (TCP 프로토콜) | 전송 계층 (UDP 프로토콜) |
| 연결 방식 | 연결 지향형 (HTTP를 통한 핸드셰이크 후 연결 설정) | 연결 지향형 (3-way 핸드셰이크 후 연결 설정) | 비연결형 (연결 설정 없음) |
| 통신 방식 | 양방향 통신 (풀-듀플렉스) | 양방향 통신 (풀-듀플렉스) | 양방향 통신 (단방향 전송에 가까움) |
| 데이터 전송 보장 | 보장 (기반 프로토콜이 TCP이기 때문) | 보장 (패킷 손실 시 재전송 및 데이터 순서 보장) | 보장하지 않음 (패킷 손실 및 순서 오류 가능) |
| 헤더 크기 | 비교적 큼 (초기 HTTP 헤더 포함) | 작음 (TCP 헤더) | 매우 작음 (UDP 헤더) |
| 통신 성능 | 중간 (TCP 기반이지만 초기 핸드셰이크가 필요) | 상대적으로 낮음 (신뢰성 보장을 위한 오버헤드 존재) | 높음 (신뢰성 보장을 위한 추가 작업이 없으므로 지연 시간 낮음) |
| 지연 시간 | 낮음 (연결 이후 지속적인 통신) | 중간 (데이터 전송 보장을 위한 오버헤드) | 매우 낮음 (오버헤드 없음) |
| 주요 용도 | 실시간 웹 애플리케이션 (채팅, 알림, 실시간 데이터 전송) | 파일 전송, 신뢰성 있는 데이터 전송이 필요한 애플리케이션 | 스트리밍, 온라인 게임, VoIP 등 빠른 데이터 전송이 필요한 애플리케이션 |
| 전송 단위 | 메시지 (메시지 프레임 단위) | 스트림 (데이터의 흐름) | 데이터그램 (독립적인 패킷 단위) |
| 포트 사용 | 80 (HTTP), 443 (HTTPS) | 사용자 정의 포트 또는 기본 포트 (예: 80, 21, 443 등) | 사용자 정의 포트 (기본 포트 없음) |
| 프로토콜 보안 | SSL/TLS를 사용하여 보안 제공 (wss://) | SSL/TLS를 사용하여 보안 제공 가능 (HTTPS와 연계 가능) | 보안 제공하지 않음 (추가적인 애플리케이션 계층에서의 보안 필요) |
| 핸드셰이크 과정 | 있음 (HTTP 핸드셰이크 후 업그레이드) | 있음 (3-way 핸드셰이크 후 연결 설정) | 없음 |
| 하위 프로토콜 지원 | 있음 (STOMP, MQTT, AMQP 등) | 없음 (애플리케이션 레벨에서 직접 구현 필요) | 없음 (애플리케이션 레벨에서 직접 구현 필요) |
추가적으로 동작 방식의 차이점을 확인하자
연결 설정
HTTP 핸드셰이크를 통해 연결이 된다. 추가적으로 Upgrade헤더와 함께 요청을 보내고 서버는 이를 수락한 후 101 Switching Protocols 응답을 통해 연결을 WebSocket프로토콜로 업그레이드를 한다. 그러면 지속적인 양방향 통신이 가능해진다.3-way핸드셰이크를 통해 연결을 설정한다. 클라이언트가 서버에 SYN패킷을 보내면 서버는 SYN-ACK패킷으로 응답을 하고, 클라이언트는 다시 ACK패킷을 보내면 연결이 설정된다. 이 과정을 통해 양측은 서로의 상태를 확인하고 연결을 보장한다.UDP패킷으로 서버에 보내구조, 서버가 데이터를 수신하면 통신이 이루어진다. 이는 연결 설정과 종료에 따른 추가적인 작업이 없으므로 지연 시간이 매주 짧지만, 데이터가 손실되거나 순서가 보장되지 않는 문제가 발생할 수 있다.데이터 전송
Web
연결이 설정된 후, 클라이언트와 서버 간에 메시지 프레임 단위로 데이터를 주고받는다. 메시지의 경계를 명확히 구분하며, 전송한 메시지가 손실되거나 왜곡되지 않도록 보장한다.
TCP
데이터를 스트림 단위로 전송한다. 이는 데이터를 지속적으로 이어 보내는 방식이며, 데이터의 경계를 구분하지 않는다. TCP는 패킷 손실이 발생하면 자동으로 재전송하고, 수신 측에서 데이터의 순서를 보장한다.
UDP
데이터를 데이터그램 단위로 전송한다. 각 데이터그램은 독립적인 단위로 취급되며, 손실되거나 순서가 뒤바뀔 수 있다. UDP는 오류 검출 기능을 제공하지만, 데이터 손실 시 재전송을 자동으로 수행하지 않기 때문에 신뢰성이 낮다.
종료 방식
Web
close 프레임을 통해 연결 종료를 통보한다. 클라이언트 또는 서버가 close 프레임을 보내면 상대방이 close 프레임을 응답하여 연결이 종료된다.
TCP
4-way 핸드셰이크를 통해 연결을 종료한다. 클라이언트 또는 서버가 FIN 패킷을 보내면, 상대방이 ACK 패킷으로 응답하고, 이후 반대쪽에서도 FIN 패킷을 보내고 ACK 패킷을 응답받으면 연결이 종료된다.
UDP
UDP는 비연결형이므로 별도의 종료 절차가 없다. 단순히 데이터를 더 이상 보내지 않으면 통신이 종료된 것으로 간주된다.
결과적으로...
WebSocket은 비교적 작은 오버헤드로 효율적인 데이터 통신이 가능하며,TCP기반이르모 신뢰성과 안정성이 보장되어 있으며, 양방향 통신과 실시간 통신이 필요한 경우 매우 적합한 선택이 된다. 그리고STOMP와 결합하여 메세지 브로커를 통해 다양한 클라이언트와 메세지 전송 및 구독 모델을 지원한다는 점에서WebSocket과STOMP의 조합은 채팅 어플리케이션 구현에 적합한 방식이다.
WebSocket은 HTML5 이후에 나타났다. 그러면 이 전의 기술로 구현되어진 서비스에는 사용이 불가능하나?라고 질문은 하면 대답은 아니다. Socket.io, SockJS와 같이 HTML5 이전의 기술로 구현된 서비스에 WebSocket처럼 사용할 수 있도록 도와준다.
그리고 WebSocket은 메세지를 주고 받을 수 있겠지만 그 외 다른 일들은 하지 않는다. HTTP는 형식을 정해두고 모두가 약속을 따르기만 한다면 해석은 가능하겠지만, WebSocket은 형식이 정해져 있지 않기에 어플리케이션에서 쉽게 해석하기 힘들다.
그래서 서브 프로토콜을 사용하여 전달하거나 전달받는 형태를 하는 경우가 많다.
여기서 서브 프로토콜로 가장 많이 사용이 되는 것이 바로 STOMP이다.
STOMP는 메세지를 보다 효율적으로 하기 위해 고안된 프로토콜이다. 이는 기본적으로 Pub/Sub 구조로 되어 메세지를 전송하고 전달받아 처리하는 부분이 확실하게 정해져 있기에 명확하게 인지하고 개발할 수 있다는 이점이 존재한다.
STOMP프로토콜은 WebSocket위에서 동작하는 프로토콜로 클라이언트와 서버가 주고 받은 메세지의 유형, 형식, 내용을 정의하는 매커니즘이다.
STOMP는 TCP 혹은 WebSocket같은 양방향 네트워크 프로토콜 기반으로 동작을 한다. Text지향 프로토콜이나, Message Payload에는 Text 혹은 Binary데이터를 포함 할 수 있다.
STOMP는 Pub/ Sub 구조로 되어 있다고 했었다. 이 구조는 Producer와 Consumer을 분리하여 제공하는 방법이다.
여기서 특정 토픽이 있다는 것은 특정한 채팅방이 있다는 것이고, 해당 토픽을 구독하면 해당 채팅방에 입장을 하는 것과 비슷하다. 추가적으로 해당 토픽으로 메세지를 전달(Pub), 해당 토픽에서 메세지를 전달 받으면(Sub) 으로 생각하면 된다.
STOMP는 HTTTP에서 모델링이 되는 Frame기반 프로토콜이다. Frame은 몇 개의 TextLine으로 지정된 구조인데 첫 번째 라인은 Text이고 이후 Key : Value형태로 헤더의 정보를 포함한다.
COMMAND
header1 : value1
header2 : value2
Body^@
위 구조를 보면 HTTP과 유사하다.
COMMAND:
STOMP 프로토콜에서 사용할 수 있는 명령어(예: CONNECT, SEND, SUBSCRIBE, UNSUBSCRIBE 등)를 정의한다.
Header:
header1:value1 형식의 키-값 쌍으로, 각 프레임의 메타데이터 정보를 포함한다. 예를 들어, destination, content-type, content-length 등이 있다.
Body:
메시지의 실제 내용. 본문이 끝날 때는 널 문자(^@)로 마무리된다.
그리고 주요 프레임을 목록은 아래의 표와 같다.
| CONNECT / CONNECTED | 클라이언트가 서버와의 연결을 맺기 위한 프레임, 서버가 성공적으로 연결되었음을 알리기 위해 CONNECTED를 반환 |
| SEND | 메세지를 특정 목적지로 전송할 때 사용을 한다. 목적지( destination)헤더를 포함하며, 이는 토픽이나 큐의 경로를 나타낸다. |
| SUBSCRIBE / UNSUBSCRIBE | 특정 토픽이나 큐를 구독하거나 해제할 때 사용한다. 구독 시 destination헤더에 구독하려는 대상의 경로를 지정한다. |
| MESSAGE | 서버가 구독 중인 클라이언트에게 메시지를 보낼 때 사용하는 프레임 |
| ACK / NACK | 클라이언트가 받은 메시지에 대해 수신 확인(ACK) 또는 거부(NACK)를 보낼 때 사용 |
| DISCONNECT | 클라이언트가 서버와의 연결을 종료할 때 사용하는 프레임 |
기본적으로 Pub / Sub구조를 따르지만 Destination정보는 설정할 때 topic과 queue를 구분하여 사용할 수 있다.
topic -> Publish : Subscribe (1:N)
queue -> Ponit-to-Point (1:1)
을 바탕으로 아래의 예시처럼 topic과 queue를 구분하여 사용 가능하다.
destination: /topic/chat.room1
결과적으로
STOMP는 웹 소켓 위에서 동작하는 텍스트 기반 메세징 프로토콜로, 프레임 구조를 사용하여 메세지를 정의한다.Pub / Sub,Point-to-Point두 모델을 지원하며, 클라이언트와 서버 간 실시간 메세지 통신을 쉽게 구현할 수 있다.
이를 통해 채팅 시스템, 알림 시스템, 비동기 데이터 처리 등의 다양한 실시간 어플리케이션 개발에 용이하게 사용된다.