- 네트워크에서 두 컴퓨터 간의 ‘실시간 양방향 통신’을 제공하는 기술을 의미합니다. 양방향 통신은 송신자 측이 데이터를 수신할 수도 있으며, 수신자 측이 데이터를 송신할 수 있는 구조를 가집니다.
- 두 컴퓨터 간에는 '특정 IP 주소와 포트번호'의 조합으로 네트워크 간의 연결을 수행하며, 수신자(클라이언트)는 데이터를 요청하면 송신자(서버)에서는 요청에 대한 응답을 제공해 주는 '클라이언트-서버' 모델을 사용하여 데이터를 송수신할 수 있습니다. - 소켓 통신은 'TCP'와 'UDP'라는 두 가지 주요 프로토콜을 사용할 수 있습니다. - 이러한 프로토콜을 통해 웹 서버, 이메일 서버, 데이터베이스 서버 등 다양한 응용 프로그램에서 사용되며, 실시간 통신, 스트리밍, 온라인 게임 등에서도 사용됩니다.
2) STOMP(Simple or Streaming Text Oriented Messaging Protocol)
💡 STOMP(Simple or Streaming Text Oriented Messaging Protocol) - 텍스트 기반의 메시징 프로토콜을 의미합니다. 클라이언트와 메시지 브로커 간의 통신을 간단하고 효율적으로 수행할 수 있도록 설계되었습니다. 이는 WebScoket에서 쉽게 메시지를 주고받을 때 사용이 됩니다. - 해당 프로토콜은 Websocket을 사용하여 클라이언트와 서버 간의 메시지 교환을 구조화하고 표준화하는 데 사용됩니다.
명령어
설명
CONNECT
클라이언트가 서버에 연결을 요청할 때 사용합니다.
SEND
클라이언트 또는 서버가 특정 목적지로 메시지를 보낼 때 사용합니다.
SUBSCRIBE
클라이언트가 특정 목적지의 메시지를 구독할 때 사용합니다.
UNSUBSCRIBE
클라이언트가 특정 목적지의 메시지 구독을 취소할 때 사용합니다.
DISCONNECT
클라이언트가 서버와의 연결을 종료할 때 사용합니다.
1. STOMP 기반 연결 및 통신 화면
💡STOMP 기반 연결 및 통신 화면
- Google Chrome Extension을 통해서 구성한 웹 소켓 연결을 수행하면 아래와 같이 연결이 되고 데이터를 주고받는 것을 확인할 수 있습니다.
3) STOMP Client 화면 구성
1. 구성환경
💡구성 환경
- 해당 글에서는 아래의 환경에서 STOMP Client가 구성이 되어 있습니다.
구성
환경 버전
Node
21.7.1
React
18.3.1
Typescript
4.9.5
@stomp/stompjs
7.0.0
stompjs
2.3.3
2. 초기 개발 환경 구축
💡 초기 개발환경 구축
- 아래와 같이 CRA + Typescript 기반으로 React 프로젝트와 Router를 구성하였습니다.
- sockjs-client : WebSocket의 대체 라이브러리로, 브라우저와 웹 서버 간의 로우 레벨 크로스-브라우저 JavaScript 객체를 생성합니다. - @stomp/stompjs : STOMP(Simple Text Oriented Messaging Protocol)는 메시징 프로토콜로, @stomp/stompjs는 이를 JavaScript에서 구현한 클라이언트 라이브러리입니다.
💡컴포넌트 구성 : StompComponent.tsx - 구성한 Spring Boot WebSocket에 최초 연결을 하고 Endpoint를 통해 Sub(구독), Pub(게시)를 통해 실시간 데이터 통신을 수행합니다. 1. stompHandler.connect()
- STOMP의 소켓에 연결을 시도합니다. - onConnect로 웹 소켓 연결이 완료되면 채팅방을 입장시켜 줍니다.(setIsEnterChat) - client.subscribe를 통해서 SUB_ENDPOINT(/sub/message)로 특정 메시지에 대한 수신을 구독(Sub)합니다. - client.activate()를 통해서 구성한 client를 활성화합니다.
2. stompHandler.sendMessage()
- 채팅방에서 입력한 값을 웹 소켓으로 전송(Pub)합니다. - 생성된 Client가 존재하고 연결이 되어 있으며, 전송하려는 메시지가 있을 때 동작을 수행합니다. - wsClient.publish를 통해서 PUB_ENDPOINT(/pub/messages)로 특정 메시지를 전송(Pub)합니다.
3. stompHandler.disconnect()
- STOMP의 소켓의 연결을 종료합니다. - 채팅방 입장을 종료합니다(setIsEnterChat) - wsClient.deactivate()를 통해서 구성한 client를 비 활성화 합니다.
import React, { useEffect, useState } from 'react';
import { Client, IFrame, IMessage } from '@stomp/stompjs';
import SockJS from 'sockjs-client';
interface MessagesType {
sender: string; // 보내는 주체
content: string; // 보내는 메시지
}
const StompComponent = () => {
const SERVER_URL = '<http://localhost:8081/ws-stomp>'; // STOMP 연결 엔드포인트
const PUB_ENDPOINT = '/pub/messages'; // 메시지를 전송하기 위한 엔드포인트
const SUB_ENDPOINT = '/sub/message'; // 메시지를 수신하기 위한 엔드포인트
// STOMP가 연결되고 생성한 Client를 관리하는 상태 관리
const [wsClient, setWsClient] = useState<Client>();
// 채팅방 입장 여부
const [isEnterChat, setIsEnterChat] = useState<boolean>(false);
// 채팅에서 누적되는 데이터를 관리합니다.
const [messages, setMessages] = useState<MessagesType[]>([]);
// 사용자의 구분을 짓기 위해 임시로 발급한 사용자 아이디
const [userId, setUserId] = useState(Math.random().toString(36).substr(2, 9));
// 채팅에서 보내지는 데이터를 관리합니다.
const [messageObj, setMessageObj] = useState<MessagesType>({
content: '',
sender: userId,
});
/**
* STOMP를 연결하고 라이프사이클을 관리하는 Handler
*/
const stompHandler = (() => {
return {
/**
* STOMP 연결을 시도합니다.
* @returns
*/
connect: () => {
// [STEP1] 연결 시 Client 객체를 생성합니다.
const client = new Client({
webSocketFactory: () => new SockJS(SERVER_URL),
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
// [STEP2] 웹 소켓 연결
onConnect: (conn: IFrame) => {
console.log('[+] WebSocket 연결이 되었습니다.', conn);
setIsEnterChat(true); // 채팅방 입장여부 상태를 변경
// [WebSocket - Subscribe] 특정 엔드포인트로 메시지를 수신합니다.
client.subscribe(SUB_ENDPOINT, (message: IMessage) => {
const receiveData = JSON.parse(message.body);
setMessages((prevMessages) => [
...prevMessages,
{
content: receiveData.content,
sender: receiveData.sender,
},
]);
});
},
// 웹 소켓 연결 종료
onWebSocketClose: (close) => {
console.log('[-] WebSocket 연결이 종료 되었습니다.', close);
},
// 웹 소켓 연결 에러
onWebSocketError: (error) => {
console.error('[-] 웹 소켓 연결 오류가 발생하였습니다.', error);
},
// STOMP 프로토콜 에러
onStompError: (frame) => {
console.error('Broker reported error: ' + frame.headers['message']);
console.error('Additional details: ' + frame.body);
},
});
setWsClient(client); // 구성한 Client 객체를 상태 관리에 추가합니다.
client.activate(); // Client를 활성화 합니다.
return () => {
stompHandler.disconnect(); // Socket 연결을 종료합니다.
};
},
/**
* 웹 소켓 메시지를 전송합니다.
*/
sendMessage: () => {
if (wsClient && wsClient.connected && messageObj.content.trim() !== '') {
// [WebSocket - Publish] 특정 엔드포인트로 메시지를 전송합니다.
wsClient.publish({
destination: PUB_ENDPOINT,
body: JSON.stringify({ ...messageObj, sender: userId }),
});
// 입력한 값을 초기화합니다.
setMessageObj({ content: '', sender: userId });
}
},
/**
* 웹 소켓 연결을 종료합니다.
*/
disconnect: () => {
console.log('[-] 웹 소켓 연결을 종료합니다.');
if (wsClient) {
wsClient.deactivate();
setWsClient(undefined);
setIsEnterChat(false);
}
},
};
})();
return (
<div style={{}}>
{!isEnterChat ? (
<div
style={{
flexDirection: 'column',
justifyContent: 'center',
alignContent: 'center',
alignItems: 'center',
textAlign: 'center',
}}>
<h2>Spring Boot WebSocket 채팅방에 오신것을 환영합니다.</h2>
<button onClick={stompHandler.connect}>채팅방 입장</button>
</div>
) : (
<div
style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignContent: 'center',
alignItems: 'center',
textAlign: 'center',
}}>
<h1>STOMP을 이용한 채팅방입니다. </h1>
<div style={{ flexDirection: 'row' }}>
<input
type='text'
value={messageObj.content}
onChange={(e) => setMessageObj({ ...messageObj, content: e.target.value })}
/>
<button onClick={stompHandler.sendMessage}>전송</button>
</div>
<div
style={{
display: 'flex',
flexDirection: 'column',
height: 300,
backgroundColor: '#f5d682',
border: '1px solid red',
margin: 20,
}}>
{messages.map((item, index) => (
<h1
style={{
fontSize: 13,
textAlign: 'left',
}}
key={`messages-${index}`}>
{item.sender === userId ? `[ME] ${item.content}` : `[OTHER] ${item.content}`}
</h1>
))}
</div>
<div style={{ marginTop: 10 }}>
<button onClick={stompHandler.disconnect}> 채팅방 나가기 </button>
</div>
</div>
)}
</div>
);
};
export default StompComponent;
4) 결과 확인
1. 채팅방 입장 결과 화면
💡채팅방 입장 결과 화면
- 아래와 같이 다른 브라우저 내에서 접속을 하였습니다.
💡 연결이 완료되어 아래와 같이 연결에 대한 결과값을 반환받았습니다.
2. 채팅 결과 화면
💡 채팅 결과 화면
- 아래와 같이 서로 간의 데이터를 주고받음을 확인하였습니다. - ME의 경우는 입력한 브라우저이고, OTHER의 경우는 다른 브라우저를 의미합니다.