WebSocket과 Starstream에 대해서 알아보자.

이번엔 실시간 데이터 스트리밍 로직을 한번 공부해보기 위해 WebSocket의 대한 개념과 Starstream에 대한 내용을 한번 알아보고자 한다!

etc-image-0

먼저 WebSocket은 클라이언트와 서버 간의 전이중 통신을 지원한다. 이 말의 뜻은 양측이 동시에 데이터를 주고 받을 수 있음을 의미하며, 실시간 채팅, 금융 데이터 스트리밍, 온라인 게임 등 빠른 응답이 필요한 앱에서 주로 사용되곤 한다.

 

HTTP는 요청-응답 방식으로, 매번 새로운 연결을 생성하지만, WebSocket은 초기 핸드쉐이크를 통해 한 번 연결이 성립되면 그 연결은 지속적으로 유지되는 특징을 가지고 있다. 이로 인해 반복적인 연결 설정 비용이 줄어들고, 실시간 데이터 전송에 최적화 된다!! 아주 중요한 개념이니 꼭 잊지 말도록 하자.

 

WebSocket의 동작 원리는?

그렇다면 WebSocket의 동작원리는 무엇일까.

 

WebSocket 통신은 먼저 HTTP 기반의 핸드쉐이크로 시작하게 된다. 클라이언트가 서버에 WebSocket 연결 요청을 보내면, 서버는 요청을 수락하면서 연결을 설정한다.

 

  • 클라이언트 요청

클라이언트는 HTTP 요청 헤더에 Upgrade: websocket과 Connection: Upgrade를 포함해 서버에 WebSocket 전환을 요청한다.

 

  • 서버 응답

서버는 요청을 검증한 후 동일한 헤더를 포함한 101 Switching Protocols 응답을 보내며, 이후 부터는 HTTP가 아닌 WebSocket 프로토콜을 사용하게 된다!

 

  • 데이터 프레임

연결이 설정된 후 데이터는 프레임 단위로 주고받게 되는데 각 프레임은 텍스트 또는 바이너리 데이터로 구성될 수 있고 경량화된 메세지 전송 형식을 사용해 오버헤드를 최소화 한다!!

 

WebSocket은 명시적으로 종료되거나 네트워크 문제로 인한 비정상 종료가 있을때까지 지속되게 되는데 이로 인해 실시간 데이터 전송이 효율적으로 이루어지게 된다.

 

연결을 종료하고 싶다면 클라이언트나 서버 모두 종료 요청을 보낼 수 있고 종료 시 종료 코드와 이유를 포함한 메세지가 교환된다.

 

WebSocket의 장점

실시간 데이터 전송

양방향 통신이 가능하기 때문에 서버가 새로운 데이터를 즉시 클라이언트에 push 할 수 있으며, 실시간 앱개발에 이상적인 측면이 있다.

 

낮은 오버헤드

초기 연결 이후에는 HTTP 헤더 등 불필요한 정보 없이 경량화된 데이터 프레임만을 주고 받으므로, 네트워크 오버헤드가 크게 줄어든다.

 

효율적인 자원 사용

지속적인 연결을 사용하므로, 연결 설정과 해제에 따른 비용이 없고, 서버와 클라이언트 모두 적은 리소스로 많은 요청을 처리할 수 있다.

 

양방향 통신

클라이언트와 서버가 독립적으로 데이터를 전송할 수 있어, 실시간 알림, 게임, 협업 툴 등 다양한 분야에서 활용된다!

 

WebSocket과 기존 HTTP 통신과의 다른점은?

웹소켓과 HTTP 통신과 차이점은 크게 세가지로 나눌 수 있다.

etc-image-1

 

첫번째로 연결방식

HTTP는 요청과 응답마다 새로운 연결이 생성되고 응답이 되면 그 연결이 종료되지만 웹소켓은 한번 연결되면 지속적인 연결 상태를 유지한다.

 

두번째로 데이터 전송 효율성인데

HTTP는 매 요청마다 전체 헤더 정보가 포함되어 전송되기 때문에 오버헤드가 발생하지만 웹소켓은 초기 핸드쉐이크 이후에는 경량화된 프레임 단위로 데이터를 주고받아 효율적이다.

 

마지막 세번째로 실시간성!

HTTP는 폴링이나 롱 폴링 기법을 사용해야하므로 실시간성이 떨어질 수 있으나 웹소켓은 실시간 양방향 통신이 기본으로 지원되어 지연 시간이 짧다는 특징이 있다.


지금까지 알아본 간단한 웹소켓의 개념과 기존의 네트워크 연결과의 차이점에 대해 알아보았다. Swift에서 Starstream을 활용해서 웹소켓을 활용한 구현 방식에 대해서 알아 보도록 하자!

 

먼저 바이낸스의 API와 Starstream을 활용해 데이터 호출을 활용해보자!

import SwiftUI
import Combine
import Starstream

class BinanceStreamViewModel: ObservableObject {
    @Published var isConnected: Bool = false
    @Published var lastMessage: String = ""
    
    private var stream: Starstream?
    
    // 웹소켓 연결을 여는 함수
    func connectToBinanceStream() {
        // 바이낸스 BTC/USDT 거래 스트림 URL 설정
        guard let url = URL(string: "wss://stream.binance.com:9443/ws/btcusdt@trade") else {
            print("유효하지 않은 URL입니다.")
            return
        }
        
        // Starstream 인스턴스 생성: 내부에서 HTTP 핸드쉐이크를 수행하며 WebSocket 연결을 설정함
        stream = Starstream(url: url)
        
        // 메시지 수신 이벤트 처리
        stream?.onMessage = { [weak self] message in
            // Starstream이 전달하는 메시지를 문자열로 처리 (실제 프로젝트에서는 JSON 파싱 필요)
            DispatchQueue.main.async {
                self?.lastMessage = message
                print("받은 메시지: \\(message)")
            }
        }
        
        // 에러 처리
        stream?.onError = { error in
            print("WebSocket 에러 발생: \\(error)")
        }
        
        // 연결 종료 이벤트 처리
        stream?.onClose = {
            DispatchQueue.main.async {
                self.isConnected = false
            }
            print("WebSocket 연결이 종료되었습니다.")
        }
        
        // 연결 시작: 이 시점에서 Starstream이 바이낸스 서버와 연결을 시도함
        stream?.connect()
        isConnected = true
        print("WebSocket 연결 시도 중...")
    }
}

하나의 함수를 통해 해당 엔드포인트를 지정하고 에러, 종료, 연결 관련 로직을 구현하는 과정인데

 

isConnected와 lastMessage를 통해 연결 상태와 마지막 수신 메시지를 관리할 수 있도록 설정하고 바이낸스 API를 연결하기 위해 거래 스트림 URL을 가져와 Starstream 인스턴스를 생성한다.

 

내부적으로 스타스트림은 HTTP 핸드쉐이크를 통해 웹소켓 연결을 초기화 하며, 이후 메세지, 에러, 종료 이벤트를 처리한다.

 

그 이후 특정 이벤트 (에러, 종료 등)을 처리할 수 있는 예외처리를 설정해둔 뒤 stream?.connect()를 호출해 바이낸스 서버와 연결을 시도하게 되면 이 시점부터 웹소켓 연결이 활성화 되어 실시간 데이터 스트리밍이 시작된다.

starstream.gif


지금은 웹소켓을 편리하게 라이브러리를 활용해 구현했지만 추후 프로토콜을 활용해 직접 웹소켓을 구현해 조금 더 깊게 공부해볼 수 있는 기회를 가져보려고 한다.

 

오늘은 여기까지!!