NOTE
이 문서는 작성 중
1. 통신 계층을 넘어서 성능을 극복한 사례
- 스포티파이는 초창기 데스크탑 앱에서 자체 TCP 기반 프로토콜과 P2P 기술을 활용하여 HTTP의 제약을 벗어남
- 자체 CDN과 클라이언트 간 분산 전달로 대규모 요청 분산 처리 및 레이지 로딩 없는 음원 스트리밍을 구현함
- WebSocket이나 REST API 같은 범용 프로토콜보다 더 낮은 레벨에서 동작하는 통신 설계로 극한의 최적화를 달성함
2. 브라우저 환경에서의 제약
- 웹에서는 브라우저 샌드박스 정책으로 인해 독자적인 네트워크 프로토콜 사용이 불가능함
- 브라우저가 허용하는 통신 수단은 HTTP(S), WebSocket, WebRTC, Fetch, XHR, MSE 등에 한정됨
- 따라서 스포티파이 웹 버전은 브라우저가 허용하는 범위 안에서의 최적화에 집중함
3. 스포티파이 웹앱의 최적화 전략
- WebSocket을 활용한 실시간 제어 및 서버 이벤트 처리
- 오디오 스트리밍은 MPEG-DASH 또는 HLS 기반으로 처리하며, CDN 캐시 및 prefetching 전략을 통해 대기 시간을 최소화함
- 오디오 데이터를 조각화하여 초기 구간만 빠르게 로드함으로써 즉시 재생처럼 보이게 하는 UX 연출을 구현함
4. 스포티파이의 독자 프로토콜: Mercury Protocol
- 스포티파이의 내부 통신 프로토콜은 일반적으로 Mercury Protocol로 불리며, 공식 명칭은 없으나 개발자 커뮤니티에서 통용되는 명칭임
- TCP 기반의 암호화된 바이너리 메시지 기반 프로토콜이며, protobuf를 메시지 포맷으로 사용함
- 주 용도는 사용자 인증, 메타데이터 요청, 스트리밍 세션 생성, 제어 명령 처리 등이며, 웹 환경에서는 사용할 수 없음
- librespot, despotify 등의 리버스엔지니어링 프로젝트에서 Mercury 프로토콜의 구조와 동작 방식이 구현되어 있음
5. Electron에서 독자 프로토콜 흉내내기
- Electron은 Node.js 환경이 포함된 데스크탑 애플리케이션이므로, HTTP 외의 TCP 소켓, UDP, WebRTC 등의 저수준 네트워크 프로토콜 접근이 가능함
- Electron main process에서 Node의
net.Socket을 활용하여 커스텀 서버와의 바이너리 통신 구조를 구성할 수 있음 - preload.js를 통해 렌더러 프로세스(웹 뷰)와 main 프로세스 간 브릿지를 구성, UI에서 “독자 프로토콜처럼 보이는 통신” 구현이 가능함
- 이 방식은 Electron 앱 내에서 Mercury 유사 프로토콜의 구조를 흉내낼 수 있음을 의미함
구조 개요
📁 your-electron-app/
├── main.js // Electron main process
├── preload.js // 브라우저 ↔ Node 브리지
├── renderer.js // 웹 페이지 (브라우저 환경)
├── tcp-client.js // TCP 소켓 클라이언트 (Node net 모듈)
Node.js에서 tcp 통신
// tcp-client.js
const net = require('net');
function connectToCustomServer({ host, port }) {
const client = new net.Socket();
client.connect(port, host, () => {
console.log('Connected to custom server');
client.write('Hello from Electron!');
});
client.on('data', (data) => {
console.log('Received:', data.toString());
});
client.on('close', () => {
console.log('Connection closed');
});
return client;
}
module.exports = { connectToCustomServer };
main.js
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const { connectToCustomServer } = require('./tcp-client');
let mainWindow;
app.whenReady().then(() => {
mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});
mainWindow.loadFile('index.html');
});
ipcMain.handle('custom-protocol:connect', (_, args) => {
connectToCustomServer(args); // args: { host, port }
});
preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('customProtocol', {
connect: (host, port) =>
ipcRenderer.invoke('custom-protocol:connect', { host, port }),
});
renderer.js
// 예: 사용자 버튼 클릭 시 TCP 연결 요청
document.getElementById('connect-btn').addEventListener('click', () => {
window.customProtocol.connect('127.0.0.1', 9000);
});
index.html
<!DOCTYPE html>
<html>
<head>
<title>Custom Protocol</title>
</head>
<body>
<h1>Custom Protocol Test</h1>
<button id="connect-btn">Connect to Custom Server</button>
<script src="renderer.js"></script>
</body>
</html>
6. 결론
- 브라우저 환경에서는 네트워크 계층의 제한으로 인해 Mercury 프로토콜 수준의 최적화는 구현이 불가능함
- 데스크탑 애플리케이션(Electron 등)을 활용하면 Node.js 환경에서 TCP 통신 및 바이너리 프로토콜 구현이 가능하여 독자 프로토콜 수준의 통신 구조를 구현 가능함
- 스포티파이의 네트워크 전략은 브라우저와 데스크탑 앱 간의 기술적 한계와 장점을 모두 활용한 복합적인 접근 방식임
참고
- ChatGPT 4