SSE: Server-Sent Events
SSE(Server-Sent Events)는 웹 애플리케이션, `서버에서 클라이언트`로 `단방향`으로 `실시간` 이벤트를 전송하는 `웹` 기술입니다. SSE는 단방향 통신 방식으로 서버에서 클라이언트로 데이터를 전송합니다. 이를 통해 서버에서 발생하는 업데이트나 알림 등을 실시간으로 클라이언트에게 전달할 수 있습니다. SSE는 이런 특징으로 실시간 알림 ,실시간 주가 업데이트 등에 사용됩니다.
SSE는 단방향 통신이기 때문에 서버에서 클라이언트로만 데이터를 전송할 수 있습니다. 클라이언트는 HTTP 프로토콜을 통해 SSE 연결을 설정하고, 서버는 HTTP 응답을 유지한 상태에서 데이터를 전송합니다. SSE는 재연결 기능을 제공하기 때문에 연결이 끊어졌을 때 자동으로 다시 연결합니다. 이는 기존의 폴링 방식과 비교했을 때 효율적이며, 서버와 클라이언트 간의 불필요한 통신을 최소화합니다. SSE는 이벤트 스트림 형태로 데이터를 전송하며, 클라이언트는 이벤트를 수신하여 처리할 수 있습니다.
폴링은 클라이언트가 주기적으로 서버에 요청을 보내고, 서버는 새로운 데이터가 있는지 확인하여 응답하는 방식인 반면, SSE는 클라이언트와서버 간에 지속적인 연결을 유지하고, 서버는 필요한시점에 데이터를전송합니다.
폴링에 비해 서버 리소스와 네트워크 트래픽을 절약할 수 있습니다.
SSE와 웹소켓 차이점
SSE와 웹소켓의 가장 큰 차이점은 `데이터의 흐름`입니다. `SSE`는 서버에서 클라이언트로 데이터를 전송하는 단방향 통신 방식입니다. 반면 `웹소켓`은 양방향 통신을 지원하여 서버와 클라이언트가 양방향으로 데이터를 주고받을 수 있습니다. 때문에 `SSE`는주로서버에서 클라이언트로 일방적인 데이터 전송이 필요한 주가 업데이트나, 실시간 알림 메시지에 적합하고, `웹소켓`은 양방향 통신이 필요한 실시간 채팅 등에 사용됩니다.
`SSE`는 웹기술이기 때문에 `HTTP 프로토콜` 위에서 동작합니다. 또한 기존의 HTTP 연결을 유지한 상태에서 재연결이나 추가 설정 없이 서버로부터 지속적인 데이터 스트림을 받을 수 있습니다. 반면 `웹소켓`은 독립적인 프로토콜을 사용하고, HTTP와는 별도의 연결을 만들어 데이터를 주고 받습니다. SSE는 CORS (Cross-Origin Resource Sharing)를 통해 다른 도메인에서도 데이터를 수신할 수 있습니다. 웹소켓도 동일한 도메인 간 통신을 제공하지만, 보안상의 이유로 추가 구성이 필요할 수 있습니다.
SSE 구현 방법
SSE를 구현하기 위해서는 서버 측과 클라이언트 측에서의 설정과 처리 로직을 구현해야 합니다.
- `서버 측`에서는 `SSE 프로토콜`을 생성하고 클라이언트에게 전송할 이벤트 데이터를 준비해야 합니다.
- `클라이언트 측`에서는 EventSource 객체를 생성하고 이벤트를 처리하는 로직을 작성해야 합니다. EventSource 객체는 SSE 프로토콜을 처리하고 이벤트를 수신하는 기능을 제공합니다.
Client
<template>
<div>
<ul>
<li v-for="event in events" :key="event.id">{{ event.message }}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
events: [] // 수신된 이벤트를 저장할 배열
};
},
mounted() {
this.setupEventSource(); // SSE 연결 설정
},
methods: {
setupEventSource() {
const eventSource = new EventSource('/sse/stream'); // SSE 연결 생성
eventSource.onmessage = this.handleEvent; // 이벤트 핸들러 등록
eventSource.onerror = this.handleConnectionError; // 연결 오류 핸들러
eventSource.onopen = this.handleConnectionOpen; // 연결 성공 핸들러
},
handleEvent(event) {
const eventData = JSON.parse(event.data); // 수신된 이벤트 데이터 파싱
this.events.push(eventData); // 이벤트 데이터를 배열에 추가
this.showNotification(eventData.message); // 브라우저 알림 표시
},
handleConnectionOpen() {
console.log('Connection to SSE server established'); // 연결 성공 로그 출력
},
handleConnectionError(error) {
console.error('SSE connection error:', error); // 연결 오류 로그 출력
// 연결 오류 처리 로직 구현
},
showNotification(message) {
// 브라우저 알림 표시 로직 구현
}
}
};
</script>
Server
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@RestController
@RequestMapping("/sse")
public class SseController {
private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();
/**
* 특정 클라이언트에게 SSE 연결을 생성합니다.
* 클라이언트마다 고유한 클라이언트 ID를 사용합니다.
* 클라이언트 ID를 경로 변수로 받아와 SSEEmitter를 생성하고 관리합니다.
*
* @param clientId 클라이언트 ID
* @return SSEEmitter
*/
@GetMapping(value = "/stream/{clientId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter stream(@PathVariable String clientId) {
SseEmitter emitter = new SseEmitter();
// 연결 완료, 오류, 타임아웃 이벤트 핸들러 등록
emitter.onCompletion(() -> {
emitters.remove(clientId);
cleanupEmitter(emitter);
});
emitter.onError((ex) -> {
emitters.remove(clientId);
cleanupEmitter(emitter);
});
emitter.onTimeout(() -> {
emitters.remove(clientId);
cleanupEmitter(emitter);
});
// 클라이언트 ID와 SSEEmitter를 맵에 저장
emitters.put(clientId, emitter);
return emitter;
}
/**
* 특정 클라이언트에게 이벤트를 전송합니다.
* 클라이언트 ID를 경로 변수로 받아와 해당 클라이언트의 SSEEmitter를 찾아 이벤트를 전송합니다.
*
* @param clientId 클라이언트 ID
* @param message 전송할 메시지
*/
@GetMapping("/send/{clientId}")
public void sendEventToClient(@PathVariable String clientId, String message) {
SseEmitter emitter = emitters.get(clientId);
if (emitter != null) {
try {
emitter.send(SseEmitter.event().data(message));
} catch (IOException e) {
emitter.completeWithError(e);
}
}
}
/**
* 모든 클라이언트에게 이벤트를 전송합니다.
* 현재 연결된 모든 SSEEmitter를 순회하며 이벤트를 전송합니다.
*
* @param message 전송할 메시지
*/
@GetMapping("/send/all")
public void sendEventToAll(String message) {
for (SseEmitter emitter : emitters.values()) {
try {
emitter.send(SseEmitter.event().data(message));
} catch (IOException e) {
emitter.completeWithError(e);
}
}
}
/**
* SSEEmitter를 정리하고 완료 처리합니다.
*
* @param emitter SSEEmitter
*/
private void cleanupEmitter(SseEmitter emitter) {
try {
emitter.complete();
} catch (Exception e) {
// 예외 처리
}
}
}
'Web' 카테고리의 다른 글
웹 서버 vs 웹 어플리케이션 서버 vs CGI 프로그램: 차이 쉽게 이해하기 (0) | 2023.08.31 |
---|---|
Nginx: 웹 서버와 리버스 프록시의 개념과 용도, 사용법 설명 (0) | 2023.06.28 |
확장성을 위한 Tomcat 클러스터링 구성과 설정 방법 (0) | 2023.06.28 |
Tomcat 성능 향상: 스레드 풀과 커넥터 설정 사용하기 (0) | 2023.06.28 |
Apache Tomcat 아파치 톰캣: 웹 애플리케이션 서버의 기능과 사용법 알아보자 (0) | 2023.06.27 |