웹 애플리케이션에서 양방향 통신을 지원하는 프로토콜
기존의 HTTP 프로토콜은 클라이언트가 서버로 요청을 보내고, 서버가 클라이언트에게 응답을 보내는 단방향 통신
WebSocket을 사용하면 클라이언트와 서버 간에 실시간으로 데이터를 주고받을 수 있게 됨
💡 프로토콜?
컴퓨터 네트워크나 통신 시스템에서 데이터를 주고받는 데
사용되는 규칙과 형식을 의미 프로토콜은 어떤 데이터를 어떤 형식으로 전송하고 받을지에 대한 약속
- 데이터의 포맷, 전송 방식, 오류 처리, 보안 등을 포함
- TCP/IP는 데이터의 분할과 재조립, 에러 검출 및 복구, 라우팅 등을 다루는 규칙의 모음
프로토콜은 통신 시스템의 여러 구성 요소 간에 상호작용을 가능하게 하며, 데이터의 안정적이고 정확한 전송을 보장합니다. 또한, 서로 다른 시스템 간에 상호 운용성을 제공하여 표준화된 통신을 가능하게 합니다.
💡TCP/IP ?
TCP는 신뢰성 있는 연결 지향형 프로토콜로, 데이터의 분할과 재조립, 에러 검출 및 복구, 흐름 제어 등을 담당 / 데이터를 보내는 측과 받는 측 간에 신뢰성 있는 연결을 설정하고, 데이터의 안정적인 전송을 보장
IP는 인터넷 프로토콜 / 데이터를 패킷으로 분할하고, 각 패킷에 출발지와 목적지 IP 주소를 부여하여 전송합니다. 이를 통해 IP는 데이터의 경로 선택과 라우팅을 담당하며, 패킷의 전송을 보장
TCP/IP는 데이터의 전송을 위한 프로토콜 집합으로, 웹 브라우징, 이메일, 파일 전송, 원격 로그인 등 다양한 인터넷 서비스에서 사용됩니다. 또한, TCP/IP는 인터넷의 기반 프로토콜로서 전 세계적으로 표준화되어 있어 다양한 시스템에서 상호 운용성을 제공
Simple Text Oriented Messaging Protocol의 약자
WebSocket을 기반으로 한 메시징 프로토콜
스프링부트 WebSocket은 웹 애플리케이션에서 실시간 양방향 통신을 가능하게 해주는 프로토콜이고, STOMP는 WebSocket을 기반으로 한 메시징 프로토콜 이를 통해 클라이언트와 서버 간에 실시간 데이터를 주고받을 수 있음
@Configuration
@RequiredArgsConstructor
@EnableWebSocketMessageBroker // WebSocket 메시지 브로커를 활성화하는 어노테이션
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
// 1. 메시지 브로커를 구성하는 메서드
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/send"); //1) 클라이언트에서 보낸 메세지를 받을 prefix
registry.enableSimpleBroker("/room"); //2)해당 주소를 구독하고 있는 클라이언트들에게 메세지 전달
}
// 2. 클라이언트와 websocket연결을 등록
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-stomp") //SockJS 연결 주소
.withSockJS(); //버전 낮은 브라우저에서도 적용 가능
// 주소 : ws://localhost:8080/ws-stomp
}
}
1) "/send"를 prefix로 하는 클라이언트로부터의 메시지를 처리
2) "/room"을 구독하고 있는 클라이언트들에게 메시지를 전달
💡 prefix?
WebSocket에서의 prefix는 메시지 브로커를 구성할 때 사용되는 개념
prefix는 클라이언트가 보내는 메시지의 경로를 지정하는데 사용
"/send"라는 prefix를 설정한다면 클라이언트에서 보내는 메시지는 "/send/메시지"와 같은 형태
prefix를 통해 메시지 브로커는 해당 경로로 들어오는 메시지를 처리하고, 해당 경로를 구독하고 있는 클라이언트들에게 메시지를 전달

