WebSocekt 작동 방식에 대한 보다 세밀한 소개는 아래의 게시글에 정리해보았다.
[프로젝트] 웹소켓으로 채팅 구현하기 #1. WebSocket
WebSocket은 HTTP와 호환되도록 설계되었으며 HTTP 요청으로 시작되지만, 두 프로토콜은 매우 다른 특성을 가지고 있다.
Sec-WebSocket-Protocol
헤더를 통해 더 높은 수준의 메시징 프로토콜(예: STOMP)을 사용해 협상할 수 있는데, 그렇지 않은 경우 따로 컨벤션을 마련해둬야 한다.웹 소켓 외에도 여러 실시간 통신이 있는데, 대략적으로 알아보자.
아래의 방법들은 모두 HTTP를 기반으로 이루어지기 때문에, 빠른 통신이 필요함에도 불구하고 Request, Response 모두 Header가 불필요하게 크다는 특징이 있다.
우리는 WebSocket을 통해 웹페이지를 동적이고 대화형으로 만들 수 있다. 그러나 AJAX와 HTTP의 Streaming, Long Polling을 결합하는 것 역시 간단하고 효율적인 방법이 될 수 있다.
실시간성을 제공하는 여러 기술들 중, 무엇을 고려하여 WebSocket의 사용을 결정할 수 있을까?
요구되는 실시간성(이는 동시에 허용 가능한 지연 시간을 의미할 수 있다.)과 메시지 양이 주요 선택 기준이 될 수 있다. 예를 들어 뉴스, 메일, 소셜 피드등 동적으로 업데이트 되어야 하나 그 업데이트의 지연 시간이 어느 정도 길게 허용 가능한 경우, WebSocekt이 아닌 다른 도구들도 효과적일것이다. 그러나 게임, 금융 앱 등은 실시간에 훨씬 가까워져야 하므로 WebSocket이 적절하다. 또한 메시지의 양이 상대적으로 적은 경우(예: 네트워크 오류 모니터링) HTTP Streming, Polling이 효과적이다.
WebSocket 사용에 가장 적합한 기준은 낮은 대기시간(높은 실시간성), 높은 빈도, 많은 메시지 양인 경우이다.
인터넷을 통한 통신에서, 제어할 수 없는 제한적인 프록시들이 WebSocket 상호작용을 방해할 수 있다. 그 이유는 Update
헤더를 전달하도록 구성되어 있지 않거나, 유휴(사용되지 않고 있는 경우) 상태로 보이는 장기 연결을 닫아버리기(끊어버리기) 때문이다.
이런 경우 SockJs를 사용해 문제를 해결할 수 있다.
앞서 언급한 것처럼, WebSocket은 HTTP와 달리 메시지 내용에 특정한 의미와 형식을 규정하지 않는다. 이는 단순히 문자열을 주고 받을 수 있게 해줄 뿐, 그 문자열의 의미와 형식이 규정되어있지 않아 애플리케이션에서 해석할 수 없음을 의미한다.
이를 위해 STOMP라는 프로토콜을 사용할 수 있다.
SockJS는 웹 브라우저와 웹 서버 간의 실시간, 양방향 통신을 가능하게 하는 JavaScript 라이브러리이다. 이는 WebSocket API를 모방하여 개발되었지만, WebSocket이 지원되지 않는 경우에도 작동할 수 있도록 다양한 통신 옵션을 제공한다.(이게 굳이 SockJS를 사용하는 큰 이유인 것 같다.) SockJS는 웹소켓 연결을 사용할 수 있는 환경에서는 웹소켓을 사용하지만, 그렇지 않은 경우에는 폴링(polling), 스트리밍(Streaming)과 같은 다른 기술을 사용해 연결을 유지한다.
SockJS 클라이언트 라이브러리와 서버 사이드 라이브러리를 함께 사용해야 한다.
추후에 실제 구현한 코드를 보며 자세히 알아보자!
STOMP는 메시지 브로커를 활용하여 메시지를 쉽게 주고받을 수 있는 프로토콜이다.
STOMP는 웹소켓 위에서 함께 사용할 수 있는 하위(서브) 프로토콜로, 웹소켓과의 결합뿐만 아니라 스프링 프레임 워크에서 웹소켓과 STOMP를 함께 사용하는 것을 지원한다는 점이 중요하다!
그러니까 스프링 프로젝트는 웹소켓이랑 스톰프 쓰면 편하다
앞서 언급했듯, 웹소켓은 단순히 문자열 전달의 기능만 가질뿐, 전송되는 문자열의 형식과 의미를 규정하지 않는다. STOMP는 커멘드, 헤더, 바디라는 형식을 규정해 두었다. 따라서 웹소켓위에 STOMP를 함께 사용하면 데이터의 형식을 고민하거나 파싱할 필요가 없다.
위와 같이 STOMP를 사용하면 자동으로 "커멘드, 헤더, 바디" 형식으로 메시지가 오가는 것을 확인할 수 있다.
먼저 개괄적으로 메시지 흐름을 요약하면 다음과 같다!
/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;
}
}
localhost:8080/protfolio
로 연결되고, 웹소켓 연결이 설정되면, STOMP 프레임(frame)이 그 위에 흐르기 시작한다./topic/greeting
목적지 헤더(destination header)와 함께 SUBSCRIBE 프레임을 전송한다. 일단 메시지가 수령되고 디코딩되면, clientInboundChannel
로 보내지고, 이후 클라이언트가 구독중인 메시지 브로커에게 전달된다./app/greeting
으로 SEND프레임을 전송한다. /app
접두사는 이를 GreetingController
컨트롤러로 전달하는 것을 돕는다. /app
접두사가 제거되면, 남아있는 /greeting
이 @MessageMapping
메서드와 매핑된다.GreetingController
에서 반환된 값은반환값을 기반으로 한 페이로드와 기본 목적지 헤더인 /topic/greeting
(/app
이 /topic
으로 대체된)으로 변환된다. 결과 메시지는 `brokerChannel
로 전달되고, 메시지 브로커에 의해 처리된다.clientOutBoundChannel
을 통해 각 구독자에게 MESSAGE 프레임을 전송한다. 이때 메시지는 STOMP 프레임으로 인코딩되어 웹소켓 연결을 통해 전송된다.