그룹채팅에서 카프카로 보낸 메세지를 받을수잇는지 로컬 테스트 하려고해.
- ✅ Kafka로 메시지를 보냄
- ✅ WebSocketSender 서비스 있음
- ❌ WebSocket 클라이언트 UI 없음 (Swagger만 있음)
1. 로컬 테스트 환경 서버 몇개 띄어야해?
2. WebSocketSender 컨트롤러는 어떻게 만들어?
3. WebSocketSender를 접속해야하는데 프론트 화면이 없어. swagger만 있어 어떻게 채팅처럼하지?
✅ 1. 로컬 테스트 환경: 서버 몇 개 필요한가?
최소한 2개 띄우면 구조 테스트가 가능
구성 | 설명 |
🔹 1번 서버 | 채팅 메시지를 Kafka에 발행 (ChatController, KafkaProducer) |
🔹 2번 서버 | Kafka 메시지를 수신하여 WebSocketSender로 broadcast (KafkaConsumer, WebSocketSender) |
➡ 혹은 하나의 서버에서도 역할을 나눠서 테스트할 수 있어요 (싱글 인스턴스, 여러 스레드).
📦 Kafka는 Docker로 띄우면 편합니다.
docker run -d --name kafka \
-p 9092:9092 -e KAFKA_ADVERTISED_HOST_NAME=localhost \
-e KAFKA_ZOOKEEPER_CONNECT=localhost:2181 \
wurstmeister/kafka
✅ 전체 흐름 구조
[Kafka Producer] (chat-messages 토픽)
↓
[ChatMessageListener]
↓
[WebSocketSender] → /topic/chat/{roomId}
↓
[구독 중인 클라이언트들]
ChatMessageListener | Kafka에서 메시지 수신 |
WebSocketSender | STOMP 브로커로 메시지 전달 (/topic/chat/{roomId}) 서버 ---> 고객에게 보낼 때 |
StompChatController | 클라이언트 → 서버 메시지 테스트용 |
WebSocketConfig | STOMP endpoint 및 브로커 설정 |
서버가 고객에게 메시지를 보낼 때
// /topic -> broadcast 용 (채팅방 등 다수 사용자 대상)
// /queue -> 1:1 개인 메시지용 (point-to-point)
registry.enableSimpleBroker("/topic", "/queue");
그래서 /topic /queue 로 시작하는구나
@Service
@RequiredArgsConstructor
public class WebSocketSender {
/**
* Spring WebSocket은 STOMP 프로토콜 위에서 작동하는데
* SimpMessageSendingOperations 구현체 내부적으로 STOMP를 알아서 처리함
*/
private final SimpMessagingTemplate messagingTemplate;
/**
* Test > StompChatController > @MessageMapping("/chat") 이 붙은 클라이언트들이 자동으로 메시지를 받음
* 세션 ID나 커넥션 직접 안 잡아도 됨 ->직접 WebSocket 세션 추적, 사용자 매핑, 메시지 전송 로직 필요 없음
*/
public void sendToChatRoom(String key, Object payload) {
messagingTemplate.convertAndSend("/topic/chat/" + key, payload);
}
/**
* Spring은 WebSocket 연결 시 user 정보까지 매핑해줄 수 있어서
* 특정 사용자한테만 보내는 것도 쉽게 가능
* 1:1 알림, 귓속말
*/
public void sendToUser(String userId, Object payload) {
messagingTemplate.convertAndSendToUser(userId, "/queue/messages", payload);
}
}
자바스크립트도 받아야지, 그래서
localhost:8080/ws-chat/topic/chat/{roomId} 로 구독 중임.
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Test</title>
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1.5.1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
</head>
<body>
<h2>WebSocket Chat Test</h2>
<div>
<label>roomId: <input id="roomId" value="53d6a720-fc93-4d4f-b83a-27e890262652"/></label>
</div>
<ul id="messages"></ul>
<script>
// 서버로부터 클라이언트가 받아야해서 /topic
const socket = new SockJS("http://localhost:8080/ws-chat");
const stompClient = Stomp.over(socket);
const roomId = document.getElementById("roomId").value;
stompClient.connect({}, () => {
stompClient.subscribe("/topic/chat/" + roomId, (msg) => {
const li = document.createElement("li");
li.textContent = "📩 " + msg.body;
document.getElementById("messages").appendChild(li);
});
});
</script>
</body>
</html>
고객이 서버로 메세지를 보낼 때
registry.setApplicationDestinationPrefixes("/app", "/pub");
채팅방처럼 접근하고싶어 서버는 하나띄우고 클라이언트 여러명이 붙고싶은데
🖥 서버 (Spring Boot) → KafkaListener → WebSocketSender
↳ 클라이언트1 (SUBSCRIBE /topic/chat/ROOM_ID)
↳ 클라이언트2 (SUBSCRIBE /topic/chat/ROOM_ID)
↳ 클라이언트3 (SUBSCRIBE /topic/chat/ROOM_ID)
✅ 클라이언트 여러 명 붙는 방법
🎯 방법 1: HTML 파일 여러 개 열기
- 아까 작성한 ws-test.html 파일을 복사해서 여러 탭/창에서 열어요.
- 각각 브라우저 탭에서 roomId를 동일하게 설정 (예: test-room)
- 서버로부터 Kafka 메시지를 받으면, 모든 탭에서 동시에 수신됨
✅ 브라우저 탭을 여러 개 열면 "클라이언트 1, 2, 3" 역할을 할 수 있음
✅ 각 탭이 SUBSCRIBE /topic/chat/test-room 하면 브로드캐스트 확인 가능
http://localhost:8080/ws-test.html
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Test</title>
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1.5.1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
</head>
<body>
<h2>WebSocket Chat Test</h2>
<div>
<label>roomId: <input id="roomId" value="53d6a720-fc93-4d4f-b83a-27e890262652"/></label>
</div>
<ul id="messages"></ul>
<script>
const socket = new SockJS("http://localhost:8080/ws-chat");
const stompClient = Stomp.over(socket);
const roomId = document.getElementById("roomId").value;
stompClient.connect({}, () => {
stompClient.subscribe("/topic/chat/" + roomId, (msg) => {
const li = document.createElement("li");
li.textContent = "📩 " + msg.body;
document.getElementById("messages").appendChild(li);
});
});
</script>
</body>
</html>
ws://localhost:8080/ws-chat/425/askf4w5s/websocket
ws://localhost:8080/ws-chat/559/sv4qlts0/websocket
ws://localhost:8080/ws-chat/085/upthrsok/websocket
ws://localhost:8080/ws-chat/425/askf4w5s/websocket
ws://localhost:8080/ws-chat/559/sv4qlts0/websocket
ws://localhost:8080/ws-chat/085/upthrsok/websocket
이 경로 뒤에 붙는 425/askf4w5s 이런 게 왜 매번 다르게 보이는가?
✅ 이유: SockJS의 세션 관리 방식 때문
🔸 SockJS는 WebSocket을 에뮬레이션하는 fallback 지원 라이브러리예요.
브라우저에서 WebSocket을 지원하지 않거나 프록시 환경에서 문제가 생길 때도 정상 작동을 보장하기 위해 다양한 프로토콜을 섞어 사용합니다.
- → 실제로는 다 같은 /ws-chat endpoint에 연결한 거예요.
- 이 값들은 SockJS가 자동으로 생성한 내부 세션 경로일 뿐입니다.
- 서버 쪽에서는
- @MessageMapping("/chat/{roomId}")
- @SendTo("/topic/chat/{roomId}")
- 같은 논리 주소만 신경 씁니다.
- 실제 WebSocket 경로가 달라도 같은 roomId 구독이면, 정상적으로 브로드캐스트됩니다.
🔸 내부적으로는 이렇게 구성됨
/ws-chat/{server_id}/{session_id}/websocket
- {server_id}: 서버 인스턴스를 구분하기 위한 임의 숫자 (랜덤)
- {session_id}: 각 클라이언트의 고유 세션 ID
'학습 기록 (Learning Logs) > Today I Learned' 카테고리의 다른 글
컴퓨터 핵심 부품(메모리, cpu, 보조기억장치, 보조기억장치) (0) | 2025.04.23 |
---|---|
실시간 데이터 동기화 (0) | 2025.04.17 |
머신러닝에서 나오는 vector, bias 뭔데? (0) | 2025.04.17 |
GPT에서 웹툰을 그린다고?! (0) | 2025.04.17 |
이미지 모델 테스트 (0) | 2025.04.17 |