stomp 를 사용하면 메세지를 전송하기 전에 subsciber/ publicher(sender)를 지정하여 편리하게 이용할 수 있게 도와준다
// 채팅
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.webjars:webjars-locator-core'
implementation 'org.webjars:sockjs-client:1.0.2'
implementation 'org.webjars:stomp-websocket:2.3.3'
// 부트 스트랩
implementation group: 'org.webjars', name: 'bootstrap', version: '5.3.2'
// jquery
implementation group: 'org.webjars', name: 'jquery', version: '3.6.4'
function connect() {
// SockJS 객체를 생성하여 '/ws-stomp' 경로를 통해 WebSocket 연결을 수립합니다.
var socket = new SockJS('/ws-stomp');
// Stomp 클라이언트 객체를 생성하여 SockJS 객체를 기반으로 STOMP 프로토콜을 사용할 수 있도록 합니다.
stompClient = Stomp.over(socket);
// 서버에 연결합니다. 빈 객체를 전달하여 추가적인 헤더나 인증 정보를 전달하지 않습니다.
// 연결이 성공하면 콜백 함수가 실행됩니다.
stompClient.connect({}, function (frame) {
// 연결 상태를 true로 설정합니다.
setConnected(true);
// 연결에 성공한 경우, 연결 관련 정보를 로그에 출력합니다.
console.log('Connected: ' + frame);
// '/room/' 경로와 roomId 변수를 조합하여 해당 경로를 구독합니다.
// 이로써 클라이언트는 해당 경로로 전송되는 메시지를 수신할 수 있게 됩니다.
stompClient.subscribe('/room/'+roomId, function (chatMessage) {
// 수신된 메시지를 처리하는 함수를 호출합니다.
showGreeting(JSON.parse(chatMessage.body));
});
});
}
function showGreeting(chatMessage) {
console.log(chatMessage.name)
$("#chatting").append("<tr><td>" + "[" + chatMessage.name + "]" + chatMessage.message + "</td></tr>");
}
function sendName() {
// stompClient를 사용하여 '/chat/' 경로로 메시지를 전송합니다.
// roomId는 현재 방의 식별자입니다.
// 빈 객체를 전달하여 추가적인 헤더나 인증 정보를 전달하지 않습니다.
// 전송할 메시지는 JSON 형식으로 변환하여 전달합니다.
stompClient.send("/chat/"+roomId, {},
JSON.stringify({
'roomId' : roomId,
'name': $("#name").val(),
'message' : $("#message").val()
}));
}
@Controller
@RequiredArgsConstructor
public class ChatController {
private final ChatService chatService; // ChatService 의존성 주입을 위한 필드 선언
// 메시지가 특정 경로로 전송되면 해당 메서드가 호출됩니다.
// {roomId}은 동적인 값으로, WebSocketConfig에서 설정한 prefixes에 의해 앞에 생략됩니다.
@MessageMapping("/{roomId}")
// 전송된 메시지를 구독하고 있는 장소로 전송합니다.
// {roomId}은 동적인 값으로, WebSocketConfig의 Broker 설정에 의해 앞에 붙게 됩니다.
public ChatMessage test(@DestinationVariable Long roomId, ChatMessage message) {
// 채팅을 저장합니다.
// ChatService를 사용하여 roomId, sender, message를 기반으로 Chat 객체를 생성하고 저장합니다.
Chat chat = chatService.createChat(roomId, message.getSender(), message.getMessage());
// 저장된 채팅 정보를 기반으로 ChatMessage 객체를 생성하여 반환합니다.
return ChatMessage.builder()
.roomId(roomId)
.sender(chat.getSender())
.message(chat.getMessage())
.build();
}
}
var stompClient = null;
var roomId = [[${roomId}]];
function setConnected(connected) {
$("#connect").prop("disabled", connected);
$("#disconnect").prop("disabled", !connected);
if (connected) {
$("#conversation").show();
}
else {
$("#conversation").hide();
}
$("#greetings").html("");
}
function connect() {
// SockJS 객체를 생성하여 '/ws-stomp' 경로를 통해 WebSocket 연결을 수립합니다.
var socket = new SockJS('/ws-stomp');
// Stomp 클라이언트 객체를 생성하여 SockJS 객체를 기반으로 STOMP 프로토콜을 사용할 수 있도록 합니다.
stompClient = Stomp.over(socket);
// 서버에 연결합니다. 빈 객체를 전달하여 추가적인 헤더나 인증 정보를 전달하지 않습니다.
// 연결이 성공하면 콜백 함수가 실행됩니다.
stompClient.connect({}, function (frame) {
// 연결 상태를 true로 설정합니다.
setConnected(true);
// 연결에 성공한 경우, 연결 관련 정보를 로그에 출력합니다.
console.log('Connected: ' + frame);
// '/room/' 경로와 roomId 변수를 조합하여 해당 경로를 구독합니다.
// 이로써 클라이언트는 해당 경로로 전송되는 메시지를 수신할 수 있게 됩니다.
stompClient.subscribe('/room/'+roomId, function (chatMessage) {
// 수신된 메시지를 처리하는 함수를 호출합니다.
showGreeting(JSON.parse(chatMessage.body));
});
});
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendName() {
stompClient.send("/chat/"+roomId, {},
JSON.stringify({
'roomId' : roomId,
'name': $("#name").val(),
'message' : $("#message").val()
}));
}
function showGreeting(chatMessage) {
console.log(chatMessage.name)
$("#chatting").append("<tr><td>" + "[" + chatMessage.name + "]" + chatMessage.message + "</td></tr>");
}
$(function () {
$("form").on('submit', function (e) {
e.preventDefault();
});
$( "#connect" ).click(function() { connect(); });
$( "#disconnect" ).click(function() { disconnect(); });
$( "#send" ).click(function() { sendName(); });
});