[프로젝트] 웹소켓으로 채팅 구현하기 #2. 웹소켓 & STOMP

bien·2024년 3월 10일
1

LegendsOfLeague

목록 보기
2/4

1. 웹소켓(WebSocket)이란?

  • 웹소켓이란 두 프로그램 간의 메시지를 교환하기 위한 통신 방법 중 하나이다.
  • WebSocket 프로토콜인 RFC 6455단일 TCP 연결을 통해 클라이언트와 서버 간에 전이중 양방향 통신 채널을 설정하는 표준화된 방법을 제공한다.
    • 이는 HTTP와 다른 TCP 프로토콜이지만 포트 80및 443을 사용하고 기존 방화벽 규칙을 재사용할 수 있도록 HTTP를 통해 작동하도록 설계되었다.
  • 성공적인 핸드셰이크 후에는 HTTP 업그래이드 요청의 기반이 되는 TCP 소켓이 클라이언트와 서버 모두에 대해 계속 열려 메시지를 계속 보내고 받을 수 있다.

WebSocekt 작동 방식에 대한 보다 세밀한 소개는 아래의 게시글에 정리해보았다.
[프로젝트] 웹소켓으로 채팅 구현하기 #1. WebSocket

1) 특징

  • 양방향 통신 (Full-Deplex)
    • 데이터 송수신을 동시에 처리할 수 있는 통신 방법
    • 클라이언트와 서버가 서로에게 원할 때 데이터를 주고 받을 수 있다.
    • 통상적인 Http 통신은 Client가 요청을 보내는 경우에만 Server가 응답하는 단방향 통신
  • 실시간 네트워킹 (Real Time-Networking)
    • 웹 환경에서 연속된 데이터를 빠르게 노출
      • Ex) 채팅, 주식, 비디오 데이터
    • 여러 단말기에 빠르게 데이터를 교환
  • 주요 특징
    • 초기 연결의 HTTP 활용: 최초 접속에서만 http 프로토콜 위에서 handshaking을 하기 때문에 http header를 사용한다.
    • 표준 포트 사용: 웹소켓을 위한 별도의 포트는 없으며, 기존 포트(http-80, https-443)을 사용한다.
    • 프레임 기반 메시징: 데이터는 프레임이라는 논리적 단위로 송수신된다. 이는 메시지를 효과적으로 구성하고 관리할 수 있게 해준다.
    • 텍스트와 바이너리 2종류의 데이터 형식을 지원한다.

2) HTTP vs WebSocket

WebSocket은 HTTP와 호환되도록 설계되었으며 HTTP 요청으로 시작되지만, 두 프로토콜은 매우 다른 특성을 가지고 있다.

  • HTTP
    • HTTP와 REST에서 애플리케이션은 많은 URL로 설계되며, 애플리케이션과 상호 작용하기 위해 클라이언트는 요청(request)-응답(response) 스타일로 해당 URL에 액세스 한다.
    • 서버는 HTTP URL, 메소드 및 헤더를 기반으로 적절한 핸들러로 요청을 라우팅한다.
  • WebSocket
    • 일반적으로 초기 연결에 대한 URL 하나만 가지며, 이후에는 모든 애플리케이션 메시지가 동일한 TCP 연결을 통해 전달된다.
      • 이는 완전히 다른 비동기식 이벤트 중심 메시징 아키텍처를 나타낸다.
    • HTTP와 달리 메시지 내용에 어떤 의미도 규정하지 않는 낮은 수준의 전송프로토콜이다.
      • 즉, 클라이언트와 서버가 메시지 의미에 동의하지 않으면 메시지를 라우팅하거나 처리할 방법이 없다.
      • WebSocket을 사용하는 클라이언트와 서버가 HTTP 핸드셰이크 요청의 Sec-WebSocket-Protocol 헤더를 통해 더 높은 수준의 메시징 프로토콜(예: STOMP)을 사용해 협상할 수 있는데, 그렇지 않은 경우 따로 컨벤션을 마련해둬야 한다.

2. 또 다른 실시간 통신 기술들

웹 소켓 외에도 여러 실시간 통신이 있는데, 대략적으로 알아보자.

아래의 방법들은 모두 HTTP를 기반으로 이루어지기 때문에, 빠른 통신이 필요함에도 불구하고 Request, Response 모두 Header가 불필요하게 크다는 특징이 있다.

Polling

  • 서버로 일정 주기마다 요청을 송신한다.
  • 실시간(real-time) 통신에서는 언제 통신이 발생할지 예측이 불가능하다. 또한 불필요한 request와 connection을 생성해야 한다.
  • 실시간 통신이라 부르기 애매할 정도의 동시성을 가지고 있다.

