2024. 6. 18. 02:41ㆍWeb/공부
자 이론 공부를 했으니 코드를 짜보겠다.
https://soddong.tistory.com/14
채팅서비스는 어떻게? 이론편
여행 플래너 서비스를 진행하면서 동행끼리 채팅서비스를 추가로 구현하였다. 실시간 웹 애플리케이션을 구현할 경우 사용되는 대표적인 방법으로 polling / websocket / SSE 가 있는데 각각의 차이
soddong.tistory.com
동작 흐름을 파악하자

(참고. 코드로 차트그려주는 https://mermaid.live/ 를 이용해보았는데, 간격조절이 쉽지 않아 타이밍들을 실제 기능과 동일하게 나타내지는 못했다)
서버 구현 : Spring Websocket
1. 핸들러 추가 및 설정
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
private final WebSocketChatHandler chatWebSocketHandler;
public WebSocketConfig(WebSocketChatHandler chatWebSocketHandler) {
this.chatWebSocketHandler = chatWebSocketHandler;
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(chatWebSocketHandler, "/ws/chat").setAllowedOrigins("*");
}
}
- @EnableWebSocket 어노테이션을 추가함으로써 WebSocket 핸들러 자동 설정 추가
- WebSocketHandlerRegistry에 Handler와 webSocket 주소를 추가
→ handler를 추가하는 부분을 보면 아래와 같이 전달한 url과 handler객체를 Map 자료구조에 넣어줌으로써 맵핑한다 - setAllowedOrigins 허가할 Origin 설정
→ Springframework 4.1.5를 기준으로 WebSocket 및 SockJS의 기본 동작은 동일한 Origin요청만 수락
2. 핸들러 구현
public class WebSocketChatHandler extends TextWebSocketHandler {
private final Map<String, WebSocketSession> sessionMap = new HashMap<>();
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
}
}
- 나는 간단한 메시지만 주고받으면 되었기 때문에 텍스트 기반의 심플한 Handler인 TextWebSocketHandler를 상속받았다.
- afterConnectionEstablished
웹소켓 연결시 호출되는 함수로 sessionMap에 현재 접속한 session id를 넣어주어야한다. 그래야, 새로운 메시지가 전송될때 모든 세션에게 실시간으로 전송이 가능하다. - afterConnectionClosed
웹소켓 연결 종료시 호출되는 함수로, sessionMap에서 session 제거해주는 작업이 필요하다. - handleTextMessage
메시지 전송시 호출되는 함수로, 현재방에 속해있는 세션들에게 브로드캐스트하고 DB에 메시지 저장해준다.
- afterConnectionEstablished
클라이언트 구현 : Vue.js
1. WebSocket 객체 생성
socket = new WebSocket(`ws://localhost:8080/ws/chat?planId=${planStore.planInfo.planId}`);
- 채팅방은 계획공유방내에서만 의존적으로 생성되기 때문에 url에 planId를 함께 보내주었다.
(권장되는 방식인지는 모르겠음.. 연결이 체결된 후에 planId를 보내야 했으려나?)
2. 이벤트 핸들러
socket.addEventListener('message', (event) => {
const message = JSON.parse(event.data);
console.log("Message received: ", message);
messages.value.push(message);
});
socket.onopen = () => {
console.log("WebSocket connection established");
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
reconnectTimeout = null;
}
};
socket.onclose = () => {
console.log("WebSocket connection closed. Attempting to reconnect...");
attemptReconnect();
};
socket.onerror = (error) => {
console.error("WebSocket error:", error);
socket.close(); // 오류가 발생하면 연결을 닫고 재연결 시도
};
- WebSocket객체가 제공하는 이벤트 핸들러를 사용하여 간단하게 이벤트를 핸들링 했다.
- addEventListener
서버로부터 메시지를 수신받았을 때 호출되며, DTO객체를 서버에서 보냈으니 JSON 파싱을 해주는 작업이 필요하다 - onopen
연결이 성립되었을때 호출되며, 타임아웃으로 인한 재연결일 경우 타임을 초기화해준다 - onclose
소켓이 닫히면 재연결을 시도한다 - onerror
에러가 발생하면 연결 닫는다 (사실상 재연결 시도)
- addEventListener
3. 메시지 전송
const sendMessage = async () => {
if (!newMessage.value.trim()) return;
const message = {
planId: planStore.planInfo.planId,
sender: userStore.userId,
message: newMessage.value,
};
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(message));
newMessage.value = '';
} else {
console.error("WebSocket connection is not open. Message not sent.");
}
};
소켓이 연결된 상태(OPEN)일때만 메시지를 직렬화하여 전송한다.
다음은 ..
간단한 채팅기능을 추가해보았는데 여기서 한발 나아가서 다음 프로젝트에서는 대규모 시스템을 목적으로 채팅서비스를 고도화해보고 싶다. 아래는 다음 프로젝트에 적용해보고 싶은 기술들이다.
- pub/sub 구조 (sock.js 혹은 socket.io)
- STOMP 프로토콜 (pub/sub 구조 적용을 위해)
- 서버를 여러개 두기
- 메시지 브로커 (redis 혹은 kafka)
참고 페이지
https://docs.spring.io/spring-framework/reference/web/websocket/server.html
WebSocket API :: Spring Framework
The Spring WebSocket API is easy to integrate into a Spring MVC application where the DispatcherServlet serves both HTTP WebSocket handshake and other HTTP requests. It is also easy to integrate into other HTTP processing scenarios by invoking WebSocketHtt
docs.spring.io
'Web > 공부' 카테고리의 다른 글
채팅서비스는 어떻게? 이론편 (0) | 2024.06.15 |
---|---|
[Spring] MVC 패턴과 Service, Domain, Repository (1) | 2023.09.06 |
[Java/Spring] JSON 파싱하기 (0) | 2023.09.01 |
HTTP 통신을 알아보자 (0) | 2023.08.28 |