먼저, 웹소켓을 사용하기 위해서 프론트엔드 측에서 사용한 라이브러리는 (Stomp.js, SockJS)입니다.

Untitled

Untitled

Stomp는 서버와 클라이언트가 웹 소켓으로 주고받는 메시지의 프로토콜 같은 건데, 어떤 메시지 형식으로 보낼 지의 클라이언트-서버 간의 약속 같은 거라고 보시면 됩니다.

가장 먼저 할 일은 페이지가 렌더링 되기 전에, (가장 초기에) socket 객체를 생성하면서 연결 도메인을 적어주고, stomp 객체에 해당 socket 객체를 연결해주는 것입니다.

//페이지 렌더링 되기 전에 웹소켓 connect
  useEffect(() => {
    socket = new SockJS("<http://localhost:8080/ws>");
    stompClient = Stomp.over(socket);
    connect();
  }, []);

이후 connect 함수를 실행하는데, connect 함수의 내용은 다음과 같습니다.

말 그대로 연결을 시작하는 함수인데, 아까 생성한 stompClient객체의 함수인 connect를 실행하고, 연결이 정상적으로 완료되면 onConnected 함수를 실행하고, 연결이 되지 않으면 Error를 콘솔에 찍도록 콜백함수를 작성해준 겁니다.

//websocket connet
  const connect = () => {
    stompClient.connect({}, onConnected, () => {
      console.log("Error");
    });
  };
//connect 되면 채팅 기록 받아오고 subscribe 열기
  const onConnected = async () => {
    await customAxios.get("/chat/room").then((res) => {
      //여기서 채팅 기록 받아오기
      setMessageList(res.data.data);
      //subscribe 주소 열기
      stompClient.subscribe("/topic/public", onMessageReceived);

      // 유저 네임을 서버에게 알리기
      stompClient.send(
        "/app/chat/addUser",
        {},
        JSON.stringify({ sender: name, type: "JOIN" })
      );

    });
  };

아까 말씀드렸듯이 연결이 정상적으로 되면 onConnected 함수가 실행되는데, 우리 채팅방은 처음 입장하기 전에 이전 대화 목록을 전부 받아와야 해서 get 요청을 통해 데이터를 먼저 받아와 줍니다. 그리고 useState로 선언했던 messageList에 해당 data 를 세팅 해주고, stompClient가 /topic/public으로 오는 모든 메시지를 수신받을 수 있도록 subscribe를 해줍니다. 메시지가 정상적으로 수신되면 onMessageReceived 함수가 실행됩니다.

onMessageReceived 함수는 뒤에 설명 드리겠습니다. subscribe를 성공적으로 완료하면 이제 해당 유저의 이름을 서버에게 알리기 위해 send 함수를 사용하여 이름과 type을 서버에 전송해줍니다. 이러면 서버는 해당 내용을 수신해서 name인 유저가 JOIN 했구나 알아채고 유저 정보를 기록해둡니다.

이제 아까 말씀드린 onMessageReceived 함수입니다.

//message 수신 받을 때
  const onMessageReceived = (payload) => {
    var message = JSON.parse(payload.body);

    if (message.type === "JOIN") {
      console.log("JOIN!!!");
      setMessageList((prev) => [...prev, message.content]);
    } else if (message.type === "LEAVE") {
      console.log("LEAVE!!");
      setMessageList((prev) => [...prev, message.content]);
    } else {
      console.log("Chatting: ", message.content);
      setMessageList((prev) => [...prev, message.content]);
      console.log("채팅 받은 이후", messageList);
    }
  };

수신 받은 데이터의 Type에 따라 동작을 다르게 나눠 놓은 겁니다. 대부분 messageList에 저장하는 거라 나누지 않아도 되지만, 나중에 Type에 따라 다른 기능을 넣을 것을 고려하여 나눠 놓았습니다.

그리고 마지막으로 이제 유저가 메시지를 보낼 때는 sendMessage 함수가 발동됩니다.

const sendMessage = () => {
    if (chat && stompClient) {
      let chatMessage = {
        sender: name,
        content: chat,
        type: "CHAT",
      };
      stompClient.send(
        "/app/chat/sendMessage",
        {},
        JSON.stringify(chatMessage)
      );
      setChat("");
    }
  };

  const onSubmit = () => {
    sendMessage();
  };

여기서 chat은 사용자가 input에 입력한 value 값입니다. 이것도 심플하게 서버랑 약속한 주소에 CHAT type으로 메시지 내용과 유저 name을 전달해주는 겁니다. 해당 메시지를 받은 서버는 내용을 DB에 저장하고 다시 /topic/public으로 메시지를 send합니다. 그러면 아까 subscribe를 /topic/public으로 열어놨던 클라이언트가 해당 메시지를 수신할 수 있는 겁니다.