SSE와 WebSocket, 그들은 왜 실시간 통신의 라이벌이 되었을까?
알림과 실시간 채팅 같은 서비스를 구현할 때, 우리는 자연스럽게 두 가지 기술 사이에서 고민에 빠지게 됩니다. SSE와 WebSocket은 각기 다른 매력을 지닌 두 명의 주인공과 같습니다.
이 포스팅에서는 SSE와 WebSocket의 특징과 사용법을 비교해 보면서, ‘내 프로젝트에서는 어떤 녀석이 주인공일까?’에 대한 답을 찾아보도록 하겠습니다.
실시간 통신의 필요성
여러분의 앱이 최신 주식 가격을 보여주거나 "우리 동네에 눈이 내립니다!" 같은 알림을 띄워야 한다면, 실시간 통신은 선택이 아니라 필수입니다.
그런데 문제는 단방향이냐, 양방향이냐! 양방향 통신을 택하면 더 복잡하지만 매력적이고, 단방향 통신은 더 기능이 적지만 그만큼 단순합니다. 둘 중 어느 쪽을 고를지 결정하기 위해선 기술들의 성격을 조금 더 들여다봐야 합니다.
SSE (Server-Sent Events)
SSE는 마치 선생님이 수업 시간 내내 일방적으로 학생들에게 말하는 것과 같습니다. 학생(클라이언트)은 계속해서 듣고 받아 적어야 하죠.
- 학생: 선생님, 질문이 있습니다
- SSE: 미안, 넌 질문할 수 없어!
SSE는 서버가 클라이언트에게 일방적으로 데이터를 푸시할 때 쓰입니다. HTTP 기반이라서 프록시나 방화벽이 거부하지 않습니다.
서버에서 매초마다 데이터를 보낸다고 상상해 볼까요?
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
@RestController
public class SSEController {
@GetMapping("/sse")
public void streamEvents(HttpServletResponse response) throws IOException {
response.setContentType("text/event-stream");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
for (int i = 1; i <= 5; i++) {
writer.write("data: SSE 이벤트 #" + i + "\n\n");
writer.flush();
try {
Thread.sleep(1000); // 1초마다 이벤트 전송
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
writer.close();
}
}
const eventSource = new EventSource('/sse');
eventSource.onmessage = function(event) {
console.log("새 이벤트 수신:", event.data);
};
eventSource.onerror = function() {
console.error("SSE 연결 오류 발생");
};
장점:
- SSE는 자동 재연결을 지원합니다.
- 설정이 간단해서 빨리 구현하고 싶은 개발자에겐 최적입니다.
단점:
- 오직 단방향입니다. 클라이언트는 서버에 말을 걸 수 없어요. (서운하지만 어쩔 수 없습니다.)
- 데이터를 텍스트로만 보내는 게 편합니다. 이미지나 비디오 전송은 꿈도 꾸지 마세요.
WebSocket
WebSocket은 연애 초기의 커플 같아요. 둘이 채팅하느라 시간 가는 줄 모르죠. 양방향 통신이 가능하기 때문에 서버와 클라이언트가 자유롭게 대화를 주고받습니다.
- A: "지금 뭐 해?"
- B: "지금 네 메시지 받았어. 다시 보낼게!"
이제 서버와 클라이언트가 한바탕 신나게 대화하는 예제를 보겠습니다.
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new TextWebSocketHandler() {
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message)
throws Exception {
session.sendMessage(new TextMessage("에코: " + message.getPayload()));
}
}, "/ws");
}
}
const socket = new WebSocket('ws://localhost:8080/ws');
socket.onopen = () => {
console.log('WebSocket 연결 성공!');
socket.send('안녕, 서버!');
};
socket.onmessage = (event) => {
console.log('받은 메시지:', event.data);
};
장점:
- 양방향이라 서버와 클라이언트가 마음껏 수다를 떨 수 있습니다.
- 텍스트뿐만 아니라 바이너리 데이터도 전송할 수 있어요. (그림도 보낼 수 있다는 얘기죠!)
단점:
- 설정이 조금 귀찮습니다. 방화벽에 걸리기 쉽고, 초기 핸드셰이크까지 해야 하니까요.
- 재연결? 그런 거 없습니다. 알아서 다시 연결할 방법을 짜야 해요.
SSE vs WebSocket 이 둘을 어떻게 고를까?
비교 항목 | SSE | WebSocket |
통신 방식 | 단방향 (서버 → 클라이언트) | 양방향 (서버 ↔ 클라이언트) |
사용 사례 | 실시간 알림, 뉴스 피드 | 채팅, 게임, 협업 도구 |
프로토콜 | HTTP/1.1 | TCP |
재연결 지원 | 자동 재연결 지원 | 별도 구현 필요 |
설정의 간편함 | 매우 간편 | 복잡함 |
방화벽 친화성 | 우호적 | 주의 필요 |
마무리 및 결론
- SSE는 "너 듣기만 해, 내가 다 말해줄게" 스타일에 적합합니다. 실시간 뉴스나 알림처럼 서버에서 클라이언트로만 정보가 흐를 때 좋습니다.
- 반면에, WebSocket은 "같이 떠들자!" 스타일입니다. 채팅, 게임처럼 상호작용이 필요한 경우 선택하세요.
저는 개인적으로 어드민 UI에서 서버 로그를 실시간으로 출력할 때 SSE를 사용했습니다. 클라이언트가 서버에게 말할 필요가 없었기 때문에 SSE가 완벽한 선택이었습니다. 만약 반대로 채팅 앱을 만들었다면? WebSocket이 당연히 주인공이 되었겠죠.
이제 여러분도 SSE와 WebSocket 중 누구를 주인공으로 선택할지 결정할 수 있겠죠?