WebSocket은 클라이언트와 서버 간의 양방향 통신(Full-Duplex Communication)을 지원하는 프로토콜이다.
기존 HTTP 통신은 요청(Request)-응답(Response) 방식으로 클라이언트가 먼저 요청해야만 서버가 응답할 수 있었지만, WebSocket은 연결이 한 번 맺어지면 서버가 클라이언트로 자유롭게 데이터를 보낼 수 있다.
즉, 실시간 양방향 데이터 송수신이 가능한 통신 방식이다.
| 구분 | HTTP | WebSocket |
|---|---|---|
| 통신 방식 | 요청-응답 (Request/Response) | 양방향 (Full-Duplex) |
| 연결 방식 | 요청 시마다 새 연결 | 한 번 연결 후 지속 유지 |
| 프로토콜 | HTTP/1.1 | ws:// 또는 wss:// |
| 주요 목적 | 문서 및 API 요청 | 실시간 데이터 전송 (채팅, 알림 등) |
| 헤더 오버헤드 | 요청마다 있음 | 초기 연결 이후 최소화 |
HTTP는 짧은 생명주기를 갖는 반면, WebSocket은 연결을 유지하며 서버 → 클라이언트 방향의 이벤트 전송이 가능하다.
따라서 채팅, 알림, 주식 시세, 게임 등 실시간성이 중요한 시스템에 주로 사용된다.
핸드셰이크(Handshake)
클라이언트가 ws:// 또는 wss:// 프로토콜로 서버에 연결을 요청한다.
서버는 HTTP 101 Switching Protocols 응답을 보내며 WebSocket 연결로 전환한다.
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
연결 유지 (Persistent Connection)
한 번 연결되면 TCP 기반의 단일 소켓을 통해 양방향 통신이 이루어진다.
메시지 송수신
클라이언트와 서버는 Text 또는 Binary 형태의 메시지를 주고받는다.
연결 종료 (Close Frame)
어느 한쪽이 Close 프레임을 전송하면 세션이 종료된다.
Spring은 WebSocket을 쉽게 구현할 수 있도록 spring-websocket 모듈을 제공한다.
Spring WebSocket은 크게 두 가지 방식으로 사용할 수 있다.
WebSocketHandler를 직접 구현하는 방식으로, 프레임 단위의 세밀한 제어가 가능하다.
@Component
public class ChatHandler extends TextWebSocketHandler {
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
for (WebSocketSession s : sessions.values()) {
s.sendMessage(message);
}
}
@Override
public void afterConnectionEstablished(WebSocketSession session) {
sessions.put(session.getId(), session);
}
}
그리고 WebSocket 설정 클래스를 등록한다.
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
private final ChatHandler chatHandler;
public WebSocketConfig(ChatHandler chatHandler) {
this.chatHandler = chatHandler;
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(chatHandler, "/ws/chat")
.setAllowedOrigins("*");
}
}
실무에서는 대부분 STOMP 프로토콜과 SockJS를 함께 사용한다.
이는 WebSocket 메시지 통신을 Pub/Sub 구조로 확장하여, 브로드캐스팅이 용이하다.
STOMP(Simple Text Oriented Messaging Protocol)는 메시징 프로토콜로,
WebSocket 위에서 “채널(Topic)” 개념을 통해 메시지를 전송하고 구독할 수 있게 해준다.SockJS란?
SockJS는 WebSocket을 지원하지 않는 브라우저에서도 동작하도록 도와주는 폴백(Fallback) 라이브러리다.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic"); // 구독 경로
registry.setApplicationDestinationPrefixes("/app"); // 발행 경로
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws") // 연결 엔드포인트
.setAllowedOrigins("*")
.withSockJS();
}
}
@Controller
public class ChatController {
@MessageMapping("/chat.sendMessage")
@SendTo("/topic/public")
public ChatMessage sendMessage(ChatMessage message) {
return message;
}
}
var socket = new SockJS('/ws');
var stompClient = Stomp.over(socket);
stompClient.connect({}, function() {
stompClient.subscribe('/topic/public', function(message) {
console.log(JSON.parse(message.body));
});
});
function sendMessage() {
stompClient.send("/app/chat.sendMessage", {}, JSON.stringify({ content: "hello" }));
}
이 구조에서:
/app/chat.sendMessage → 메시지 발행 경로/topic/public → 메시지 구독 경로 로 동작한다.| 구분 | 설명 |
|---|---|
| 인증 | JWT 토큰 기반의 사용자 인증 필요 (HandshakeInterceptor 사용) |
| 확장성 | 단일 서버 한계를 넘어서는 경우 Redis Pub/Sub 또는 Kafka 사용 |
| 에러 처리 | 연결 종료/예외 발생 시 세션 관리 필요 |
| 성능 | 연결 유지 수가 많을 경우 커넥션 풀 관리 중요 |
| 보안 | CORS, 토큰 검증, 세션 스니핑 방지 필요 |