네트워크의 선택기준
- ✅ 📌 "어떤 데이터인가?" → 데이터 유형에 맞는 최적의 프로토콜을 선택할 수 있음.
- ✅ 📌 "어떤 방향으로 가는가?" → 단방향 vs 양방향에 따라 불필요한 기술 도입을 피할 수 있음.
- ✅ 📌 "속도와 효율성이 중요한가?" → 과도한 리소스 사용을 막고 최적의 성능을 유지할 수 있음.
웹소켓의 특징
1. 양방향 통신 (Full-Duplex)
- 클라이언트와 서버가 동시에 데이터를 주고받을 수 있어 실시간 데이터 처리가 가능.
- 채팅, 게임, 주식 시세, 실시간 알림 시스템에 적합.
2. 연결 유지 (Persistent Connection)
- HTTP는 요청마다 새로운 TCP 연결을 생성해야 하지만, 웹소켓은 한 번 연결 후 지속적으로 재사용 가능.
- 기존 HTTP 방식보다 **응답 지연(Latency)**이 적고, 연결 유지 비용이 낮음.
3. 낮은 오버헤드 (Low Latency)
- http 헤더가 없으며 바이너리 데이터 전송가능하므로 인코딩을 위한 추가 오버헤드 절약가능
- 네트워크 오버헤드가 적어 실시간 데이터 교환이 많은 지도 서비스, IoT, 실시간 모니터링 시스템에도 활용 가능.
웹소켓의 동작 방식
웹소켓은 단순한 TCP 연결이 아니라 TCP 위에서 동작하는 자체적인 "프레임 포맷"을 정의한 프로토콜이야.
1️⃣ 클라이언트가 웹소켓 연결 요청 (HTTP 요청 사용)
- 클라이언트는 HTTP 요청을 보내면서 웹소켓 업그레이드(Upgrade) 요청을 포함함.
- Upgrade 및 Connection 헤더를 사용하여 웹소켓으로 전환을 요청.
2️⃣ 서버가 핸드셰이크 응답 (HTTP 101 Switching Protocols)
- 서버가 웹소켓을 지원하면 HTTP 응답을 보내며 프로토콜을 웹소켓으로 전환함.
3️⃣ TCP 소켓을 사용하여 웹소켓 연결 유지
- HTTP는 텍스트(UTF-8)만 전송 가능, 하지만 웹소켓은 바이너리(Binary)도 지원.
- 핸드셰이크가 완료되면 기존의 HTTP 연결이 유지된 상태에서 TCP 소켓을 통해 통신이 진행됨.
- 이후에는 HTTP가 아닌 **웹소켓 프레임(WebSocket Frames)**을 주고받음.
**웹소켓 프레임(WebSocket Frames)**
왜 HTTP는 양방향 통신이 안 되고, TCP는 가능한가?
- 브라우저 내 HTTP를 핸들링하는 코드가 요청와 응답을 맵핑하는 방식으로 동작하기 때문
브라우저 내부에서 동작하는 흐름
- 클라이언트가 요청을 보냄 → 브라우저는 내부적으로 요청 ID를 생성함.
- 서버가 응답을 보냄 → 브라우저는 응답을 받아 요청 ID와 매칭함.
- 요청-응답 매칭이 성공하면, onload 핸들러를 실행.
🚨 문제:
- 만약 서버가 요청과 관련 없는 응답을 보낸다면? → 브라우저가 이를 무시함!
- 즉, 브라우저는 요청을 보냈을 때만 응답을 기다리며, 서버가 먼저 보낸 응답은 받아들이지 않음.
WebSocket이 요청없이 응답을 감지할 수 있는 이유: 이벤트 루프 기반 동작
기존 HTTP 방식은 요청이 끝날 때마다 연결을 닫으므로, 네트워크 부하가 많아지고 응답 속도가 느려짐. 반면, WebSocket은 한 번 연결되면 이벤트 루프(Event Loop)를 사용하여, 요청이 필요할 때만 CPU를 사용하면서도 연결을 계속 유지할 수 있음.
비교 항목HTTP (요청-응답 방식, Blocking I/O)WebSocket (이벤트 루프 기반, Non-Blocking I/O)
연결 유지 | ❌ 요청할 때마다 새로 연결 | ✅ 한 번 연결되면 지속 유지 |
데이터 전송 방식 | ⛔ 매번 요청해야 응답 가능 | ✅ 언제든 데이터 주고받을 수 있음 (Full-Duplex) |
CPU & 네트워크 부하 | 🔥 요청마다 연결 생성 → 리소스 낭비 | 🟢 이벤트 루프 기반으로 최소 리소스 사용 |
실시간 처리 성능 | ❌ 지속적인 요청 필요 (Polling, Long Polling) | ✅ 실시간 데이터 전송 가능 |
적용 사례 | 일반적인 웹 API (RESTful API) | 실시간 채팅, 게임, 주식 시세, IoT 등 |
📌 즉, WebSocket은 이벤트 루프 덕분에 HTTP 대비 CPU, 네트워크 부하를 크게 줄이면서도 실시간 처리를 할 수 있는 강력한 구조를 가질 수 있어.
서버 웹소켓 구현
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-websocket'
}
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new NotificationWebSocketHandler(), "/notifications").setAllowedOrigins("*");
}
}
설명
- /notifications 엔드포인트에서 웹소켓 연결을 처리
- NotificationWebSocketHandler를 등록하여 세션 관리
(1) @Configuration
- Spring 컨텍스트(Application Context)에 웹소켓 설정을 등록함.
(2) @EnableWebSocket
- Spring Boot에서 웹소켓 기능을 활성화하는 애너테이션.
- 이 애너테이션이 붙어 있어야 WebSocketConfigurer 인터페이스를 구현할 수 있음.
- 내부적으로 WebSocketHandlerRegistry를 활성화함.
- 웹소켓 핸들러를 등록할 수 있도록 Spring에 알림.
(3) public class WebSocketConfig implements WebSocketConfigurer
- Spring Boot의 WebSocketConfigurer 인터페이스를 구현하여 웹소켓 설정을 정의하는 클래스.
- WebSocketHandlerRegistry를 이용해서 웹소켓을 사용할 URL과 핸들러를 설정함.
- 등록된 핸들러는 클라이언트에서 웹소켓 연결을 요청할 때 실행됨. 즉, 클라이언트가 ws://서버주소/notifications로 연결하면, 이 핸들러가 실행됨.
(4) setAllowedOrigins("*")
- CORS(Cross-Origin Resource Sharing) 설정.
- *을 설정하면 모든 도메인에서 웹소켓을 연결할 수 있음.
웹소켓 핸들러 (사용자 세션 관리)
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class NotificationWebSocketHandler extends TextWebSocketHandler {
// userId → WebSocketSession을 저장하는 맵
private static final Map<String, WebSocketSession> userSessions = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 클라이언트에서 userId를 요청 파라미터로 전달한다고 가정
String userId = getUserIdFromSession(session);
if (userId != null) {
userSessions.put(userId, session);
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, org.springframework.web.socket.CloseStatus status) throws Exception {
// 연결이 종료되면 해당 세션을 제거
userSessions.values().remove(session);
}
public void sendNotification(String userId, String message) throws Exception {
WebSocketSession session = userSessions.get(userId);
if (session != null && session.isOpen()) {
session.sendMessage(new TextMessage(message));
}
}
private String getUserIdFromSession(WebSocketSession session) {
// 요청 URL에서 userId 추출 (예: ws://localhost:8080/notifications?userId=123)
return session.getUri().getQuery().split("=")[1]; // 간단한 방법 (실제 프로젝트에서는 검증 추가)
}
}
이 클래스는 웹소켓 핸들러(WebSocket Handler) 로서, 특정 사용자에게 실시간 알림을 전송하는 역할을 해. Spring Boot의 TextWebSocketHandler를 상속받아서 웹소켓 연결을 관리하고, 특정 유저에게 메시지를 보낼 수 있도록 구현했어.
사용된 주요 클래스
- TextWebSocketHandler → Spring에서 제공하는 웹소켓 핸들러 (텍스트 기반 메시지 처리 가능)
- WebSocketSession → 웹소켓 연결이 유지되는 동안 세션을 관리하는 객체
- TextMessage → 웹소켓을 통해 텍스트 메시지를 주고받을 때 사용
- ConcurrentHashMap → 동시성을 고려한 안전한 해시맵 (멀티스레드 환경에서도 안전)
afterConnectionEstablished() (웹소켓 연결이 열렸을 때 실행)
- 클라이언트가 웹소켓에 연결되었을 때 실행되는 메서드.
- userId 값을 웹소켓 URL의 요청 파라미터에서 추출 (ws://localhost:8080/notifications?userId=123).
- userId가 있다면 userSessions 맵에 저장하여, 이후 해당 사용자에게 메시지를 보낼 수 있도록 함.
sendNotification() (특정 유저에게 실시간 메시지 전송)
- 특정 userId를 가진 사용자에게 실시간으로 메시지를 전송하는 메서드.
- userSessions.get(userId)를 이용하여 해당 유저의 웹소켓 세션을 가져옴.
- 세션이 열려 있다면 메시지를 전송함 (session.sendMessage(new TextMessage(message));).
클라이언트 웹소켓 테스트
// WebSocket 연결
const socket = new WebSocket("ws://localhost:8080/notifications?userId=123");
socket.onopen = function() {
console.log("웹소켓 연결 성공!");
};
socket.onmessage = function(event) {
console.log("서버로부터 메시지 수신: ", event.data);
};
socket.onerror = function(error) {
console.log("웹소켓 에러 발생: ", error);
};
socket.onclose = function() {
console.log("웹소켓 연결 종료");
};
- "ws://localhost:8080/notifications?userId=123" :
- ws://localhost:8080/notifications → 로컬 개발 서버(localhost:8080)의 /notifications 엔드포인트에 연결합니다.
- ?userId=123 → 쿼리 파라미터를 통해 특정 유저의 ID를 서버에 전달합니다. (예: userId=123)
- 웹소켓에서 발생하는 이벤트를 처리하는 이벤트 리스너를 정의합니다.
- onopen - 연결 성공 :서버와 웹소켓 연결이 성공적으로 이루어졌을 때 실행됩니다.
- onmessage - 서버에서 메시지 수신 : event.data를 통해 서버가 보낸 데이터를 확인할 수 있습니다.
- onerror - 에러 발생 시 : 네트워크 문제 또는 서버의 예기치 않은 종료 등으로 인해 발생할 수 있습니다.
'개발기술 > 통신 인터페이스, 프로토콜' 카테고리의 다른 글
RabbitMQ 구현 (0) | 2025.02.17 |
---|---|
메시지 큐, RabbitMQ, Kafka 개념 (0) | 2025.02.14 |
AI 영상분석과 websocket + message broker (0) | 2025.02.14 |
실시간 통신 기술의 개발 역사 (0) | 2025.02.14 |