메시징 방식을 잘 정의한다면 WebSocket 만으로도 충분히 좋은 Server/Client 소켓 서버를 완성할 수 있습니다. 하지만 단순한 통신 구조로 인해 WebSocket만을 이용해 채팅을 구현하면 해당 메시지가 어떤 요청인지, 어떻게 처리하고 어디로 보내야하는지에 따라 채팅방과 session을 일일히 구현하고 메시지 전송 부분을 관리하는 추가 코드를 작성해야 합니다.
그래서 이번에는 Stomp
를 사용해서 이 부분에 대해서 해결해보려고 합니다. Stomp는 메시징 전송을 효율적으로 하기 위해 나온 프로토콜이며 기본적으로 Pub/Sub
구조로 되어 있어 메시지를 발송하고, 메시지를 받아 처리하는 부분이 확실히 정해져 있기 때문에 개발하는 입장에서 명확하게 인지하고 개발할 수 있는 이점이 있습니다. 또한 Stomp를 이용하면 통신 메시지의 헤더에 값을 세팅할 수 있어 헤더 값을 기반으로 통신 시 인증 처리를 구현하는 것도 가능합니다.
여기서 Pub/Sub
은 메시지를 공급하는 주체와 소비하는 주체를 분리하여 제공하는 메시징 방법입니다.
Stomp
를 사용하기 위해 @EnableWebSocketMessageBroker
를 선언하고 WebSocketMessageBrokerConfigurer
를 상속 받도록 합니다./pub
으로 시작하도록 설정하고 메시지를 구독하는 요청의 prefix는 /sub
으로 시작하도록 설정해줍니다./ws/chat
으로 설정해줍니다. 서버의 접속 주소는 ws://localhost:8080/ws/chat
으로 설정됩니다.@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Autowired
private WebSocketHandler webSocketHandler;
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/sub");
registry.setApplicationDestinationPrefixes("/pub");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/ws/chat")
.setAllowedOrigins("*")
.withSockJS();
}
}
pub/sub 구조를 이용하게 되면 구독자 관리가 알아서 되므로 WebSocket Session 관리가 필요 없어집니다. 그리고 전송의 구현도 알아서 해결되므로 일일이 Client에게 메시지를 전송하는 구현이 필요없어집니다.
public class ChatRoom {
private String id;
private String name;
protected ChatRoom() {
}
private ChatRoom(String id, String name) {
this.id = id;
this.name = name;
}
public static ChatRoom of(String id, String name) {
return new ChatRoom(id, name);
}
...
}
@MessageMapping
을 통해 WebSocket 으로 들어오는 메시지 발행을 처리합니다./pub/chat/message
로 발행 요청을 하면 Controller가 해당 메시지를 받아 처리합니다. /sub/chat/room/{roomId}
로 메시지를 send
하는 것을 볼 수 있는 Client에서는 해당 주소(/sub/chat/room/{roomId}
)를 구독(subscribe)하고 있다가 메시지가 전달되면 화면에 출력하면 됩니다./sub/chat/room/{roomId}
는 채팅룸을 구분하는 값이므로 pub/sub에서 Topic
의 역할이라고 보면 됩니다.@RestController
public class ChatController {
@Autowired
private SimpMessageSendingOperations messagingTemplate;
@MessageMapping(value = "/chat/message")
public void send(ChatMessage message) {
if (MessageType.ENTER.equals(message.getMessageType())) {
message.setMessage(message.getSender() + " 님이 입장하셨습니다.");
}
messagingTemplate.convertAndSend("/sub/chat/room/" + message.getChatRoomId(), message);
}
}