지난 포스팅에서 websocket만으로 채팅을 단순하게 구현했다면
이번에는 좀 더 나은 채팅을 위해 STOMP를 적용하게 되었습니다.
(1편을 실수로 삭제했네요.. 빠른 시일 내에 복구하겠습니다....)
- WebSocket만으로 채팅 구현
- STOMP를 활용한 채팅 구현
- Redis를 활용한 채팅 구현
- 채팅 고도화
STOMP란
- Simple Text Oriented Messaging Protocol의 약자로 메시지 전송을 위한 프로토콜이다.
- STOMP는 클라이언트와 서버 간 전송할 메시지의 유형, 형식, 내용들을 정의한 규칙으로 TCP 또는 WebSocket과 같은 양방향 네트워크 프로토콜을 기반으로 동작한다.
- STOMP에서 Message Payload에는 Text or Binary데이터를 포함할 수 있다.
- STOMP는 pub/sub 구조로 동작한다.
- STOMP는 상호 운용가능한 wire format을 제공해준다. 이는 STOMP의 클라이언트들이 STOMP의 메시지 브로커를 통해 통신하여 다양한 언어와 플랫폼, 브로커 간에 쉽고 광범위한 메시징 상호 운용성을 제공할 수 있다.
STOMP를 쓰는 이유
WebSocket만으로 채팅을 구현하게 된다면 메시지 발신자와 수신자에 대해서 스프링 내에서 직접 관리를 해야만 한다.
하지만 STOMP를 적용하게 된다면 STOMP의 pub/sub을 통해 메시지 송신과 수신에 대한 처리를 명확하게 처리할 수 있다.
(pub/sub은 간단히 말해서 특정 Channel(or Topic)을 subscribe(구독)하고 있는 사람들에게만 메시지를 publish(발행)해준다라고 이해하면 좋을 것 같다.)
또한 메시지 형식을 정의하여 클라이언트와 서버간의 통신에서 일관성을 유지할 수 있다.
프로젝트
구성
- Springboot 3.3.1
- java 17
- Gradle Dependencies
- web
- websocket
- lombok
- STOMP
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation group: 'org.webjars', name: 'stomp-websocket', version: '2.3.3-1'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
WebSocketConfig
@Configuration
@EnableWebSocketMessageBroker // 메시지 브로커가 지원하는 WebSocket 메시지 처리 활성화
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
// HandShake와 통신을 담당할 Endpoint를 지정한다.
// 클라이언트에서 서버와 WebSocket 연결을 하고 싶으면 "/stomp/chat"으로 요청을 보내도록 한다.
// setAllowedOrigins는 cors를 위한 설정
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/stomp/chat").setAllowedOrigins("*");
}
// configureMessageBroker는 메모리 기반의 Simple Message Broker를 활성화 한다.
// /sub으로 시작하는 주소의 Subscriber들에게 메시지 전달하는 역할을 한다.
// 이때, 클라이언트가 서버로 메시지 보낼 때 붙어야 하는 prefix는 /pub으로 지정한다.
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 메시지를 받을 때, 경로를 설정해주는 함수
// 내장 브로커를 사용하겠다는 설정
// /sub가 api에 prefix로 붙은 경우, messageBroker가 해당 경로를 가로채 처리
// 해당 경로 /sub으로 SimpleBroker를 등록한다.
// SimpleBroker는 해당하는 경로로 구독하는 client에게 메시지를 전달하는 간단한 작업을 수행한다.
registry.enableSimpleBroker("/sub");
// 메시지를 보낼 때, 관련 경로를 설정해주는 함수
// client에서 SEND 요청을 처리
// 클라이언트가 메시지를 보낼 때, 경로 앞에 /pub가 붙어있으면 Broker로 보내진다.
registry.setApplicationDestinationPrefixes("/pub");
}
}
ChatMessage
채팅 메시지에 대해서 다음과 같이 Dto형식으로 정의한다.
@Getter
@Setter
public class ChatMessage {
public enum MessageType {
ENTER, TALK
}
private MessageType type;
private String roomId;
private String sender;
private String message;
}
ChatRoom
채팅방도 다음과 같은 구성으로 구성하였습니다.
@Getter
public class ChatRoom {
private String roomId;
private String name;
@Builder
public ChatRoom(String roomId, String name) {
this.roomId = roomId;
this.name = name;
}
}
ChatController
@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
public class ChatController {
private final ChatService chatService;
// WebSocketConfig에서 setApplicationDestinationPrefixes()를 통해 prefix를 /pub으로 설정 해주었기 때문에,
// 경로가 한번 더 수정되어 /pub/chat/message로 바뀐다.
@MessageMapping("/chat/message")
public void sendMessage(ChatMessage message) {
chatService.sendMessage(message);
}
@PostMapping("/rooms")
public ChatRoom createRoom(@RequestParam String name) {
return chatService.createRoom(name);
}
@GetMapping("/rooms")
public List<ChatRoom> findAllRoom() {
return chatService.findAllRoom();
}
}
ChatService
@Slf4j
@RequiredArgsConstructor
@Service
public class ChatService {
private Map<String, ChatRoom> chatRooms;
private final SimpMessagingTemplate template;
@PostConstruct
private void init() {
chatRooms = new LinkedHashMap<>();
}
public List<ChatRoom> findAllRoom() {
return new ArrayList<>(chatRooms.values());
}
public ChatRoom findRoomById(String roomId) {
return chatRooms.get(roomId);
}
public ChatRoom createRoom(String name) {
String roomId = UUID.randomUUID().toString();
ChatRoom chatRoom = ChatRoom.builder()
.roomId(roomId)
.name(name)
.build();
chatRooms.put(roomId, chatRoom);
return chatRoom;
}
// /sub을 Config에서 설정해주었다.
// 그래서 Message Broker가 해당 send를 캐치하고 해당 토픽을 구독하는 모든 사람에게 메시지를 보내게 된다.
public void sendMessage(ChatMessage message) {
// 메시지 저장로직 추가
ChatRoom chatRoom = chatRooms.get(message.getRoomId());
// ex) roomId가 2일때, /sub/chat/room/2를 구독하는 유저들에게 모두 메시지가 보낸다.
template.convertAndSend("/sub/chat/room/" + chatRoom.getRoomId(), message);
}
}
테스트
참고
STOMP
STOMP is a very simple and easy to implement protocol, coming from the HTTP school of design; the server side may be hard to implement well, but it is very easy to write a client to get yourself connected. For example you can use Telnet to login to any STO
stomp.github.io
[Spring] Stomp로 채팅 기능 구현
📍Stomp STOMP 는 Simple Text Oriented Messaging Protocol 의 약자로 메시지 전송을 위한 프로토콜입니다. 기본적인 Websocket 과 가장 크게 다른 점은 기존의 Websocket 만을 사용한 통신은 발신자와 수신자를 Spri
velog.io
STOMP을 알아보고 서버 구현해보자!
Simple Text Oriented Messaging ProtocolTCP 또는 WebSocket 같은 양방향 네트워크 프로토콜 기반으로 동작Message Payload에는 Text or Binary 데이터를 포함 할 수 있다.pub/sub 구조로 동작Spring에서
velog.io
'Series' 카테고리의 다른 글
[WebSocket] 채팅 1 - WebSocket만으로 채팅 구현하기 (3) | 2024.07.22 |
---|