공부하고 기록하는, 경제학과 출신 개발자의 노트

프로그래밍/이것저것_개발일지

Java WebSocket과 Stomp로 간단한 채팅프로그램 만들기

inspirit941 2021. 2. 4. 12:12
반응형

WebSocket

  • Http처럼 서버와 클라이언트 간 양방향 통신을 제공하는 프로토콜.
  • 한 번 connection이 맺어지면, 서버 또는 클라이언트가 connection을 종료하기 전까지 계속 통신이 가능하다.
  • 따라서 지속적으로 서버와 클라이언트가 high frequency / low latency로 통신해야 하는 경우 http보다 websocket 프로토콜이 유리하다.

스크린샷 2021-02-02 오후 7 13 09

  1. url로 topic을 지정한 채 메시지를 전송
  2. 해당 메시지가 Message Broker로 도달 (Simple Broker)
  3. Message Broker는 해당 토픽에 대응되는 response channel로 Route
  4. receiver가 메시지 수신.

이 과정을 진행하기 위해서는 Stomp라는 프로토콜이 추가로 필요.

Stomp

  • WebSocket은 단지 통신 프로토콜일 뿐이다. 특정 토픽의 subscriber에게만 / 특정 user에게만 메시지를 보내는 방법을 담당하는 프로토콜이 Stomp
  • Streaming Text Oriented Messaging Protocol로, 프로토콜을 지원하는 Message Broker와 stomp client 간 통신을 지원한다.
  • Spring은 default로 Support. 다른 messaging protocol - RabbitMQ / ActiveMQ 등도 사용 가능함.

 


먼저, WebSocket Config 클래스를 생성한다.

package com.inspirit941.websocketexample.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WsConfig implements WebSocketMessageBrokerConfigurer {
    // 웹소켓 config. @Enable... 어노테이션 + WebSockekMsgBrokerConfigurer 구현체.
    // 메시지를 중개 / 라우팅하는 브로커 설정.
    // 아래 두 개의 메소드 오버라이딩이 필요함.

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // stomp에서 prefix URL 적용하는 부분
        registry.addEndpoint("/testEndpoint").withSockJS();
        // withSockJS 사용시의 장점:
        // websocket 형태로 연결이 불가능한 경우 http를 사용해서 연결이 지속되도록 만든다는 듯.
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic");
        registry.setApplicationDestinationPrefixes("/app");
    }
}
  • EndPoint : 서버와 클라이언트가 WebSocket 통신을 하기 위한 엔드포인트라고 생각하면 된다. 클라이언트 측에서 Socket을 생성할 때, 여기에 정의한 문자열로 생성해야 통신이 된다.
  • enableSimpleBroker: 일종의 topic. 해당 문자열로 시작하는 message 주소값을 받아서 처리하는 Broker를 활성화한다
  • ApplicationDesinationPrefixes: 클라이언트가 서버로 메시지를 보낼 때 붙여야 하는 url prefix.

간단한 메시지 클래스를 생성한다.

package com.inspirit941.websocketexample.model;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class Message {
    private String content;
    private String sender;
    private MessageType type;

    public enum MessageType {
        CHAT, LEAVE, JOIN
    };
}

이제, 클라이언트에게서 메시지를 받아 처리할 Controller를 생성한다.

package com.inspirit941.websocketexample.controller;

import com.inspirit941.websocketexample.model.Message;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Controller;

@Controller
public class ChatController {

    // add user to the chat / send message
    // @Payload : WebSocket에서 전송하는 데이터를 받기 위한 어노테이션.
    // capture the username (who will chat / currently in join state)
    @MessageMapping("/chat.register")  // map the same url from client to server
    @SendTo("/topic/public")
    // specify queue. (request / response. based on url). 여기서는 config에서 enableSimpleBroker에서 등록한 거
    public Message register(
            @Payload Message msg,
            SimpMessageHeaderAccessor headerAccessor
            ) {
        // 누가 보냈는지 정보 담기
        headerAccessor.getSessionAttributes().put("username", msg.getSender());
        return msg;
    }

    @MessageMapping("/chat.send")
    @SendTo("/topic/public")
    public Message sendMessage(@Payload Message msg) {
        return msg;
    }
}

 

  • /chat.register 라는 url로 message가 올 경우, 해당 메시지를 포함한 세션에 username을 key값으로, sender를 value값으로 저장한 뒤 리턴한다.
  • @SendTo로 static하게 토픽을 구독한 사람 전원에게 메시지를 보낼 수도 있지만, SimpMessageSendingOperations의 convertAndSend 메소드를 사용하면 @Payload로 넘어온 메시지 내부의 값을 토픽으로 활용할 수 있다.
  • 위의 로직은
    • /chat.register로 '00님이 입장하였습니다' 라는 채팅을 참여자 전체에게,
    • /chat.send로 참여한 사람들에게 입력한 메시지를 전송하는 거라고 보면 된다.

 

프론트에서의 코드 예시를 보면

function connect(event) {
    username = document.querySelector('#name').value.trim();

    if(username) {
        usernamePage.classList.add('hidden');
        chatPage.classList.remove('hidden');

        var socket = new SockJS('/testEndpoint');
        stompClient = Stomp.over(socket);

        stompClient.connect({}, onConnected, onError);
    }
    event.preventDefault();
}


function onConnected() {
    // Subscribe to the Public Topic
    stompClient.subscribe('/topic/public', onMessageReceived);

    // Tell your username to the server
    stompClient.send("/app/chat.register",
        {},
        JSON.stringify({sender: username, type: 'JOIN'})
    )

    connectingElement.classList.add('hidden');
}
  1. 처음에 /testEndpoint로 socket을 연결하고
  2. /topic/public을 subscribe한 후
  3. /app/char.register 메시지로 메시지를 보낸다.
function send(event) {
    var messageContent = messageInput.value.trim();

    if(messageContent && stompClient) {
        var chatMessage = {
            sender: username,
            content: messageInput.value,
            type: 'CHAT'
        };

        stompClient.send("/app/chat.send", {}, JSON.stringify(chatMessage));
        messageInput.value = '';
    }
    event.preventDefault();
}

추가로 메시지를 보낼 때에는 /app/chat.send 로 보내는 것을 확인할 수 있다.

 


원본 영상은 이곳을 참고했다. 영어발음이 쾌적하지 못해서 알아듣기는 힘들지만, 설명한 내용은 거의 다 담았다.

www.youtube.com/watch?v=4Hyv4M1kFeM

원본 소스코드는 여기

github.com/Java-Techie-jt/Spring-Boot-WebSocket

 

Java-Techie-jt/Spring-Boot-WebSocket

Building a chat application using Spring Boot and Web Socket - Java-Techie-jt/Spring-Boot-WebSocket

github.com

 

 

반응형