본문 바로가기
Today I Learned 2024. 8. 9.

(24.08.09)[17주차] WebSocket과 실시간 메시지 구조

프로젝트를 진행하면서 사용자의 화면단과 서버의 데이터 요청응답이 필요한 기능을 사용이 필요했다.

특히, 사용자가 포함된 그룹에서 그룹내 실시간 채팅 기능 구현이 필요해지면서 WebSocket을 사용해서 구현을 했다.

 

관련해서 코드 구현방향은 해당 담당자의 개발로 진행이 되었지만, 관련해서 내가 부족할 수 있는 이론과 작동 방식에 대해서 한번더 짚고 넘어가기 위해 정리


WebSocket

  • 웹을 통한 클라이언트(브라우저) - 서버 간 실시간으로 양방향 통신을 가능하게 하는 기술
    • 즉, Stateless HTTP 통신과 달리 Stateful 프로토콜
    • 클라이언트
  • HTTP가 Request-Response 의 단방향 통신 방식임에 반해 WebSocket을 통해 한번의 연결로 클라이언트-서버 간 지속적인 양방향 통신이 가능
    • 따라서, 초기 연결을 위한 HTTP 핸드쉐이크 후에는, 지속적으로 서버에 메시지를 전달과 실시간 업데이트 등 실시간 통신 가능
    • ex) 실시간 채팅, 실시간 게임 연동, 스트리밍 등
    • 연결 후, Header에 많은 정보를 담지 않고 통신이 가능
  • 이벤트 기반의 통신
    • 서버가 요청에 의해서만 응답하는 것이 아닌, 서버의 이벤트가 발생했을 때에도 클라이언트에 Push할 수 있는 방식
      • 기존의 클라이언트가 주기적으로 서버의 응답을 받기 위해 요청을 계속 보내야했던 비효율적인 Polling 방식에서의 탈피
    • Polling 방식이 아닌 지연없는 효율적으로 빠른 실시간 응답이 가능
  • TCP 기반으로 동작하는 프로토콜
    • OSI 4계층 전송계층에서 TCP 위에서 의존하여 동작
    • TCP의 80 포트(HTTPS일 경우 443)을 똑같이 사용할 수 있음
  • ws:// 스킴을 사용
    • HTTPS 처럼 Secure일경우는 wss:// 사용

소켓 Socket

  • 네트워크 동신에서 두 프로그램 사이를 연결 수, 자유롭게 데이터교환을 수행할 수 있는 엔드포인트

엔드포인트 Endpoint

  • IP 주소 + 포트 번호를 조합한 네트워크 통신에서 출발, 또는 도착지를 명시한 추상적인 개념

실시간 채팅 구현의 기본

  • 가장 기본적인 설정을 통해서 어떻게 작동이 되는 것인지 보는 것을 직접 코드를 분석하면서 볼 수 있도록
  • 클라이언트 - 서버 통신이기 때문에 클라이언트의 JavaScript 코드도 유사한 함수로 서버에게 요청을 보내야하기 때문에 여기서는 Java 코드 기준으로 진행

환경 구성

  • WebSocket 라이브러리 Gradle 의존성 주입
implementation 'org.springframework.boot:spring-boot-starter-websocket'

Config 설정

  • WebSocket Handler를 등록하고, 관리하는 Config
  • @EnableWebSocketMessageBroker 를 통해서 WebSocketMessageBrokerConfigurer 를 구현한 어떤 Config임을 명시해야함
@Configuration
@EnableWebSocketMessageBroker
public class WebSokectConfig implements WebSocketMessageBrokerConfigurer{

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
                .setAllowedOriginPatterns("*")
                .withSockJS();
    }

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

StormEndpointRegistry

  • WebSocket을 통해 클라이언트-서버 간의 STOMP 연결을 설정, registry에 등록
  • Endpoint : 클라이언트 → 서버로 “/ws” 요청시 서버에서의 엔드포인트를 /ws 로 설정 = WebSocket을 연결
  • setAllowedOriginPatterns("*") : 모든 출처에서 CORS 요청을 허용한다는 것으로 그냥 클라이언트가 어떤 위치에서든지 해당 주소로 Endpoint를 지정할 수 있다는 예기
    • setAllowedOrigins("*") 역시 동일
  • withSockJS : WebSocket 미지원 브라우저에 대한 전송방식을 설정, 아무것도 지정하지 않을 경우 가장 우선적올 최적의 전송방법을 임의로 지정이됨
    • WebSocket, Polling 등 옵션을 제공

