알림 서비스과 같이 서버에서 클라이언트에게 데이터를 전송하는 방법들중에 Polling
, WebSocket
방식을 사용해 본적이 있습니다. 또 하나의 방법인 Server Sent Events
에 대해 간단하게 구현해보고 다른 방식들과 장단점도 정리해 보았습니다.
server-sent events
란 클라이언트(웹페이지)의 요청없이도 언제든지 서버가 새로운 데이터를 보내는 것이 가능하게 해주는 방법입니다. 단방향 통신 방법입니다.
특징을 정리하자면,
EventSource
를 사용해 간단히 구현할 수 있습니다.EventSource
는 연결이 끊어질 경우 자동으로 재연결을 시도하므로 안정적인 데이터 스트림을 제공합니다.단점
그래서 해당 특징을 고려해서 적합한 곳은
SSE, WebSocket 그리고 Polling과 서로 특징을 비교한 것을 정리해보았습니다.
특징 | Server-Sent Events | WebSocket | Polling |
---|---|---|---|
통신 방향 | 단방향 (서버 → 클라이언트) | 양방향 | 단방향 (클라이언트 → 서버) |
데이터 전송 | 텍스트 (UTF-8) | 텍스트/바이너리 | 텍스트 |
복잡성 | 낮음 | 높음 | 낮음 |
실시간성 | 높음 | 매우 높음 | 주기에 따라 다름 |
브라우저 지원 | 최신 브라우저 지원 | 모든 주요 브라우저 | 모든 브라우저 |
방화벽/프록시 문제 | 적음 | 있음 | 없음 |
적합한 예시 | 실시간 알림, 단순 스트림 | 채팅, 게임, 실시간 협업 | 주기적인 상태 업데이트 |
운영하는데에 중요한 시스템 자원 관점에서도 정리해보았습니다.
자원 요소 | SSE | WebSocket | Polling |
---|---|---|---|
서버 자원 사용 | 중간 (지속적인 연결 유지) | 높음 (지속적인 TCP 연결 관리) | 높음 (주기적 요청 처리 부하) |
네트워크 사용량 | 낮음 (텍스트 전송, 재연결) | 낮음 (연결 재사용, 바이너리 지원) | 높음 (HTTP 헤더 오버헤드) |
클라이언트 자원 사용 | 낮음 | 높음 (양방향 통신 관리) | 중간 (주기적 요청) |
적합한 사용을 정리해보면
생각했던것보다 엄청 간단하게 구현할 수 있었습니다. 따로 추가해야할 의존성패키지도 없었습니다.
구현해야하는 부분들을 간단히 정리해 보자면
SseEmitter
서버에서 클라이언트로 이벤트를 스트리밍할 수 있도록 하는 객체ScheduledExecutorService
: 비동기 처리를 스케줄링 해주는 서비스scheduleAtFixedRate
: 반복 실행 함수(javaScript의 setTimeout와 유사)send
: 메시지 전송completeWithError
: 에러메시지 전송 후 스트림 종료onCompletion
: 완료시 취해야할 동작 지정// 간단하게 사용할 메시지 데이터
record MessageData(String content, String author, String currentDate){}
@RestController
public class SseController{
@GetMapping("/sse")
public SseEmitter streamEvents(){
SseEmitter emitter = new SseEmitter(30*60*1000L);
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(()->{
try {
emitter.send(new MessageData("안녕","홍길동", LocalDateTime.now().toString()));
}catch (IOException e){
emitter.completeWithError(e);
}
}, 0, 1, TimeUnit.SECONDS);
emitter.onCompletion(executor::shutdown);
return emitter;
}
}
Client 에도 따로 설치해야할 부분이 없어서 매우 간단했습니다.
간단히 정리해보면
eventSource
: 이벤트 스트림을 관리하는 객체, 매개 변수에 sse
에 의 endpoint입력onmessage
: 전송된 메시지 수신 메서드oneerror
: 오류 처리 메서드
// 전송 데이터 객체
interface MessageData{
content: string;
author: string;
currentDate: string;
}
const SseComponent = () => {
const [message, setMessage] = useState<MessageData>();
useEffect(() => {
// EventSource 인스턴스 생성
const eventSource = new EventSource('http://localhost:8080/sse');
// 메시지 수신
eventSource.onmessage = (event) => {
const data: MessageData = JSON.parse(event.data);
setMessage(data);
};
// 오류 처리
eventSource.onerror = (error) => {
console.error('SSE 연결 오류:', error);
eventSource.close();
};
// 컴포넌트 언마운트 시 연결 종료
return () => {
eventSource.close();
};
}, []);
return (
<div>
<h1>Server-Sent Events</h1>
<p>수신한 메시지</p>
<p>content: {`${message?.content}`}</p>
<p>author: {`${message?.author}`}</p>
<p>currentTime: {`${message?.currentDate}`}</p>
</div>
);
};
SSE는 단방향 통신이 필요한 실시간 데이터 전송에 적합하며, 간단한 구현과 자동 재연결 기능등을 제공합니다. 하지만 양방향 통신과 같은 실시간성, 바이너리 데이터를 전송하는 경우에는 적절하지 않습니다. 각 기술들간의 특징들을 잘 파악해서 요구사항에 대해 적절하게 사용하는 것이 중요하다고 생각합니다.