
일반적으로 HTTP 기반 애플리케이션은 클라이언트가 요청을 보내면 서버가 응답하는 방식으로 동작한다. 하지만 실시간 양방향 통신이 필요한 경우, HTTP만으로는 한계가 발생할 수 있다. 이러한 상황에서는 WebSocket을 활용하면 보다 효율적인 통신이 가능하다. WebSocket이 필요한 대표적인 사례는 다음과 같다.
WebSocket을 사용하면 서버와 클라이언트가 지속적으로 연결을 유지하면서 데이터를 실시간으로 주고받을 수 있기에 위와같은 용도에서 사용한다.
다음 섹션에서는 WebSocket을 활용하여 Binary 데이터를 처리하는 방법을 살펴보자.
의존성부터 주입해보자.
implementation 'org.springframework.boot:spring-boot-starter-websocket'
그 다음에는 Configuration 파일을 만들어보자.
@Configuration
@EnableWebSocket
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer {
private final WebSocketHandler webSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler, "/test").setAllowedOrigins("*");
}
}
여기서 "/test" 엔드포인트는 이후 ws://localhost:8080/test와 같이 WebSocket 연결을 설정하는 데 사용된다. 또한 setAllowedOrigins("*")는 CORS 정책을 우회하기 위해 모든 도메인을 허용하는 설정이지만, 실제 서비스에서는 보안 문제로 인해 와일드카드를 사용하는 것은 지양해야 한다.
이제 WebSocket을 처리하는 핸들러 클래스를 만들어보자. 쉽게 말해 REST API 에서 Controller라고 생각하면 된다.
@Component
public class WebSocketHandler extends BinaryWebSocketHandler {
private static final ConcurrentHashMap<String, WebSocketSession> clients = new ConcurrentHashMap<String, WebSocketSession>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
clients.put(session.getId(), session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
clients.remove(session.getId());
}
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
// Binary 데이터를 처리하는 로직을 추가할 수 있다.
// 예를 들어, 음성 데이터라고 가정하고 byte[]로 변환해보자.
byte[] audioData = message.getPayload().array();
// 클라이언트에게 응답을 보낼 수도 있다.
session.sendMessage(new TextMessage("Binary data received"));
}
}
이 코드에서 BinaryWebSocketHandler를 상속하여 WebSocket 연결을 관리하고 Binary 데이터를 처리한다. 중요한 메서드는 다음과 같다 :
afterConnectionEstablished(WebSocketSession session): 새로운 WebSocket 연결이 생성되었을 때 호출되며, 연결된 세션을 저장한다.afterConnectionClosed(WebSocketSession session, CloseStatus status): WebSocket 연결이 종료될 때 호출되며, 저장된 세션을 제거한다.handleBinaryMessage(WebSocketSession session, BinaryMessage message): 클라이언트로부터 Binary 데이터를 받을 때 실행되는 메서드이다.이제 WebSocket을 활용한 Binary 데이터 처리의 기본적인 설정이 완료되었다. 이후에는 해당 데이터를 원하는 방식으로 활용하는 로직을 추가하면 된다.
나는 마이크를 통한 음성 데이터를 실시간으로 처리하기 위한 기능을 구현하던 중이었기 때문에 샘플용 프론트 코드 또한 보여주자면,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebSocket Mic Test</title>
</head>
<body>
<h2>WebSocket Mic Test</h2>
<button onclick="startRecording()">Start</button>
<button onclick="stopRecording()">Stop</button>
<script>
let ws;
let mediaRecorder;
function connectWebSocket() {
ws = new WebSocket("ws://localhost:8080/test");
ws.onopen = () => console.log("WebSocket 연결됨");
ws.onmessage = (event) => console.log("서버 응답:", event.data);
ws.onerror = (error) => console.error("WebSocket 오류:", error);
ws.onclose = () => console.log("WebSocket 연결 종료");
}
async function startRecording() {
connectWebSocket();
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
});
mediaRecorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
mediaRecorder.ondataavailable = (event) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(event.data);
console.log("음성 데이터 전송:", event.data);
}
};
mediaRecorder.start(500); // 500ms마다 데이터 전송
console.log("녹음 시작");
}
function stopRecording() {
mediaRecorder.stop();
ws.close();
console.log("녹음 종료 및 WebSocket 닫음");
}
</script>
</body>
</html>

이 화면에서 테스트해보면 된다.
좋은 글 감사합니다.
질문이 있는데, 만약 WebSocket 연결이 비정상적으로 종료되는 상황을 처리하려면 어떻게 해야 하나요?