[SW정글 116일차] 생각보다 오래 걸린 채팅

rg.log·2023년 1월 11일
0

SW 사관학교 JUNGLE

목록 보기
26/31

나만무 시작한지 벌써 3주가 흘렀다. react-native와 spring framework를 사용해서 범용적인 서비스를 만들겠다고 하던 패기 넘치는 우리팀이 share extension이라는 입문자에게는 높았던 허들을 넘어 구현을 진행해나가고 있다. 최근에는 채팅을 구현했는데 처음해보는 거라 신기하고 바로바로 결과가 보여 재밌었다.

이전에 고민했던대로 채팅 서버는 Stomp와 SockJS, MySQL을 사용해 구현했다.

시행착오 및 적용

Stomp와 클라언트는 vue.js를 통해 적용하는 예시로 처음 WebSocket의 흐름을 파악했다.

네트워크 통신되는 것을 하나하나 분석해서 흐름을 잡아갔다.

vue.js를 처음 사용해보며 만난 Whitelabel Error Page 오류는 application.yml에서 mvc.view.suffix: .html 설정으로 해결해주었다.

또한 favicon, vue 파일 등 403 오류로 화면 안뜨면, spring security (security config 파일) 해당 uri 권한 허가로 해결했다.

위와 같이 서버사이드로 테스트를 해본 후 실제 클라이언트를 두고 진행한 코드는 아래와 같다.

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override // messageBroker는 송신자에게 수신자의 이전 메세지 프로토콜 변환해주는 모듈 중 하나. 요청 오면 해당하는 통신 채널로 전송
    public void registerStompEndpoints(StompEndpointRegistry registry) { // 최초 소켓 연결시 endpoint
        registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS(); // react-native에서 SockJS생성자를 통해 연결
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/sub"); // 메세지 응답 prefix
        registry.setApplicationDestinationPrefixes("/pub"); // 클라이언트에서 메세지 송신시 붙여줄 prefix
    }
}
  • @EnableWebSocketMessageBroker
    WebSocket 서버를 사용한다는 설정이다.
    pub/sub라는 메시지를 공급하는 주체와 소비하는 주체를 분리하여 제공하는 메시징 방법이다.

    🧑🏻‍🎨publisher(집배원) → 📮topic(우체통) → 💁🏻‍♀️subscriber(구독자 : 여러명 가능)

    마치 집배원이 우체통에 배달하면 구독자는 배달을 기다렸다 빼보는 것처럼
    0. 채팅방을 생성한다 (topic 생성) 1. 채팅방 입장한다 (topic 구독) 2. 채팅방에서 메시지 보내고(pub), 받는다(sub)

  • /ws
    WebSocket 또는 SockJS Client가 웹소켓 핸드세이크 커넥션을 생성할 경로이다.

  • registry.enableSimpleBroker
    메세지 브로커를 등록한다. /sub 으로 시작하는 Stomp 메세지의 "destination" 헤더는 @Controller 객체의 @MessageMapping 메서드로 라우팅된다.

  • registry.setApplicationDestinationPrefixes
    도착 경로에 대한 prefix를 설정한다.

@Slf4j
@Controller
@RequiredArgsConstructor
public class MessageController {
    private final ChatService chatService;
    private final SimpMessagingTemplate template;

    @MessageMapping("/chat")
    public void sendMessage(ChatMessageRequestDto dto){
        ChatMessageResponseDto responseDto = chatService.saveChatMessage(dto);
        this.template.convertAndSend("/sub/chat/" + responseDto.getChatRoomId(), responseDto);
    }
}
  • SimpMessagingTemplate
    @EnableWebSocketMessageBroker를 통해서 등록되는 bean으로써 특정 Broker로 메시지를 전달한다.

  • @MessageMapping
    Client가 SEND를 할수 있는 경로다. WebSocketConfig에서 등록한 setApplicationDestinationPrefixes와 @MessageMapping의 경로가 합쳐진다. (/pub/chat)

채팅시 내부 동작을 그림으로 표현하면 아래와 같다.


아래는 클라이언트인 react-native 코드이다.

const ChatRoom = ({navigation, route}) => {
  console.log('***route.parmas are : ', route.params);
  const insets = useSafeAreaInsets();
  const {
    nickName,
    addFriendList,
    shareCollectionTitle,
    shareCollectionId,
    chatRoomId,
  } = route.params;
  const client = useRef({});

  const connect = roomId => {
    client.current = new Client({
      brokerURL: 'wss://api.sendwish.link:8081/ws',
      connectHeaders: {},
      webSocketFactory: () => {
        return SockJS('https://api.sendwish.link:8081/ws');
      },
      onConnect: () => {
        subscribe(roomId);
      },
      onStompError: frame => {
        console.log('error occur' + frame.body);
      },
    });
    client.current.activate();
  };
  const disconnect = () => {
    console.log('here is disconnect!');
    client.current.deactivate();
  };
  const subscribe = roomId => {
    console.log('connected!');
    client.current.subscribe('/sub/chat/' + roomId, msg => {
      console.log(JSON.parse(msg.body));
    });
    client.current.publish({
      destination: '/pub/chat',
      body: JSON.stringify({
        roomId: roomId,
        sender: 'hcs4125',
        message: '',
        type: 'ENTER',
      }),
    });
  };
  const publish = roomId => {
    console.log('here is publish');
    if (!client.current.connected) {
      return;
    }
    client.current.publish({
      destination: '/pub/chat',
      body: JSON.stringify({
        roomId: roomId,
        sender: 'hcs4125',
        message: 'test',
        type: 'TALK',
      }),
    });
  };

useEffect(() => {
    connect(chatRoomId);
  }, []);
  return (
    <Container insets={insets}>
      <UpperContainer>
        <Ionic name="chevron-back" size={25} color={theme.basicText} />
        <View
          style={{
            justifyContent: 'center',
            alignItems: 'center',
            width: '40%',
          }}>
          <MainTitle>bulksup</MainTitle>
        </View>
        <Feather
          name="menu"
          size={30}
          style={{
            color: theme.basicText,
          }}
        />
      </UpperContainer>
      <Temp>
        <MiddleContainer>
          <MySaying />
          <OthersSaying />
        </MiddleContainer>
        <BottomContainer>
          <InputContainer>
            <Input />
            <TouchableOpacity
              onPress={() => {
                publish(chatRoomId);
              }}>
              <SendIcon>
                <Feather
                  name="arrow-up"
                  size={30}
                  style={{
                    color: theme.mainBackground,
                  }}
                />
              </SendIcon>
            </TouchableOpacity>
          </InputContainer>
        </BottomContainer>
      </Temp>
    </Container>
  );
};

export default ChatRoom;

참고. STOMP DB 설계
웹소켓 테스트

드디어 채팅 끝!! 🎊
추가 구현위해 달리자!

0개의 댓글