configureMessageBroker

  • WebSocket 메시징 시스템에서 메시지 브로커의 내용을 를 설정, registry에 등록
  • enableSimpleBroker : 클라이언트가 ()안의 prefix별 특정 주제에 대해 발행할 수 있는 간단한 메모리 기반 메시지 브로커를 활성화
    • /sub : 서버→클라이언트 메시지 발송 시, 클라이언트에서 특정 주제에 대한 메시지 수신 ex) /sub/notification 등등
      • 붙여서 보내야 클라이언트 측에서 계속적으로 알아차리고 받은 정보를 WebSocket에러 처리를 할 수 있는것
    • /topic : 다수의 메시지를 여러 클라리언트에게 동시에 전달
    • /queue: 특정 클라이언트에게 직접 메시지 수신
    • 그 밖에 /broadcast, /direct /alert 많이 있음
  • setApplicationDestinationPrefixes : 클라이언트→서버 방향으로 메시지 발송시 사용하는 경로 접두어를 설정
    • 서버가 클라이언틔 요청을 처리할 수 있는 핸들러 메서드를 구분하기 하기 위한 자유로운 설정
    • 경로매핑에 꼭필요하기 때문에 무리해서 생략하지 않도록 주의

WebSocketMessageBrokerConfigurer

  • Spring 의 메시지브로커와 관련된 설정을 담당 STOMP(Simple Text Oriented Messaging Protocol, 그냥 단순한 메시지 전달) 메시징설정
    • WebSocketConfigurer를 기본적으로 사용하여 WebSocket 핸들러를 사용
      • 단순한 연결 뿐, 메시지나 관련 STOMP 프로토콜은 사용하지 않음
  • MessageBrocker
    • 송신자 - 수신자 간 메시지 전달 중간 소프트웨어로 WebSocket 메시징 기능의 핵심 구성 요소
    • 송신자가 규칙 또는 Topic을 토대로 적절하게 메시지를 분류하여 전달
    • 큐를 통한 비동기적 처리 : 수신자가 받을 준비가 되었을 때, 받을 수 있도록 하는 것
    • 메시지 형식, 내용을 변환 또는 필터링
    • 메시지를 저앟했다가 다시 연결되었을 때, 복구 시켜서 이어서 슬 수 있도록 함
    • Transaction 관리
      • 메시지 전송 과정 자체를 트랜잭션으로 관리

Controller

  • Client에서 메시지를 담은 요청 DTO가 있다고 가정
@Controller
@RequiredArgsConstructor
public class ChatController {

   @MessageMapping("/chat/enter")
   public void enterChatroom(...) {
      ...
   }
   
   @MessageMapping("/chat/send")
   public void sendMessage(...) {
       ...
   }
   
	  @MessageMapping("/chat/notify")
    @SendTo("/sub/chat/notifications")
    public void notifyClients(ChatMessage message) {
       ...
    }
}

@MessageMapping(”경로”)

  • 클라이언트에서 특정 WebSocket 경로로 보낸 메시지를 서버 측의 메서드로 라우팅
  • 라우팅을 하는 주소는 WebSokectConfig의 setApplicationDestinationPrefixes에 의해서, Client 단에서는 “/pub/” 앞에 Prefix로 달고 요청을 보내야

@SendTo(”경로”)

  • 해당 경로를 구독하고 있는 모든 클라이언트의 WebSocket 에게 메시지를 전달할 수 있음
  • ex) 서버측에서 자동적으로 알려주는 공지 메시지 등

→ 따라서 클라이언트는 WebSokectConfig의 enableSimpleBroker에 의해서 구독하고 있는 주소가 클라이언트 단에 로드되면서 지정이 될 경우 해당 주소를 통해서 웹소켓을 통해 데이터를 주고 받을 수 있음


서비스 단의 코드 역시 작성을 하기에는 JavaScript function 관련 언어를 직접 배워서 작성을 해야하기 때문에 이를 학습에서는 생략을 했지만,

 

필드에서 만약 사용할 부분이 있다면, 화면단의 개발자들과 통일된 자료를 기반으로 기능을 구현을 할 필요가 있다고 생각한다.