Long Polling

  • 서버에 요청을 보내고 이벤트가 생겨 응답을 받을 때까지 연결을 종료하지 않는다.
    • 응답을 받으면 연결을 종료하고 다시 요청한다.
  • 많은 양의 에너지가 쏟아질 경우 사실상 polling과 거의 유사한 수준이다.

Streaming

  • 서버에 요청을 보내고 끊기지 않은 연결상태에서 끊임없이 데이터를 수신한다.
  • 클라이언트에서 서버로의 데이터 송신이 어렵다.

3. WebSocket을 사용해야 하는 경우

우리는 WebSocket을 통해 웹페이지를 동적이고 대화형으로 만들 수 있다. 그러나 AJAX와 HTTP의 Streaming, Long Polling을 결합하는 것 역시 간단하고 효율적인 방법이 될 수 있다.

실시간성을 제공하는 여러 기술들 중, 무엇을 고려하여 WebSocket의 사용을 결정할 수 있을까?

요구되는 실시간성(이는 동시에 허용 가능한 지연 시간을 의미할 수 있다.)과 메시지 양이 주요 선택 기준이 될 수 있다. 예를 들어 뉴스, 메일, 소셜 피드등 동적으로 업데이트 되어야 하나 그 업데이트의 지연 시간이 어느 정도 길게 허용 가능한 경우, WebSocekt이 아닌 다른 도구들도 효과적일것이다. 그러나 게임, 금융 앱 등은 실시간에 훨씬 가까워져야 하므로 WebSocket이 적절하다. 또한 메시지의 양이 상대적으로 적은 경우(예: 네트워크 오류 모니터링) HTTP Streming, Polling이 효과적이다.

WebSocket 사용에 가장 적합한 기준은 낮은 대기시간(높은 실시간성), 높은 빈도, 많은 메시지 양인 경우이다.


4. 웹 소켓의 한계

WebSocket을 지원하지 않는 환경이 있을 수 있다.

인터넷을 통한 통신에서, 제어할 수 없는 제한적인 프록시들이 WebSocket 상호작용을 방해할 수 있다. 그 이유는 Update 헤더를 전달하도록 구성되어 있지 않거나, 유휴(사용되지 않고 있는 경우) 상태로 보이는 장기 연결을 닫아버리기(끊어버리기) 때문이다.

이런 경우 SockJs를 사용해 문제를 해결할 수 있다.

WebSocket은 낮은 수준의 전송 프로토콜이다.

앞서 언급한 것처럼, WebSocket은 HTTP와 달리 메시지 내용에 특정한 의미와 형식을 규정하지 않는다. 이는 단순히 문자열을 주고 받을 수 있게 해줄 뿐, 그 문자열의 의미와 형식이 규정되어있지 않아 애플리케이션에서 해석할 수 없음을 의미한다.

이를 위해 STOMP라는 프로토콜을 사용할 수 있다.


5. SockJS

SockJS는 웹 브라우저와 웹 서버 간의 실시간, 양방향 통신을 가능하게 하는 JavaScript 라이브러리이다. 이는 WebSocket API를 모방하여 개발되었지만, WebSocket이 지원되지 않는 경우에도 작동할 수 있도록 다양한 통신 옵션을 제공한다.(이게 굳이 SockJS를 사용하는 큰 이유인 것 같다.) SockJS는 웹소켓 연결을 사용할 수 있는 환경에서는 웹소켓을 사용하지만, 그렇지 않은 경우에는 폴링(polling), 스트리밍(Streaming)과 같은 다른 기술을 사용해 연결을 유지한다.

  • HTML5 이전의 기술로 구현된 서비스에서 웹소켓처럼 사용할 수 있도록 도와주는 기술
  • JavaScript를 이용하여 브라우저 종류에 상관없이 실시간 웹을 구현
  • WebSocket, FlashSocket, AJAX Long Polling, AJAx Mutlipart Streaming, IFrame, JSONP POlling을 하나의 API로 추상화
    • 즉, 브라우저와 웹 서버의 종류와 버전을 파악하여 가장 적합한 기술을 선택하여 사용하는 방식이다.

사용법

SockJS 클라이언트 라이브러리와 서버 사이드 라이브러리를 함께 사용해야 한다.

  • 클라이언트 측에서는 SockJS 객체를 생성하여 서버와의 연결을 시작해야 한다.
  • 서버 측에서는 SockJS 서버를 구성하여 클라이언트의 연결을 수락하고 처리해야 한다.

