깃헙: https://github.com/62hoon99/mvc-chatting
WebSocket은 HTTP와 다르게, 연결을 유지하며 양방향 통신이 가능한 프로토콜
1. 클라이언트가 WebSocket 연결을 요청
ws://localhost:8080/ws-chat
2. 서버가 STOMP 메시지 처리
3. 클라이언트가 메시지 전송
stompClient.send("/app/chat.sendMessage", {}, JSON.stringify({...}));
4. 서버가 해당 메시지를 처리하고 구독자에게 전달
messagingTemplate.convertAndSend("/topic/chat/" + roomId, message);
5. 클라이언트가 /topic/chat/{roomId}을 구독해서 메시지 수신
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic"); // 구독용 경로 설정
registry.setApplicationDestinationPrefixes("/app"); // 메시지 전송 경로
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-chat") // 웹소켓 엔드포인트 등록
.withSockJS(); // SockJS 지원 추가
}
}
/app/chat.sendMessage 경로로 클라이언트가 보낸 메시지를 /topic/chat/{roomId} 경로를 구독하고 있는 클라이언트에게 전달한다.@Controller
@RequiredArgsConstructor
public class ChatController {
private final SimpMessagingTemplate messagingTemplate;
@MessageMapping("/chat.sendMessage")
public void sendMessage(@Payload ChatMessage chatMessage) {
String roomId = chatMessage.getRoomId(); // ChatMessage 안에 있는 roomId 가져오기
messagingTemplate.convertAndSend("/topic/chat/" + roomId, chatMessage);
}
@MessageMapping("/chat.addUser")
public void addUser(@Payload ChatMessage chatMessage, SimpMessageHeaderAccessor headerAccessor) {
headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
String roomId = chatMessage.getRoomId(); // ChatMessage 안에 있는 roomId 가져오기
messagingTemplate.convertAndSend("/topic/chat/" + roomId, chatMessage, headerAccessor.toMap());
}
}
(아래 코드 참고) 동작 방식
1. /ws-chat 경로로 웹소켓 연결 요청을 보낸다.
2. 연결이 수립되면 /topic/chat/{roomId} 경로를 구독한다. 그리고 /app/chat.addUser 경로로 json 형식의 메시지를 보낸다.
3. 사용자가 메시지를 보내면 /app/chat.sendMessage 경로로 메시지를 보낸다.
4. 구독한 경로(/topic/chat/{roomId})에서 메시지를 수신하면 화면에 받은 메시지를 추가한다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>채팅방</title>
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1.5.1/dist/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
</head>
<body>
<h2 th:text="'채팅방: ' + ${roomId}"></h2>
<h3 th:text="'사용자: ' + ${username}"></h3>
<div id="chat-box">
<ul id="messageArea"></ul>
</div>
<input type="text" id="messageInput" placeholder="메시지를 입력하세요..." />
<button onclick="sendMessage()">전송</button>
<a href="/chat/">채팅방 목록으로</a>
<script>
var username = "[[${username}]]";
var roomId = "[[${roomId}]]";
var socket = new SockJS('/ws-chat');
var stompClient = Stomp.over(socket);
stompClient.connect({}, function () {
stompClient.subscribe('/topic/chat/' + roomId, function (message) {
showMessage(JSON.parse(message.body));
});
stompClient.send("/app/chat.addUser", {}, JSON.stringify({
sender: username,
type: "ENTER",
roomId: roomId,
content: username + "님이 입장했습니다."
}));
});
function sendMessage() {
var messageContent = document.getElementById("messageInput").value;
if (messageContent) {
var chatMessage = {
sender: username,
content: messageContent,
type: "CHAT",
roomId: roomId
};
stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage));
document.getElementById("messageInput").value = "";
}
}
function showMessage(message) {
var messageArea = document.getElementById("messageArea");
var li = document.createElement("li");
li.textContent = message.sender + ": " + message.content;
messageArea.appendChild(li);
}
</script>
</body>
</html>