우리는 mvc를 적용하기로 햇고, network서비스로 서버와 클라이언트를 연결한다.
- View (ChatFrame): 사용자가 글자를 입력하고 버튼을 클릭함 (이벤트 발생).
- Controller (ChatClientApp): 이벤트를 감지하고, View에서 글자를 가져옴.
- Service (NetworkService): Controller가 넘겨준 데이터를 JSON으로 포장해서 서버로 던짐.
- Network (STOMP): /app/chat.send 주소로 배달.
스프링 기초 내용 학습
"스프링부터에서 STOMP 기반 웹소켓으로 서버 클라이언트 연결하기 - 기초개념과 대표 사용법을 알려줘"
사용한 프롬프트
1. 클라이언트랑 서버 연결하기
서버 설정
WebSocketConfig 사용
@EnableWebSocketMessageBroker
@Configuration
@EnableWebSocket
@RequiredArgsConstructor
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final StompHandler stompHandler;
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 1. 연결 주소: ws://localhost:8080/ws
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*") // CORS 허용
.withSockJS(); // (선택) 낮은 버전 브라우저 지원
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(stompHandler);
}
}
registerStompEndpoints 클라이언트가 연결할 진입점.
서버: WebSocketConfig를 읽고, /ws에 만들라고 읽음. -> ws://localhost:8080/ws 문 개방
클라이언트 접속: 문 주소 ( ws://localhost:8080/ws )로 연결 요청
서버: 들여보내 줌. ( configureClientInboundChannel : 서버에 들어오기 위해 stompHandler를 거쳐야 함.)
클라이언트 설정
클라이언트(프론트엔드)에서는 SockJS와 STOMP 라이브러리를 사용한다.
DIP를 위해 network연결을 담당하는 netwrokService를 interface로 만들고,
stompNetworkService에서 구현한다.
NetworkService
public interface NetworkService {
void connect(String url, Runnable onConnected, Consumer<Throwable> onError);
void disconnect();
void subscribe(String destination, MessageHandler handler);
void send(String destination, Object payload);
boolean isConnected();
}
네트워크 연결을 관리한다. (이 인터페이스를 구현한 클래스가 실제 네트워크 연결, 메시지 송수신등을 담당)
connect(String url, Runnable onConnected, Consumer<Throwable> onError)
→ 특정 URL로 연결을 시도하고, 성공하면 onConnected 실행, 실패하면 onError 실행.
연결 성공 시 스레드를 실행한다.
subscribe(String destination, MessageHandler handler)
→ 특정 채널/토픽(destination)에 구독을 걸고, 메시지가 오면 handler가 처리.
send(String destination, Object payload)
→ 특정 채널/토픽(destination)으로 데이터를 보냄.
채팅 메시지, json, 바이너리 데이터 등 object로 선언해서 어떤 타입 이든 보낼 수 있다.
** Runnable과 Consumer를 인자로 받는 이유
네트워크 연결은 시간이 걸리기 때문에, 상황에 따라 실행할 동작을 외부에서 전달 받아, 바로 실행되게 한다.
-> 메서드안에서 직접 처리하지 않고, 외부에서 원하는 동작을 주입받아 실행하는 구조.
MessageHandler
@FunctionalInterface
public class MessageHandler {
void handle(String payload);
}
메시지 처리용 함수형 인터페이스
메시지가 도착했을 때 실행되는 콜백 역할.
@FunctionalInterface를 붙여 메서드가 딱 하나만 있는 인터페이스 임을 보장해, 람다식으로 사용할 수 있게 한다.
-> 호출 하는 곳에서 실행될 함수를 지정해서 다룰 수 있음.
SompNetworkService
public class StompNetworkService implements NetworkService{
private StompSession session;
private WebSocketStompClient stompClient;
public StompNetworkService() {
// SockJS + STOMP 클라이언트 초기화
SockJsClient sockJsClient = new SockJsClient(
List.of(new WebSocketTransport(new StandardWebSocketClient()))
);
this.stompClient = new WebSocketStompClient(sockJsClient);
this.stompClient.setMessageConverter(new MappingJackson2MessageConverter());
}
}
NetworkService interface를 구현하여, 네트워크 연결을 담당한다.
내부적으로 STOMP 프로토콜과 WebSocket을 사용하여 서버와 통신할 수 있도록 준비한다.
- StompSession → 실제 연결된 세션을 나타냄. (서버와 연결된 상태를 관리)
- WebSocketStompClient → STOMP 메시징을 지원하는 WebSocket 클라이언트 객체.
생성자 부분 설명
SockJs(안정망)를 통해 대체 전송방식을 지원한다.
STOMP에서는 WebSocketStompClient 역할이 핵심이다. 단순 WebSocket만 쓰면 "메시지를 보내고 받는다"정도인데, StompClient를 쓰기 때문에, STOMP프로토콜을 사용한다.
setMessageConverter
해당 클라이언트가 메시지를 주고 받을 때 JSON<->객체변환을 자동으로 처리하도록 set을 해준다.
-> 문자열을 직접 파싱하지 않고, 자바 객체로 다룰 수 있다.
연결/끊김 함수.
@Override
public void disconnect() {
// TODO Auto-generated method stub
if (session != null) {
session.disconnect();
session = null;
}
}
@Override
public boolean isConnected() {
// TODO Auto-generated method stub
return session != null && session.isConnected();
}
구독 함수
@Override
public void subscribe(String destination, MessageHandler handler) {
// TODO Auto-generated method stub
if (session == null || !session.isConnected()) {
throw new IllegalStateException("Not connected");
}
session.subscribe(destination, new StompFrameHandler() {
@Override
public Type getPayloadType(StompHeaders headers) {
return String.class; //메시지를 문자열로 받음
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
handler.handle((String) payload); //payload를 string으로 캐스팅 후 처리
}
});
}
📌 함수 동작 단계별 설명
- 연결 상태 확인
- STOMP 세션(session)이 없거나 연결되지 않은 상태라면 예외를 던져서 더 이상 진행하지 않습니다
if (session == null || !session.isConnected()) { throw new IllegalStateException("Not connected"); }
- STOMP 세션(session)이 없거나 연결되지 않은 상태라면 예외를 던져서 더 이상 진행하지 않습니다
2. 구독 실행
- session.subscribe()를 호출해서 특정 채널(destination)을 구독합니다.
- 예: /topic/chat, /topic/alerts 같은 STOMP destination
session.subscribe(destination, new StompFrameHandler() { ... });
3. 메시지 처리 핸들러 정의 (StompFrameHandler)
- 구독할 때 메시지를 어떻게 받을지 정의하는 콜백을 넘깁니다.
(a) getPayloadType(StompHeaders headers)
- 서버에서 오는 메시지를 어떤 타입으로 변환할지 지정합니다.
- 여기서는 String.class → 메시지를 문자열로 받겠다는 뜻입니다.
(b) handleFrame(StompHeaders headers, Object payload)
- 실제 메시지가 도착했을 때 실행되는 콜백(자동으로 실행).
- payload는 서버가 보낸 메시지 본문이고, headers는 메시지의 메타데이터(어느 채널에서 왔는지 등).
- 여기서는 payload를 문자열로 변환한 뒤, 외부에서 넘겨준 MessageHandler의 handle() 메서드로 전달합니다.
@Override public void handleFrame(StompHeaders headers, Object payload) { handler.handle((String) payload); // payload를 String으로 캐스팅 후 처리 }@Override public Type getPayloadType(StompHeaders headers) { return String.class; // 메시지를 문자열로 받음 }
send 함수
@Override
public void send(String destination, Object payload) {
// TODO Auto-generated method stub
if (session == null || !session.isConnected()) {
throw new IllegalStateException("Not connected");
}
session.send(destination, payload);
}
연결상태를 확인하고, 세션에서 destination으로 메세지를 payload로 보내면 서버가 메시지를 받아서 필요한 채널로 다시 브로드 캐스트를 한다.
📌 사용 예시
// 문자열 메시지 보내기
stompNetworkService.send("/app/chat", "안녕하세요!");
// JSON 객체 보내기
ChatMessage msg = new ChatMessage("끼루룩", "안녕하세요!");
stompNetworkService.send("/app/chat", msg);
- 서버는 /app/chat을 처리하는 핸들러(@MessageMapping 같은 것)를 통해 메시지를 받고,
- 필요한 경우 /topic/chat 같은 브로드캐스트 채널로 다시 뿌려줍니다.
📌 전체 흐름
- connect() → 서버와 WebSocket 연결
- subscribe() → 특정 채널 구독 (메시지 수신 준비)
- send() → 특정 채널로 메시지 전송
- 서버가 메시지를 받아서 필요한 채널로 다시 브로드캐스트
- 구독 중인 클라이언트들이 handleFrame()을 통해 메시지 수신
'프로젝트' 카테고리의 다른 글
| 자프실 프로젝트 - 6 controller, 이벤트 흐름 (0) | 2025.12.06 |
|---|---|
| 자바프로젝트-5 클라이언트 시작! ChatService, userService (0) | 2025.12.06 |
| 자프실 프로젝트-3 (0) | 2025.11.29 |
| 자프실 프로젝트-1 (0) | 2025.11.29 |
| 자프실 프로젝트-2 (0) | 2025.11.29 |