[Springboot] Websocket + stomp 를 이용한 채팅구현

이수영·2021년 12월 15일
1
post-thumbnail

📚 Websocket + Stomp 채팅 구현

✍🏻 Websocket

보통 서버에게 정보를 요청할 때 http/https 통신을 거치게 된다. 이것은 클라이언트가 서버에게 요청을 했을 때 서버가 해당하는 정보를 응답해주는 구조이다

그러나 채팅은 누군가 대화를 보내면 내가 서버에게 요청을 보내지 않아도 서버가 나 자신에게 정보를 주어야한다 이럴 때 웹소켓을 사용한다

  • 내가 원하는 정보에 대해 구독 (sub)을 하고 , 구독한 토픽에 대해 메시지를 발행하면 해당 토픽을 구독하고 있는 모든 사용자에게 메시지를 보내주는 방식이 바로 웹소켓이다.
  • http/https 통신은 같은 사용자가 서버에게 여러 번 자원이나 정보를 요청하는 경우에 매번 연결을 요청해야하며 그 때마다 요청정보를 실어보내야하는데 소켓통신은 한번 연결을 하면 연결이 유지되어 별다른 설정없이 정보를 주고 받을 수 있음

✍🏻 Stomp

📌 What is stomp?

  • stomp 는 tcp 또는 websocket 같은 양방향 네트워크 프로토콜 기반으로 동작(신뢰성 프로토콜 )

  • stomp 는 text 지향 프로토콜이나 message payload 에는 text or binary 데이터를 포함할 수 있다.

  • stomp 는 pub/sub 란 메시지를 공급하는 주체와 소비하는 주체를 분리해 제공하는 메시징 방법

  • stomp 는 http 에서 모델링되는 Frame 기반 프로토콜이다 Frame 은 몇 개의 text line 으로 지정된 구조

    COMMAND
    header1: value1
    header2: value2
    (공백)
    Body

  • COMMAND : SEND ,SUBSCRIBE 지시

  • header 의 destination 을 사용해서 이 헤더로 메시지를 보내거나 구독할 수 있음

📌 Flow

  • 채팅방 생성 : pub/sub 구현을 위한 Topic 이 생성됨
  • 채팅방 입장 : topic 구독 (/pub/chat/enter) -> topic 을 구독하고 있는 사용자들에게 template.convertAndSend("/sub/chat/room/" + message.getRoomId(), message); 로 입장메시지 보냄
  • 채팅방에서 메세지를 송수신 :
    해당 Topic 으로 메세지를 송신 (/pub/chat/message)
    해당 Topic은 자신을 구독한 사용자에게 메시지 보냄 (/sub/chat/room/{roomid})
    사용자는 stomp.subscribe(/sub/chat/room/ + roomId, callback함수) 를 통해 구독한 채팅방이 보낸 메시지 받음

  • (/app == /pub, /topic == /sub)
  • spring-message 모듈은 Spring framework의 통합된 Messaging 어플리케이션을 위한 지원
  • SimpleAnnotationMethod : @MessageMapping 등 Client의 SEND를 받아서 처리한다.
  • brokerChannel : Server 내부에서 사용하는 채널이며, 이를 통해 SimpleAnnotationMethod는 SimpleBroker의 존재를 직접 알지 못해도 메세지를 전달할 수 있다.

✍🏻 Important Code

📌 Websocket Config

@EnableWebSocketMessageBroker
@Configuration
public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer {

   @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/stomp/chat")
               // 도메인허용 .setAllowedOrigins("http://api.tildp.shop","http://www.tildp.shop","http://localhost:8080")
                .withSockJS();
    }

    /*어플리케이션 내부에서 사용할 path를 지정할 수 있음*/
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/pub");
        registry.enableSimpleBroker("/sub");
    }
}
  • setApplicationDestinationPrefixes 는 Client에서 SEND 요청을 처리한다. /pub 으로 보냄
  • enableSimpleBroker 는 해당 경로로 SimpleBroker 를 등록한다 . SimpleBroker는 해당 경로를 구독하는 client에게 메시지를 전달한다.

📌 StompChatHandler

채팅방 입장 부분 코드

@MessageMapping 을 통해 웹소켓으로 들어오는 메세지 발행을 처리한다. 클라이언트에서는 "/pub/chat/enter" 로 발행 요청을 하면 Controller가 해당 메세지를 받아 해당 주소를 구독하고 있는 사용자에게 메시지 발행

=> 메세지가 발행되면 "/sub/chat/room/[roomId]"로 메세지가 전송되는 것을 볼 수 있다.

=> Client 에서는 해당 주소를 구독하고 있다가 메시지가 전달되면 화면에 출력한다 .

=> 기존의 핸들러 ChatHandler 의 역할을 대신 해줌 핸들러 필요 없어짐

@MessageMapping(value = "/chat/enter")
    public void enter(ChatMessageDTO message) {
        String id = message.getRoomId();
        Long room_id = Long.valueOf(id);

        // chat user 정보 저장 (채팅유저 , 채팅방)
        ChatRoom chatRoom = chatRoomRepository.findByRoomId(room_id).orElseThrow(
                () -> new NullPointerException("해당 채팅방이 존재하지 않습니다."));
        User user = userRepository.findByUsername(message.getWriter()).orElseThrow(
                () -> new NullPointerException("해당 사용자가 존재하지 않습니다."));


        if (!(chatUserRepository.findByChatRoomAndUser(chatRoom, user).isPresent())) { //채팅방 처음입장
            ChatUser chatUser = new ChatUser(user, chatRoom);
            chatUserRepository.save(chatUser);
            int count  =chatUserRepository.countByChatRoom(chatRoom);

            chatRoom.setCount(count);
            chatRoomRepository.save(chatRoom);

            message.setMessage("채팅방에 참여하였습니다.");
            template.convertAndSend("/sub/chat/room/" + message.getRoomId(), message);
        }
        else
        {
            message.setMessage("채팅방에 재입장하였습니다.");
            template.convertAndSend("/sub/chat/room/" + message.getRoomId(), message);
        }
    }
  • 나는 채팅방 인원 수 구현을 위해 chatUser라는 entity 도 만들엇다
  • 채팅방 , 채팅방에 들어간 사용자의 정보를 담고있으며 사용자가 방을 구독할 때마다 ChatUser 가 생성된다.
  • 하지만 채팅방에 들어갈 때마다 해주면 중복이 발생하므로 먼저 repository 에서 존재를 파악한 후 생성해야한다.

채팅방 인원 수 구현

https://velog.io/@dltndudvlzm/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-211215-TIL

내가 오늘 잘 정리해놓은 글이다 . 참고바람 >_<

profile
Hongik Univ 💻

0개의 댓글

관련 채용 정보