추후에 실제 구현한 코드를 보며 자세히 알아보자!


6. STOMP(Simple Text Oriented Messaginig Protocol)

STOMP는 메시지 브로커를 활용하여 메시지를 쉽게 주고받을 수 있는 프로토콜이다.

  • Pub – Sub(발행 – 구독): 발신자가 메시지를 발행하면 수신자가 그것을 수신하는 메시징 패러다임
    • 발신자가 어떤 범주(path, 경로)로 메시지를 발행하면 이 범주를 구독하고 있는 수신자들은 그 메시지를 받아볼 수 있다.
  • 메시지 브로커: 발신자의 메시지를 받아와서 수신자들에게 메시지를 전달하는 어떤 것

STOMP는 웹소켓 위에서 함께 사용할 수 있는 하위(서브) 프로토콜로, 웹소켓과의 결합뿐만 아니라 스프링 프레임 워크에서 웹소켓과 STOMP를 함께 사용하는 것을 지원한다는 점이 중요하다!
그러니까 스프링 프로젝트는 웹소켓이랑 스톰프 쓰면 편하다

STOMP 예시

앞서 언급했듯, 웹소켓은 단순히 문자열 전달의 기능만 가질뿐, 전송되는 문자열의 형식과 의미를 규정하지 않는다. STOMP는 커멘드, 헤더, 바디라는 형식을 규정해 두었다. 따라서 웹소켓위에 STOMP를 함께 사용하면 데이터의 형식을 고민하거나 파싱할 필요가 없다.

위와 같이 STOMP를 사용하면 자동으로 "커멘드, 헤더, 바디" 형식으로 메시지가 오가는 것을 확인할 수 있다.

장점

  • 웹소켓 하위 프로토콜을 따로 정의해줄 필요가 없다. (편하다)
  • 연결 주소마다 새로 핸들러를 구현하고 설정해줄 필요가 없다. (편하다)
  • Spring Security를 활용할 수 있다. (안전하다)

사용법

먼저 개괄적으로 메시지 흐름을 요약하면 다음과 같다!

  • 발신자는 구독자들에게 메시지를 보내고 싶어한다.
  • 구독자들은 /topic이라는 경로를 구독하고 있다.
  • 서버내에서 처리, 가공이 필요한 경우
    • /aap이라는 경로로 보내고, 서버는 메시지를 가공한 뒤 다시 /topic으로 메시지를 전송한다.
    • 메시지 브로커는 이 전달받은 메시지를 구독자들에게 전달한다.
  • 서버내의 가공이 필요없는 경우
    • 바로 메시지 브로커를 통해 메시지를 전송한다.

실제 코드로 확인하는 메시지 흐름

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		registry.addEndpoint("/portfolio");
	}

	@Override
	public void configureMessageBroker(MessageBrokerRegistry registry) {
		registry.setApplicationDestinationPrefixes("/app");
		registry.enableSimpleBroker("/topic");
	}
}

@Controller
public class GreetingController {

	@MessageMapping("/greeting")
	public String handle(String greeting) {
		return "[" + getTimestamp() + ": " + greeting;
	}
}
  1. 클라이언트가 localhost:8080/protfolio로 연결되고, 웹소켓 연결이 설정되면, STOMP 프레임(frame)이 그 위에 흐르기 시작한다.
  2. 클라이언트가 /topic/greeting 목적지 헤더(destination header)와 함께 SUBSCRIBE 프레임을 전송한다. 일단 메시지가 수령되고 디코딩되면, clientInboundChannel로 보내지고, 이후 클라이언트가 구독중인 메시지 브로커에게 전달된다.
  3. 클라이언트가 /app/greeting으로 SEND프레임을 전송한다. /app 접두사는 이를 GreetingController 컨트롤러로 전달하는 것을 돕는다. /app 접두사가 제거되면, 남아있는 /greeting@MessageMapping 메서드와 매핑된다.
  4. GreetingController에서 반환된 값은반환값을 기반으로 한 페이로드와 기본 목적지 헤더인 /topic/greeting(/app/topic으로 대체된)으로 변환된다. 결과 메시지는 `brokerChannel로 전달되고, 메시지 브로커에 의해 처리된다.
  5. 메시지 브로커는 모든 구독자를 찾고 clientOutBoundChannel을 통해 각 구독자에게 MESSAGE 프레임을 전송한다. 이때 메시지는 STOMP 프레임으로 인코딩되어 웹소켓 연결을 통해 전송된다.

Reference

profile
Good Luck!

0개의 댓글