Web Socket, STOMP 알아보자

시연·2024년 10월 13일
0

학교 전공 동아리 프로젝트를 진행하면서 Web Socket에 대해 공부해야했다.
실시간 통신을 통한 코딩 배틀 서비스를 기획하고 있었으니 당연했다.
백엔드 친구의 권유로 WebSocket과 STOMP를 함께 사용하게 되어 백엔드 친구에게 STOMP 개념 강의를 들었다.
블로그도 찾아보며 이해한 내용을 바탕으로 개념을 정리해보자 한다!


Web Socket이란?

⭐️ 서버와 클라이언트 간의 지속적인 연결을 제공하는 프로토콜이다.

특징

  • 양방향 통신 (Full-Duplex)
    • 데이터 송/수신을 동시에 처리할 수 있다.
    • 클라이언트가 요청을 보낼 때만 서버가 응답하는 단방향 통신인 http 통신과는 달리, 클라이언트와 서버가 서로 원하는 순간에 데이터를 주고 받을 수 있다.
  • 실시간 네트워킹 (Real-Time Networking)
    • 연속된 데이터를 빠르게 노출시킬 수 있다.
  • TCP(Transmission Control Protocol)를 기반
    • 신뢰성있는 데이터 전송을 보장한다.
    • 메시지 경계를 존중해준다.
    • 순서가 보장된 양방향 통신 제공한다.

요청

위 사진과 같이 WebSocket은 초기 연결을 위한 하나의 연결만 사용한다.
그 후에는 모든 에플리케이션 메세지는 같은 tcp connection을 가지고 주고 받게 된다.

응답

위 사진과 같은 요청을 받은 WebSocket을 지원하는 서버는 200 상태코드 대신 101 상태코드를 반환한다.

데이터 전송

프로토콜이 http/https에서 ws/wss로 변경된다.

연결 종료

클라이언트 혹은 서버가 연결 종료를 위한 Close Frame을 전송하고 요청을 받은 쪽에서 응답으로 Close Frame을 전송해 WebSocket 연결을 종료한다.




STOMP (Simple Text Oriented Messaging Protocol)

💡 Frame 기반의 서브 프로토콜 (sub-protocol)이다.

  • Web Socket 자체는 메시지를 주고받는 형식이 정해져있지 않지만 STOMP는 클라이언트와 서버가 주고받는 메세지의 형태를 약속한다.
  • TCP, Web Socket과 같은 양방향 네트워크 프로토콜에서 사용될 수 있다.

메세지의 발행자구독자가 존재하고 메세지를 보내는 사람과 받는 사람이 구분되어 있다.

  • Publish-Subscribe 구조로 이루어져 있다.
    • Publisher : 메세지를 공급하는 주체이
    • Subcriber : 메새지를 받는 주체
  • Message Broker : 발행자가 메세지를 발행하면 구독자가 발행된 메세지를 수신하도록 메세지를 전달하는 주체

흐름

  1. 구독자들이 특정 경로를 구독한다.
  2. 발행자가 메세지를 송신한다.
  3. 메세지가 서버 내에서 처리되거나 혹은 즉각적으로 Message Broker에게 전달된다.
  4. Message Broker는 특정 경로를 구독하고 있는 모든 구독자들에게 전달받은 메시지를 송신한다.

예제

import { useEffect, useState, useRef } from "react";
import { Client, IMessage } from "@stomp/stompjs";
import SockJS from "sockjs-client";

const WebSocketComponent = () => {
  const [message, setMessage] = useState<string>(""); // 서버에서 수신한 메시지
  const [inputMessage, setInputMessage] = useState<string>(""); // 사용자 입력 메시지
  const clientRef = useRef<Client | null>(null); // STOMP 클라이언트
  const [isConnected, setIsConnected] = useState<boolean>(false);

  // WebSocket 연결 설정
  const connectWebSocket = () => {
    const socket = new SockJS("http://localhost:8080/ws"); // WebSocket 서버 주소
    const stompClient = new Client({
      webSocketFactory: () => socket,
      debug: (str) => {
        console.log(str); // 디버그 로그 출력
      },
      onConnect: (frame) => {
        console.log("Connected to WebSocket:", frame);
        setIsConnected(true);

        // 메시지 구독
        stompClient.subscribe("/topic/messages", (msg: IMessage) => {
          const body = msg.body;
          console.log("Received message:", body);
          setMessage(body); // 수신 메시지를 상태로 저장
        });
      },
      onStompError: (frame) => {
        console.error("STOMP error:", frame.headers.message);
      },
    });

    stompClient.activate(); // WebSocket 연결 활성화
    clientRef.current = stompClient; // 클라이언트 참조 저장
  };

  // WebSocket 연결 해제
  const disconnectWebSocket = () => {
    if (clientRef.current) {
      clientRef.current.deactivate(); // WebSocket 연결 해제
      console.log("Disconnected from WebSocket");
    }
    setIsConnected(false);
  };

  // 메시지 전송
  const sendMessage = () => {
    if (clientRef.current && inputMessage.trim() !== "") {
      clientRef.current.publish({
        destination: "/app/chat", // 서버에 메시지 보낼 경로
        body: inputMessage, // 전송할 메시지
      });
      setInputMessage(""); // 입력 필드 초기화
    }
  };

  useEffect(() => {
    connectWebSocket(); // 컴포넌트가 마운트될 때 WebSocket 연결

    return () => {
      disconnectWebSocket(); // 컴포넌트가 언마운트될 때 WebSocket 연결 해제
    };
  }, []);

  return (
    <div>
      <h1>WebSocket with STOMP Example</h1>
      <div>
        <p>Connection status: {isConnected ? "Connected" : "Disconnected"}</p>
      </div>

      <div>
        <label>Message from Server: </label>
        <div>{message}</div>
      </div>

      <div>
        <input
          type="text"
          value={inputMessage}
          onChange={(e) => setInputMessage(e.target.value)}
          placeholder="Enter message"
        />
        <button onClick={sendMessage} disabled={!isConnected}>
          Send Message
        </button>
      </div>

      <div>
        <button onClick={disconnectWebSocket} disabled={!isConnected}>
          Disconnect WebSocket
        </button>
      </div>
    </div>
  );
};

export default WebSocketComponent;

이해한 내용을 바탕으로 예제코드도 한번 보면 좋을 것 같아서 가져와 봤습니다.
코드도 어렵지 않으니 읽어보는 것을 추천드립니다!




느낀점

백엔드 친구에게 들은 강의로 대충 흐름은 알고 있었지만 정리하고 또 공부하면서 메세지 브로커 라는 주체가 있다는 것을 처음 알았다.
STOMP를 처음 접했을때 정말 충격적이였다. 그와 동시에 WebSocket과 정말 편리하고 유용하게 사용될 것 같았다. 코드를 짤때도 수월했다.
신세계를 경험했다.
처음 해보는 실시간 통신인데 어렵지 않고 오히려 재밌는 경험을 한 것 같아 기분이 째진다.
이제 본격적인 실시간 통신 개발만 남았는데 으쌰으쌰 힘내서 다 끝내보자 화이띵!




참고한 블로그

https://innu3368.tistory.com/213
https://growth-coder.tistory.com/157
https://velog.io/@mw310/Stomp-WebSocket-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%ACver-Spring

profile
Frontend Developer

0개의 댓글