
- 웹소켓 : 전이중 통신 제공 -> 실시간성 보장
- 실시간성을 보장하는 서비스
- 게임, 채팅, 실시간 주식 거래 사이트 등
HTTP vs 웹소켓
- HTTP에서도 실시간성을 보장하는 기법 존재
- Polling : 클라이언트가 요청 계속 보내기
- Long Polling : 요청을 보내고 커넥션 오랫동안 유지하기
- Streaming : 커넥션 당 다중 요청 / 응답 가능
- HTTP
- 비연결성
- 매번 연결 맺고 끊는 과정의 비용
- 요청 - 응답 한 쌍의 구조
- 매번 Header, Response의 정보가 생성되고 응답되고 계속 주고받아야 함(메서드, ..)
- 웹소켓
- 연결 지향
- 한번 연결 맺은 뒤 유지
- 양방향 통신
- 연결된 채널을 통해 상대가 보낸 메세지 듣고있으면 됨
- 연결할때만 HTTP Header등이 필요하고, 연결된 이후에는 새로 만들 필요가 없음
- 통신에 사용되는 비용 줄일 수 있음
- SockJS, Socket.io : 웹 소켓을 지원하지 않는 웹 브라우저에서도 웹소켓을 사용하는 것 같은 비슷한 기능 제공
- 브라우저 웹소켓 지원 확인하고 안하면 이 기법들을 사용
- Spring은 SockJS 지원
- 웹소켓 지원하면 소켓 사용
- 지원하지 않으면 Streaming
- Streaming도 지원하지 않으면 Polling
Spring
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandler(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketTextHandler(), "/user")
.setAllowedOrigins("*")
.withSockJS();
}
}
- 스프링에서 웹소켓 사용하려면 클라이언트가 보낸 통신 처리할 핸들러 필요
- SocketTextHandler() : 직접 구현한 웹소켓 핸들러
- /user : 웹소켓 연결 주소
- setAllowedOrigins() : Cors 설정
- Spring에서 WebSocket 사용할 때 same origin만 허용하는 것이 기본 정책
- withSockJS() : SockJS 사용을 위한 설정
public class SocketTextHandler extends TextWebSocketHandler {
private final Set<WebSocketSession> sessions = ConcurrentHashMap.newKeySet();
@Override
public void afterConnectionEstablished(WebSocketSession session) {
sessions.add(session);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
JSONObject jsonObject = new JSONObject(payload);
for(WebSocketSession s : sessions) {
s.sendMessage(new TextMessage("Hi" + jsonObject.get("user) + "! How may I Help you?"));
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
}
}
- 웹소켓 프로토콜은 기본적으로 Text와 Binary Type 지원
- 필요에 따라 Spring이 제공하는 TextWebSocketHandler 혹은 BinaryWebSocketHandler를 상속한 handler 생성하기만 하면 됨
@Override
public void afterConnectionEstablished(WebSocketSession session) {
sessions.add(session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
}
Spring Messaging?
- Spring boot에서 WebSocket 의존성 추가 하니 딸려오는 발표자의 문제 발생
- 어떤것인지 설명
STOMP 프로토콜
- Simple Text Oriented Messaging Protocol
- 메시지 브로커를 활용하여 쉽게 메세지를 주고 받을 수 있는 프로토콜
- Pub - Sub(발행 - 구독) : 발신자가 메시지를 발행하면 수신자가 그것을 수신하는 메시징 패러다임
- 메세지 브로커 : 발신자의 메시지를 받아와서 수신자들에게 메시지를 전달하는 어떤 것
- 웹소켓 위에 얹어 함께 사용할 수 있는 하위(서브) 프로토콜
- 웹소켓만을 위해 생긴 프로토콜은 아님
- 웹소켓과 같은 몇몇 양방향 통신 프로토콜에서 함께 사용할 수 있음
- 스프링이 웹소켓 위에 얹어 사용하는 방법을 지원한다는 것
STOMP의 필요성
- WebSocket은 Text, Binary 타입의 메세지 양방향으로 주고받을 수 있는 프로토콜
- Message 어떤 형식으로 주고받을 지 정해진 규칙이 없음
- 하지만 웹소켓만으로 간단한 프로젝트는 구현 가능
- 프로젝트가 커지고 협업 사람이 많아진다면?
규칙 정의 및 parsing 필요
- Client - Server 어떤 형식의 메세지를 주고 받을지
- 주고 받는 Message Type은 어떤것인지?
- Message의 본문과 설정 정보같은건 어떻게 구분할 것인지 등
- STOMP를 사용하면 위와 같은 형식과 Parsing에 관해 고민할 필요가 없음
- STOMP는 프레임 단위의 프로토콜
- 커멘드
- 헤더
- 바디

- 웹소켓만 사용했을 때 : 날것의 Message
- STOMP 사용 : 커멘드, 헤더, 바디 형태의 메세지가 오고 갈 수 있음
- 프레임 구축 or 해석 코드를 구현하지 않아도 프레임 형식의 메세지를 오고 가고 하게 할 수 있음(Spring)
통신 흐름

- 상황 가정
- 발신자는 구독자들에게 메시지를 보내고 싶음
- 구독자들은
/topic
이라는 경로를 구독하는 가정
- 발신자는
/topic
이라는 destination header로 넣어서 메세지를 송신할 수 있지만 서버 내에서 메세지 가공/처리를 위해 /app
이라는 곳에 송신
- 가공되거나 처리된 메세지를
/topic
이라는 경로로 다시 전송 -> Message Broker에게 전달 -> 전달받은 메세지를 구독자들에게 최종적으로 전달
- Server단 처리 or 메세지 가공 필요가 없다면 바로 Message Broker를 통해서 보내는 것도 가능
Spring - WebSocketMessageBroker
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketBrokerConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue", "/topic");
registry.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
}
- configureMessageBroker : MessageBroker 설정 부분
- enableSimpleBroker()
- 내장 브로커 사용
- prefix가 붙은 메시지를 발생 시 브로커가 처리
- "/queue", "/topic"부분
- "/queue" : 메세지가 1:1 송신 컨벤션
- "/topic" : 메세지가 1:N 송신 컨벤션(브로드 캐스팅)
- 처리 후 구독자들에게 전달
- setApplicationDestinationPrefixes()
- 메시지 처리나 가공기 필요한 경우 핸들러로 라우팅 되는 Prefix
- 핸들러 설정 아래와 같음
@Controller
public class GreetingController {
@MessageMapping("/hello")
@SendTo("/topic/greeting")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000);
return new Greeting)
"Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
}
- @MessageMapping : STOMP 웹소켓 -> Message destination header와 MessageMapping에 설정한 경로가 일치하는 Handler가 처리
- configuration에서 설정한
/app
과 합쳐져서 /app/hello
가 됨
- @SendTo : handler 처리 완료 후 메세지 다시 보낼 경로
/topic
이 붙었으니 simple broker로 전달됨
- registerStompEndpoints : 웹소켓 addHandler 메소드와 비슷
- addEndPoint()
- 처음 웹소켓 handShake를 위한 주소
- 웹소켓 연결 주소
- 이전처럼 Handler 하나하나 추가할 필요 없음
- Handler를 구현하는 방식이 아닌 Controller 방식으로 사용하기 때문
- cors, SockJS 설정 가능
STOMP 사용 장점
- 하위 프로토콜 혹은 컨벤션을 따로 정의할 필요 없다
- 연결 주소마다 새로 핸들러를 구현하고 설정해줄 필요가 없다
- 외부 Messaging Queue를 사용할 수 있다(RabbitMQ, 카프카, ..)
- Spring Security를 사용할 수 있다