일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- UE4
- FSM
- 스파르타내일배움캠프
- C++
- 유니티
- 스파르타내일배움캠프TIL
- QueryDSL
- 워크플로
- Unity
- 해시
- UnrealEngine
- BFS
- Unity3d
- 구현
- 순열
- Photon
- 포톤
- 내일배움캠프
- unityui
- Inventory
- 문자열
- 프로그래머스
- 이분탐색
- Unity2D
- 언리얼엔진
- c#
- 알고리즘
- 유클리드호제법
- Firebase
- 스택
- Today
- Total
개발 낙서장
[TIL] 내일배움캠프 64일차 - STOMP로 실시간 채팅 구현 본문
오늘의 학습 키워드📚
STOMP
STOMP는 스프링 프레임워크에서 제공하는 웹소켓 프로토콜이다.
TCP나 웹소켓과 같은 실시간 양방향 통신이 가능하기에 실시간 채팅 등에 활용이 가능하다.
이번 파이널 프로젝트에서 나는 오픈 채팅에 대한 도메인과 기능 구현을 맡았기에 어떤 것이 있을까 찾아보다 STOMP를 알게 되어 이를 사용해 구현하기로 했다.
Config
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
// 메세지 브로커 구성
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic/chat-rooms/"); // 구독 요청
config.setApplicationDestinationPrefixes("/app"); // 접두사 처리
}
// STOMP 엔드포인트 등록
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket"); // 핸드셰이크 엔드포인트 설정
}
}
모델
먼저 Config에서 접두사를 수정했다. 채팅방은 ID값으로 나누어져있기에 채팅방의 ID를 입력받도록 설정한다.
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Message {
private String userName;
private String content;
}
모델 부분도 수정해주었다. 누가 어떤 메세지를 보냈는지 표시해주기 위해 Message라는 모델을 만들었다.
컨트롤러
@Controller
public class MessageController {
@MessageMapping("/messages/{roomId}")
@SendTo("/topic/chat-rooms/{roomId}")
public Message sendMessage(@DestinationVariable Long roomId, Message message) throws Exception {
Thread.sleep(500); // 지연 시뮬레이션
return new Message(message.getUserName(), message.getContent());
}
}
컨트롤러에서도 roomId에 따라 다른 값을 받을 수 있도록 했고 HTTP 통신에서는 @PathVariable 어노테이션으로 값을 받았지만 메시징에서는 @DestinationVariable 어노테이션을 통해 받을 수 있다.
여기서 생겼던 문제점은 처음엔 각 userName, content를 String 필드로 받았는데 보통 프론트에서 요청을 보낼 때 JSON으로 파싱해서 보내기에 잘 되지 않았다.
필드를 매핑해서 보내는 방법도 있겠지만 찾아봐도 잘 안 나오고 실제로도 필드값보단 DTO에 담아서 값을 활용하기 때문에 객체 형식으로 body를 받았다.
HTML
<!DOCTYPE html>
<html>
<head>
<title>Multi-Room Chat</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@stomp/stompjs@7.0.0/bundles/stomp.umd.min.js"></script>
<script src="/js/app.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6">
<h3>채팅방 선택</h3>
<input type="text" id="roomId" placeholder="채팅방 ID">
<button id="joinRoom" class="btn btn-primary">입장</button>
<button id="exitRoom" class="btn btn-primary" disabled>나가기</button>
</div>
<div class="col-md-6">
<h3>메시지 보내기</h3>
<input type="text" id="userName" placeholder="사용자 이름">
<input type="text" id="messageInput" placeholder="메시지 입력">
<button id="sendMessage" class="btn btn-primary" disabled>보내기</button>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h3>채팅 내용</h3>
<table id="messageTable" class="table table-striped">
<thead>
<tr>
<th>사용자 이름</th>
<th>메시지</th>
</tr>
</thead>
<tbody id="messageList"></tbody>
</table>
</div>
</div>
</div>
</body>
</html>
HTML도 바꿔주었다. AI의 힘을 빌려 작성했는데 그냥 간단하게 채팅방 번호를 입력해 입장하고 이름과 메시지를 입력해 보내면 같은 방에 있는 다른 클라이언트에서 메세지를 수신할 수 있도록 했다.
JS
let currentRoomId = 0;
// STOMP 클라이언트 초기화
const stompClient = new StompJs.Client({
brokerURL: 'ws://localhost:8080/gs-guide-websocket' // 엔드포인트
});
// 연결 성공 시 콜백 함수
stompClient.onConnect = (frame) => {
console.log('Connected: ' + frame);
setConnected(true);
currentRoomId = $("#roomId").val();
stompClient.subscribe(`/topic/chat-rooms/${currentRoomId}`,
onMessageReceived); // 서버로부터 받은 메시지(Body)를 보여줌
};
// 웹소켓 오류 발생 시 콜백 함수
stompClient.onWebSocketError = (error) => {
console.error('Error with websocket', error);
};
// STOMP 메시지 브로커에서 오류 발생 시 콜백 함수
stompClient.onStompError = (frame) => {
console.error('Broker reported error: ' + frame.headers['message']);
console.error('Additional details: ' + frame.body);
};
// 연결에 따른 UI 설정
function setConnected(connected) {
$("#joinRoom").prop("disabled", connected);
$("#exitRoom").prop("disabled", !connected);
$("#sendMessage").prop("disabled", !connected);
if (connected) {
$("#messageTable").show();
} else {
$("#messageTable").hide();
}
$("#messageList").html("");
}
// STOMP 에 웹소켓 연결 활성화
function connect() {
stompClient.activate();
}
// 웹소켓 연결 비활성화 및 연결 상태 false 로 설정
function disconnect() {
stompClient.deactivate();
setConnected(false);
console.log("Disconnected");
}
$(function () {
$("#joinRoom").click(function () {
currentRoomId = $("#roomId").val();
connect();
});
$("#exitRoom").click(function () {
disconnect();
});
$("#sendMessage").click(function () {
sendMessage();
})
});
STOMP에 연결하고 기본적인 설정을 하는 부분이다.
설정한 엔드포인트로 STOMP 클라이언트를 초기화하고 방 번호를 입력해 입장 버튼을 누르면 해당 방 번호의 경로로 구독한다.
// 메세지 보내는 함수
function sendMessage() {
const userName = $("#userName").val();
const content = $("#messageInput").val();
stompClient.publish({
destination: `/app/messages/${currentRoomId}`,
body: JSON.stringify({'userName': userName, 'content': content})
});
$("#messageInput").val("");
}
이 부분 때문에 컨트롤러에서 객체로 데이터를 받았다.
데이터를 보낼 경로를 설정하고 JSON 형태로 값을 STOMP 클라이언트에 보낸다.
// 메시지 수신
function onMessageReceived(payload) {
const message = JSON.parse(payload.body);
const messageRow = `
<tr>
<td>${message.userName}</td>
<td>${message.content}</td>
</tr>
`;
$("#messageList").append(messageRow);
}
서버에서 메세지를 받으면 구독한 클라이언트들에게 해당 메소드를 실행하여 채팅을 받을 수 있게 해준다.
같은 방에 입력한 사용자끼리 실시간 채팅이 가능한 모습이다.
입장한 방이 다를 경우 서로의 메세지가 보이지 않고 같은 방에 입장해야 메세지가 보인다.
정말 실시간 채팅'만' 가능하도록 간단하게 기능 구현을 해봤는데 추후에 프로젝트에 적용할 때는 DB에 저장도 하고 캐싱 처리도 하는 등 여러가지 부가 기능도 구현할 예정이다.
오늘의 회고💬
새로운 개념을 학습하느라 조금 힘들었지만 언제나 새로운 걸 학습하고 구현에 성공했을 때는 정말 재밌는 것 같다.
내일의 계획📜
내일은 채팅 내역을 DB에 저장하고 가능하다면 방 입장, 퇴장 기능도 구현할 수 있었으면 좋겠다.
'Java > Sparta' 카테고리의 다른 글
[TIL] 내일배움캠프 66일차 - JPQL concat (1) | 2024.04.01 |
---|---|
[TIL] 내일배움캠프 65일차 - STOMP JWT 인증 (0) | 2024.03.29 |
[TIL] 내일배움캠프 63일차 - 논리적 참조키 (0) | 2024.03.27 |
[TIL] 내일배움캠프 62일차 - Open API (0) | 2024.03.26 |
[TIL] 내일배움캠프 61일차 - 참조키 인덱싱 (0) | 2024.03.25 |